<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" 
  xmlns:atom="http://www.w3.org/2005/Atom" 
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>sgo.to</title>
    <description>Hello World</description>
    <link>https://code.sgo.to</link>
    <atom:link href="https://code.sgo.to/rss.xml" rel="self" type="application/rss+xml" />

    
      <item>
        <title>Delegation Devtrial</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <p>This is a demo of the <a href="https://github.com/w3c-fedid/delegation">Delegation-oriented FedCM</a> Devtrials.</p>
<p>Step 0) Download and open <a href="https://www.google.com/chrome/canary/">chrome canaries</a></p>
<p>Step 1) Enable necessary development flags. Type <code>chrome://flags</code> in the url bar and enable the following flags</p>
<ul>
<li>#fedcm-without-well-known-enforcement</li>
<li>#fedcm-delegation (in devtrials)</li>
</ul>
<p>Step 2) Use <a href="https://github.com/samuelgoto/static-issuer">my own test issuer</a> or enter yours:</p>
<textarea id="issuer" cols="80" rows="3">https://issuer.sgo.to/fedcm.json</textarea>
<p>Step 3) Click on this button <button onclick="signin()">Sign-in</button></p>
<p>Step 4) See the resulted token in this textarea:</p>
<textarea id="token" cols="80" rows="10"></textarea>
<p>Step 5) Split the token between the SDJWT and the KB by &quot;~&quot; and base64 decode:</p>
<textarea id="sdjwt.header" cols="80" rows="3"></textarea>
<textarea id="sdjwt.body" cols="80" rows="10"></textarea>
<textarea id="sdjwt.signature" cols="80" rows="3"></textarea>
<p>Step 6) Parse the disclosures:</p>
<textarea id="disclosures" cols="80" rows="10"></textarea>
<p>Step 7) Parse the KB:</p>
<textarea id="kb.header" cols="80" rows="3"></textarea>
<textarea id="kb.body" cols="80" rows="10"></textarea>
<textarea id="kb.signature" cols="80" rows="3"></textarea>
<p>Step 8) Check for signatures and hashes and so on (left as an exercise).</p>
<script>
async function signin() {
 const issuer = document.getElementById("issuer").value;
 const {token} = await navigator.credentials.get({
  identity: {
  mode: "active",
  providers: [{
    configURL: issuer,
    format: "vc+sd-jwt",
    nonce: "1234",
    clientId: "5678"
   }]
  }});

  main(token);
}

async function main(token) {
 const [sdjwt, ...disclosures] = token.split("~");

 const kb = disclosures.pop();
 
 document.getElementById("token").value = token; 

 const [header, body, signature] = sdjwt.split(".");
 document.getElementById("sdjwt.header").value = atob(header); 
 document.getElementById("sdjwt.body").value = JSON.stringify(JSON.parse(atob(body)), undefined, 2); 
 document.getElementById("sdjwt.signature").value = signature; 

 document.getElementById("disclosures").value = disclosures.map((disclosure) => atob(disclosure)).join("\n"); 

 const [h, b, s] = kb.split(".");
 
 document.getElementById("kb.header").value = atob(h); 
 document.getElementById("kb.body").value = JSON.stringify(JSON.parse(atob(b)), undefined, 2); 
 document.getElementById("kb.signature").value = s; 
}

const token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImprdSI6Imh0dHBzOi8vaXNzdWVyLnNnby50by9qd2tzLmpzb24ifQ.eyJjbmYiOnsiandrIjp7ImFsZyI6IkVTMjU2IiwiY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJDeUwwWW9GaS1pN3FLUmQxS200UTRrLVBjV2RTN082SkkyVzFYWGlOVGUwIiwieSI6IlZ1aXc0TDhhQWNOMUh3TkwwSnVVWVEwejJoYmVkUjcwcVFNTkttNFlMc2MifX0sIl9zZCI6WyJoNU82UE04VndlOV9uaXJkb1BzTHRJMDRSb3VFaWZtZmxGemkwU2h0eldjIiwiU1pnTnFCeUNIMFFBTy1zLUx4d19XQ0l1djNCcmJDNDhCU3FhOVl5Q0xidyIsIlF0ck1vQ3VSbl93S2otZnRtbHdPZnlCWmJiak1hU2hZd29ON1h3WWxqVFkiLCJGSXF5VEluaXI3XzJxdTRhSjB3dTM2NzcxRVRmWmEwTF9qOHhrMWprejNzIiwiUjEwemtvV3J4cUVhUm1seGZfajVRWWEtWDVBR19zZ0NYdnlhaE8tcjJMbyIsIjhWaVZaQU9oeWYyM1Eya1Ftal9qREtFRFl3dlo1WTNHQUpvWXp1MzRwalEiXX0.Oy7eR4LDYnVhWaAVf6fiJO95zy6FwHahFB_H-wmQnhse6me_0V7DUJDumo5OJ4JJFj3MrNHGVNhK_Dl3TDME7g~WyJVaG9mcDBGeTh0RmUzbnhKY0VVWjdtaVRyaG1MWTdqdEhNMVJvbm52SjdrIiwibmFtZSIsIlNhbSBHb3RvIl0~WyJfTmhNbUN6R2dmS05nQXM5TElxc3dnLWpBTWRJUXlZclVnajhBTXNkS0E0IiwiZW1haWwiLCJtZUBzZ28udG8iXQ~WyJfbVF6RXM2MldXUGNJR083RHo2V3lsNHhWT0JleEJfd1BsSXphOFIzbTBFIiwicGljdHVyZSIsImh0dHBzOi8vcGJzLnR3aW1nLmNvbS9wcm9maWxlX2ltYWdlcy85MjA3NTgwMzkzMjU1NjQ5MjgvdnAwUHg0a0NfNDAweDQwMC5qcGciXQ~eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJpYXQiOjE3NzA4MzQ1MTgsIm5vbmNlIjoiMTIzNCIsInNkX2hhc2giOiJmY010RTR1LThfYXNURWtvSkpjQ3AwXzZ6cHRhWVNNYm41T3EtcE5PZTFJIn0.KjvWlxWeMbBUW7Hd-ycgNXM5GJNibMq6X4Yf4QTLilSUHZEx_ZhEGAaq7lsZ_ONwFk2MZ30rnHyCSZp47yfF0Q";
 
// main(token);
</script>
        ]]></description>
        <pubDate>Tue Feb 10 2026 16:00:00 GMT-0800 (Pacific Standard Time)</pubDate>
        <link>https://code.sgo.to/2026/02/11/delegation-devtrial.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2026/02/11/delegation-devtrial.html</guid>
      </item>

      <item>
        <title>&amp;lt;login&amp;gt; Devtrial</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <p>This is a demo page for https://github.com/fedidcg/login-element</p>
<textarea id="log" rows=10 cols=80>
</textarea>
<p>Click on the following element:</p>
<style>
login {
  border: 1px solid black;
  padding: 1em;
  cursor: pointer;
}
</style>
<login oncomplete="alert(this.credential)">
  <credential type="federated" clientId="5678" configURL="https://idp.sgo.to/fedcm.json">
   Sign-in with my IdP
 </credential>
</login>
<script>
function log(str) {
 document.getElementById("log").value += "> " + str + "\n";
}
if (window.HTMLLoginElement) {
  log("Your browser supports <login>!");
} else {
  log("Your browser does not support <login> :(");
}
</script>
        ]]></description>
        <pubDate>Wed Jan 28 2026 16:00:00 GMT-0800 (Pacific Standard Time)</pubDate>
        <link>https://code.sgo.to/2026/01/29/login.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2026/01/29/login.html</guid>
      </item>

      <item>
        <title>Email Verification Protocol Devtrial</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <div class="evp-page">
  <section class="auth-hero" id="auth-hero" aria-labelledby="auth-title">
    <div class="auth-copy">
      <p class="eyebrow">Verified email sign-in</p>
      <h1 id="auth-title">Sign in with your verified email</h1>
      <p class="auth-lede">Use browser autofill to prove control of your email address and continue without leaving this page.</p>
      <div class="auth-proof-list" aria-label="Verification benefits">
        <span>Signed by your email provider</span>
        <span>Bound to this browser session</span>
        <span>No emailed code to copy</span>
      </div>
    </div>
    <div class="auth-panel" aria-live="polite">
      <div id="auth-result" class="auth-result" hidden>
        <div class="auth-result-mark" aria-hidden="true"></div>
        <p class="auth-result-kicker" id="auth-result-kicker"></p>
        <h2 id="auth-result-title"></h2>
        <p id="auth-result-body"></p>
        <div id="otp-fallback" class="otp-fallback" hidden>
          <form id="otp-form">
            <label for="otp-input">One-time code</label>
            <div class="otp-row">
              <input id="otp-input" name="otp" inputmode="numeric" autocomplete="one-time-code" placeholder="123456" required>
              <button type="submit">Verify code</button>
            </div>
            <p id="otp-error" class="otp-error" hidden></p>
          </form>
        </div>
        <div class="auth-actions">
          <button id="logout-button" class="logout-button" type="button" hidden>Log out</button>
        </div>
      </div>
      <form id="signup-form" class="signup-form" method="get" action="/2024/10/25/verified-email-autocomplete.html">
        <label for="email-input">Email address</label>
        <div class="email-row">
          <input id="email-input" type="email" name="email" autocomplete="email" placeholder="you@example.com" required>
          <button type="submit">Sign in</button>
        </div>
        <input type="hidden" name="evt" nonce="--a-fake-nonce--" autocomplete="email-verification-token">
      </form>
      <p class="form-note">When your browser supports EVP, autofill adds a signed verification token before sign-in submits.</p>
    </div>
  </section>
  <section id="setup" class="debugging" aria-labelledby="setup-title">
    <p class="eyebrow">Debugging setup</p>
    <h2 id="setup-title">How to try this page</h2>
    <ol class="setup-list">
      <li>Install <a href="https://www.google.com/chrome/canary/">Chrome Canary</a>.</li>
      <li>Go to <code>chrome://flags/</code>, search for <code>Email Verification Protocol</code>, enable <code>#email-verification-protocol</code>, and restart the browser.</li>
      <li>Make sure you are logged in to the domain of your email address.</li>
      <li>Use the sign-in form above and autofill your email address.</li>
    </ol>
  </section>
  <section id="debug" class="debugging debug-log" hidden aria-labelledby="log-title">
    <p class="eyebrow">Verifier log</p>
    <h2 id="log-title">Protocol checks</h2>
    <div class="browser">
      <div id="log"></div>
    </div>
  </section>
</div>
<style>
  [hidden] {
    display: none !important;
  }

  .evp-page {
    margin: 1.5rem 0 0;
    padding: 0;
    color: inherit;
    font-family: inherit;
  }

  .auth-hero {
    display: block;
    margin: 0 0 1.5rem;
    padding: 1.25rem;
    background: #f8faf9;
    border: 1px solid #d9e4dc;
    border-radius: 8px;
  }

  .auth-hero[data-state="checking"],
  .auth-hero[data-state="success"],
  .auth-hero[data-state="error"] {
    margin: 0 0 1.5rem;
    padding: 0;
    background: transparent;
    border: 0;
  }

  .auth-copy {
    max-width: none;
  }

  .auth-hero[data-state="checking"] .auth-copy,
  .auth-hero[data-state="success"] .auth-copy,
  .auth-hero[data-state="error"] .auth-copy {
    display: none;
  }

  .eyebrow {
    margin: 0 0 0.5rem;
    color: #0b6b58;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0;
  }

  .auth-copy h1 {
    margin: 0;
    max-width: none;
    color: #111827;
    font-size: 1.75rem;
    line-height: 1.2;
    letter-spacing: 0;
  }

  .auth-lede {
    margin: 0.75rem 0 0;
    max-width: none;
    color: #43505f;
    font-size: 1rem;
    line-height: 1.55;
  }

  .auth-proof-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
  }

  .auth-proof-list span {
    padding: 0.35rem 0.55rem;
    background: #ffffff;
    border: 1px solid #d9e4dc;
    border-radius: 999px;
    color: #253044;
    font-size: 0.875rem;
    line-height: 1.35;
  }

  .auth-panel {
    box-sizing: border-box;
    margin-top: 1.1rem;
    padding: 1rem;
    background: #ffffff;
    border: 1px solid #cfd9d2;
    border-radius: 8px;
  }

  .auth-hero[data-state="checking"] .auth-panel,
  .auth-hero[data-state="success"] .auth-panel,
  .auth-hero[data-state="error"] .auth-panel {
    margin-top: 0;
  }

  .auth-result {
    margin-bottom: 1rem;
    padding: 1rem;
    border: 1px solid #d9e4dc;
    border-radius: 8px;
    background: #f7faf7;
  }

  .auth-result-mark {
    display: flex;
    width: 1.6rem;
    height: 1.6rem;
    margin-bottom: 0.75rem;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: #0b6b58;
    color: #ffffff;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1;
  }

  .auth-result-mark::after {
    content: "";
    display: block;
    width: 0.4rem;
    height: 0.7rem;
    margin-top: -0.1rem;
    border: solid #ffffff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
  }

  .auth-result-kicker {
    margin: 0 0 0.35rem;
    color: #0b6b58;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0;
  }

  .auth-result h2 {
    margin: 0;
    color: #111827;
    font-size: 1.35rem;
    line-height: 1.2;
    letter-spacing: 0;
  }

  #auth-result-body {
    margin: 0.6rem 0 0;
    color: #43505f;
    line-height: 1.45;
  }

  .auth-hero[data-state="checking"] .auth-result-mark {
    background: #2f5d8c;
  }

  .auth-hero[data-state="checking"] .auth-result-mark::after {
    content: "...";
    width: auto;
    height: auto;
    margin: 0 0 0.15rem;
    border: 0;
    transform: none;
  }

  .auth-hero[data-state="error"] .auth-result {
    border-color: #f1c6c6;
    background: #fff7f7;
  }

  .auth-hero[data-state="error"] .auth-result-mark {
    background: #b42318;
  }

  .auth-hero[data-state="error"] .auth-result-mark::after {
    content: "!";
    width: auto;
    height: auto;
    margin: 0;
    border: 0;
    transform: none;
  }

  .auth-hero[data-state="error"] .auth-result-kicker {
    color: #b42318;
  }

  .otp-fallback {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid #f1c6c6;
  }

  .otp-fallback label {
    display: block;
    margin-bottom: 0.5rem;
    color: #253044;
    font-size: 0.875rem;
    font-weight: 700;
  }

  .signup-form label {
    display: block;
    margin-bottom: 0.5rem;
    color: #253044;
    font-size: 0.875rem;
    font-weight: 700;
  }

  .email-row,
  .otp-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
  }

  .email-row input,
  .otp-row input {
    box-sizing: border-box;
    flex: 1 1 16rem;
    min-width: 0;
    height: 2.75rem;
    padding: 0 14px;
    border: 1px solid #bac7c0;
    border-radius: 8px;
    color: #111827;
    font: inherit;
  }

  .email-row input:focus,
  .otp-row input:focus {
    outline: 3px solid rgba(11, 107, 88, 0.18);
    border-color: #0b6b58;
  }

  .email-row button,
  .otp-row button {
    flex: 0 0 auto;
    height: 2.75rem;
    padding: 0 18px;
    border: 0;
    border-radius: 8px;
    background: #0b6b58;
    color: #ffffff;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
  }

  .email-row button:hover,
  .otp-row button:hover {
    background: #095748;
  }

  .otp-error {
    margin: 0.75rem 0 0;
    color: #b42318;
    font-size: 0.875rem;
    line-height: 1.45;
  }

  .auth-actions {
    margin-top: 1rem;
  }

  .logout-button {
    height: 2.5rem;
    padding: 0 16px;
    border: 1px solid #bac7c0;
    border-radius: 8px;
    background: #ffffff;
    color: #253044;
    font: inherit;
    font-weight: 700;
    cursor: pointer;
  }

  .logout-button:hover {
    background: #f1f5f3;
  }

  .form-note {
    margin: 0.85rem 0 0;
    color: #617083;
    font-size: 0.8125rem;
    line-height: 1.45;
  }

  .auth-hero[data-state="checking"] .form-note,
  .auth-hero[data-state="success"] .form-note,
  .auth-hero[data-state="error"] .form-note {
    display: none;
  }

  .debugging {
    box-sizing: border-box;
    margin: 1.5rem 0 0;
    padding: 1rem;
    border: 1px solid #dde3ea;
    border-left: 4px solid #dde3ea;
    border-radius: 8px;
    background: #fafafa;
  }

  .debugging h2 {
    margin: 0 0 0.85rem;
    color: #111827;
    font-size: 1.25rem;
    line-height: 1.2;
    letter-spacing: 0;
  }

  .setup-list {
    margin: 0;
    padding-left: 22px;
    color: #43505f;
    line-height: 1.6;
  }

  .setup-list li + li {
    margin-top: 0.5rem;
  }

  .debug-log {
    background: #f8fafc;
  }

  .browser {
    background-color: #0f172a;
    border-radius: 8px;
    padding: 16px;
    overflow: auto;
  }

  #log {
    min-height: 120px;
    color: #dbe7ff;
    font-size: 12px;
    line-height: 1.5;
    font-family: Consolas, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", Courier, monospace;
  }

  #log p {
    margin: 0 0 4px;
    padding: 2px 6px;
    border-radius: 4px;
  }

  .success {
    background-color: #146c43;
    color: white;
  }
 
  .failure {
    background-color: #b42318;
    color: white;
  }

  .warning {
    background-color: #fff3cd;
    color: #5f3b00;
  }

  @media (max-width: 560px) {
    .auth-hero,
    .auth-panel,
    .debugging {
      padding: 1rem;
    }

    .email-row,
    .otp-row {
      display: grid;
      grid-template-columns: 1fr;
    }

    .email-row button,
    .otp-row button {
      width: 100%;
    }
  }

 /**
 .success .status {
   content: "";
   display: inline-block;
   background: url(/static/success.png);
   background-size: contain;
   width: 12px;
   height: 12px;
 }

 .failure .status {
   content: "";
   display: inline-block;
   background: url(/static/failure.png);
   background-size: contain;
   width: 12px;
   height: 12px;
 }
 **/
</style>
<script>
function log(message, style = "") {
 const el = document.createElement("p");
 el.className = style;
 const d = new Date();
 el.textContent = `[${d.toLocaleTimeString()}] ${message}`;
 document.getElementById("log").appendChild(el);
 console.log(message);
}

const EVT_IAT_WINDOW_SECONDS = 10 * 60;
const KB_IAT_WINDOW_SECONDS = 10 * 60;
const FALLBACK_NONCE = "--a-fake-nonce--";
const VERIFICATION_TOKEN_COOKIE = "evp_verification_token";
const VERIFICATION_TOKEN_COOKIE_MAX_AGE_SECONDS = 10 * 60;
let verificationFailureCount = 0;
  
function pass(message) {
  log(message, "success");
  return true;
}

function fail(message) {
  verificationFailureCount++;
  log(message, "failure");
  return false;
}

function warn(message) {
  log(message, "warning");
  return false;
}

function requireStep(condition, successMessage, failureMessage) {
  return condition ? pass(successMessage) : fail(failureMessage);
}

function base64UrlToBase64(base64url) {
  const base64 = base64url.replace(/_/g, '/').replace(/-/g, '+');
  return base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
}

const base64decode = (base64) => JSON.parse(atob(base64UrlToBase64(base64))); 

const jwt = (str) => {
  const header = str.substring(0, str.indexOf("."));
  str = str.substring(header.length + 1);
  
  const payload = str.substring(0, str.indexOf("."));
  str = str.substring(payload.length + 1);

  //const signature = str.substring(0, str.indexOf("~"));
  //str = str.substring(issued.signature.length + 1);
  const signature = str;

  return {
    //header: base64decode(header),
    //payload: base64decode(payload),
    header: header,
    payload: payload,
    signature: signature
  }
};

function parse(token) {
  let str = token;
 
  const first = str.substring(0, str.indexOf("~"));
  str = str.substring(first.length + 1);

  const issued = jwt(first);

  // console.log(jwt);
  //console.log(issued);
  
  const disclosures = [];
  //console.log(str.indexOf("~"));
  while (str.indexOf("~") > 0) {
    const disclosure = str.substring(0, str.indexOf("~"));
    str = str.substring(disclosure.length + 1);
    // console.log("hi");
    disclosures.push(base64decode(disclosure));
  }

  // console.log(disclosures);

  const kb = jwt(str);

  // console.log(kb);
 return [issued, disclosures, kb];
}

// console.log(parse(token));

function stripurlencoding(b64) {
  return base64UrlToBase64(b64);
}

function base64ToArrayBuffer(b64) {
  var byteString = atob(b64);
  var byteArray = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    byteArray[i] = byteString.charCodeAt(i);
  }
  return byteArray.buffer;
}

function textToArrayBuffer(str) {
  var buf = unescape(encodeURIComponent(str)) // 2 bytes for each char
  var bufView = new Uint8Array(buf.length)
  for (var i=0; i < buf.length; i++) {
    bufView[i] = buf.charCodeAt(i)
  }
  return bufView
}

const jinaReader = (url) => `https://r.jina.ai/${url}`;

async function fetchWithTimeout(url, timeoutMs = 8000, init = {}) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, {...init, signal: controller.signal});
  } finally {
    clearTimeout(timeout);
  }
}

async function fetchJsonDirect(url) {
  const response = await fetchWithTimeout(url);
  if (!response.ok) {
    const text = await response.text();
    throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText} ${text}`);
  }
  return response.json();
}

async function readJsonResponse(response, url) {
  const text = await response.text();
  if (!response.ok) {
    throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText} ${text}`);
  }
  try {
    return JSON.parse(text);
  } catch (e) {
    throw new Error(`Failed to parse JSON from ${url}: ${e}. Body: ${text}`);
  }
}

function unwrapJinaReaderJson(json, url) {
  if (!isObject(json) || !isObject(json.data) || typeof json.data.content != "string") {
    return json;
  }
  if (json.code && json.code >= 400) {
    throw new Error(`Jina Reader failed to fetch ${url}: ${json.code} ${json.data.warning || ""}`);
  }
  try {
    return JSON.parse(json.data.content);
  } catch (e) {
    throw new Error(`Failed to parse JSON from Jina Reader content for ${url}: ${e}. Body: ${json.data.content}`);
  }
}

async function fetchJsonWithCorsBypass(url) {
  const proxyUrl = jinaReader(url);
  try {
    log(`Fetching via Jina Reader: ${url}`);
    const json = await readJsonResponse(
      await fetchWithTimeout(proxyUrl, 8000, {headers: {Accept: "application/json"}}),
      proxyUrl);
    pass(`Fetched ${url} via Jina Reader`);
    return unwrapJinaReaderJson(json, url);
  } catch (e) {
    throw new Error(`Failed to fetch ${url} via Jina Reader: ${e.stack || e}`);
  }
}

async function jwks(url) {
  let response;
  try {
    log(`Fetching JWKS directly: ${url}`);
    response = await fetchJsonDirect(url);
  } catch (e) {
    warn(`Direct JWKS fetch failed for ${url}: ${e}. Trying CORS proxy fallback.`);
    response = await fetchJsonWithCorsBypass(url);
  }
  //console.log(response);
  const {keys} = response;
  //console.log(json);
  return keys;
}

async function verify(jwt, jwk) {
  const {header, payload, signature} = jwt;
  const bufSignature = base64ToArrayBuffer(stripurlencoding(signature));

  const data = header + "." + payload; 
  const bufData = textToArrayBuffer(data);

  const {alg} = base64decode(header);
  //console.log();
  //console.log(jwk);
  //return;
 
  const algo = {
    name: "ECDSA",
    namedCurve: "P-256", // secp256r1,
    hash: {
      name: "SHA-256"
    } 
  };
  //const hash = {name: "SHA-256"};
  //const signAlgo = {...algo, hash};

  //console.log(jwk);
  //console.log(jwk);
  //log(`Importing key ${JSON.stringify(jwk)}, algorithm ${alg}.`);

  // console.log(key);

  //debugger;

  if (jwk.kty == "OKP") {
    algo.name = "Ed25519";
  }
 
  console.log(JSON.stringify(algo, undefined, 2));
 
  try {
    const key = await crypto.subtle.importKey(
       "jwk", jwk, algo, true, ["verify"]);
    log("Key imported!");
    log(JSON.stringify(jwk, undefined, 2));
 
    const verified = await crypto.subtle.verify(
     algo, key, bufSignature, bufData);

    if (verified) {
      log("Signature with an imported key verifies!!!", "success");
    } else {
      log("Doesn't verify :(");
    }
 
    return verified;
  } catch (e) {
    log(e, "failure");
    // console.log("failed");
    return false;
  }
 
  //if (verified) {
  //  return true;
  //}

  // return verified;
}

const ab2str = (arrayBuffer) =>
   btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
 
async function sha256(message) {
  // console.log(message);
  const encoder = new TextEncoder();
  const data = encoder.encode(message);
  const hash = await window.crypto.subtle.digest("SHA-256", data);
  const hashArray = Array.from(new Uint8Array(hash));
  return base64UrlEncode(String.fromCharCode(...hashArray));
}

function urlEncode(str) {
  return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
 
function base64UrlEncode(str) {
  const base64Encoded = btoa(str);
  return urlEncode(base64Encoded);
}

function isObject(value) {
  return value && typeof value == "object" && !Array.isArray(value);
}

function hasValue(value) {
  return value !== undefined && value !== null && value !== "";
}

function requireClaim(container, claim, section) {
  const value = container && container[claim];
  requireStep(hasValue(value),
    `${section}: required ${claim} is present: ${JSON.stringify(value)}`,
    `${section}: required ${claim} is missing`);
  return value;
}

function verifierTokenInput() {
  return document.getElementsByName("evt")[0] || document.getElementsByName("token")[0];
}

function clearVerifierTokenInput() {
  const tokenInput = verifierTokenInput();
  if (tokenInput) {
    tokenInput.value = "";
  }
}

function expectedNonce() {
  const tokenInput = verifierTokenInput();
  const emailInput = document.getElementsByName("email")[0];
  return tokenInput?.getAttribute("challenge")
    || tokenInput?.getAttribute("nonce")
    || emailInput?.getAttribute("challenge")
    || emailInput?.getAttribute("nonce")
    || FALLBACK_NONCE;
}

function validateEmailSyntax(email, section) {
  return requireStep(
    typeof email == "string" && /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email),
    `${section}: email has valid address syntax`,
    `${section}: email is not syntactically valid: ${email}`);
}

function validateIat(iat, windowSeconds, section) {
  if (typeof iat != "number") {
    return fail(`${section}: iat must be a numeric timestamp`);
  }
  const now = Math.floor(Date.now() / 1000);
  const age = Math.abs(now - iat);
  return requireStep(
    age <= windowSeconds,
    `${section}: iat is within ${windowSeconds} seconds of now`,
    `${section}: iat ${iat} is ${age} seconds from now, outside ${windowSeconds} seconds`);
}

function validateJwk(jwk, section) {
  const valid = isObject(jwk)
    && jwk.kty == "OKP"
    && jwk.crv == "Ed25519"
    && typeof jwk.x == "string"
    && jwk.x.length > 0;
  requireStep(valid,
    `${section}: cnf.jwk contains an Ed25519 public key`,
    `${section}: cnf.jwk must contain kty=OKP, crv=Ed25519, and x`);
  return valid;
}

function normalizeTxtRecord(data) {
  if (Array.isArray(data)) {
    return data.join("");
  }
  return String(data).replace(/^"|"$/g, "").replace(/"\s+"/g, "");
}

function issuerMatchesClaim(issuerIdentifier, iss) {
  return iss == issuerIdentifier || iss == `https://${issuerIdentifier}`;
}

function parseJwtStrict(jwtString, section) {
  const parts = jwtString.split(".");
  if (parts.length != 3 || parts.some((part) => part.length == 0)) {
    throw new Error(`${section}: JWT must contain header, payload, and signature components`);
  }
  const [header, payload, signature] = parts;
  const headerJson = base64decode(header);
  const payloadJson = base64decode(payload);
  pass(`${section}: parsed JWT into header, payload, and signature components`);
  log(`${section}: Header: ${JSON.stringify(headerJson)}`);
  log(`${section}: Payload: ${JSON.stringify(payloadJson)}`);
  return {header, payload, signature, headerJson, payloadJson, jwt: jwtString};
}

function parseEvtKb(token) {
  const parts = token.split("~");
  if (parts.length < 2 || !parts[0] || !parts[parts.length - 1]) {
    throw new Error("6.5.1: EVT+KB must be two JWTs separated by a tilde");
  }
  requireStep(parts.length == 2,
    "6.5.1: parsed EVT+KB by separating the EVT and KB-JWT at the tilde",
    `6.5.1: EVT+KB had ${parts.length - 1} tilde separators; EVP does not use selective disclosures`);
  return {
    evtJwt: parts[0],
    evtWithTrailingTilde: `${parts[0]}~`,
    kbJwt: parts[parts.length - 1],
    disclosures: parts.slice(1, -1),
  };
}

async function discoverIssuer(email, expectedIss) {
  validateEmailSyntax(email, "3.1");
  const [, domain] = email.split("@");
  const dns = `_email-verification.${domain}`;
  log(`3.1: fetching DNS TXT records for ${dns}`);

  const response = await fetch(`https://dns.google/resolve?name=${dns}&type=TXT`);
  if (!response.ok) {
    throw new Error(`3.1: failed to fetch DNS records: ${response.status} ${response.statusText}`);
  }

  const json = await response.json();
  const answers = (json.Answer || []).filter(({type}) => type == 16);
  requireStep(answers.length == 1,
    `3.1: found exactly one TXT record for ${dns}`,
    `3.1: expected exactly one TXT record for ${dns}, got ${answers.length}`);

  if (answers.length == 0) {
    throw new Error(`3.1: no TXT records found for ${dns}`);
  }

  const data = normalizeTxtRecord(answers[0].data);
  log(`3.1: TXT data: ${JSON.stringify(data)}`);
  requireStep(data.startsWith("iss="),
    "3.1: TXT record starts with iss=",
    `3.1: TXT record must start with iss=, got ${data}`);

  const issuerIdentifier = data.substring("iss=".length);
  requireStep(hasValue(issuerIdentifier),
    `3.1: extracted issuer identifier ${issuerIdentifier}`,
    "3.1: issuer identifier is empty");

  requireStep(issuerMatchesClaim(issuerIdentifier, expectedIss),
    `5.3.4: EVT iss claim matches DNS issuer identifier ${issuerIdentifier}`,
    `5.3.4: EVT iss ${expectedIss} does not match DNS issuer identifier ${issuerIdentifier}`);

  const wellKnownURL = `https://${issuerIdentifier}/.well-known/email-verification`;
  log(`3.2: fetching issuer metadata from ${wellKnownURL}`);
  const metadata = await fetchJsonWithCorsBypass(wellKnownURL);
  pass("3.2: fetched issuer metadata JSON");
  log(`3.2: issuer metadata: ${JSON.stringify(metadata)}`);

  requireStep(hasValue(metadata.issuance_endpoint),
    "3.2: issuer metadata includes issuance_endpoint",
    "3.2: issuer metadata is missing issuance_endpoint");
  requireStep(hasValue(metadata.jwks_uri),
    "3.2: issuer metadata includes jwks_uri",
    "3.2: issuer metadata is missing jwks_uri");
  if (metadata.signing_alg_values_supported) {
    requireStep(Array.isArray(metadata.signing_alg_values_supported),
      "3.2: signing_alg_values_supported is a JSON array",
      "3.2: signing_alg_values_supported must be a JSON array when present");
    requireStep(!metadata.signing_alg_values_supported.includes("none"),
      "3.2: signing_alg_values_supported does not include none",
      "3.2: signing_alg_values_supported MUST NOT include none");
  } else {
    pass("3.2: signing_alg_values_supported omitted, defaulting to EdDSA");
  }

  return {issuerIdentifier, metadata};
}

async function verifyKbClaims(kb, evtWithTrailingTilde) {
  const {alg, typ} = kb.headerJson;
  const {aud, nonce, iat, sd_hash} = kb.payloadJson;

  requireClaim(kb.headerJson, "alg", "6.1.1");
  requireStep(alg != "none",
    "6.1.1: KB-JWT alg is not none",
    "6.1.1: KB-JWT alg MUST NOT be none");
  requireClaim(kb.headerJson, "typ", "6.1.1");
  requireStep(typ == "kb+jwt",
    "6.1.1: KB-JWT typ is kb+jwt",
    `6.1.1: KB-JWT typ must be kb+jwt, got ${typ}`);

  requireClaim(kb.payloadJson, "aud", "6.1.2");
  requireClaim(kb.payloadJson, "nonce", "6.1.2");
  requireClaim(kb.payloadJson, "iat", "6.1.2");
  requireClaim(kb.payloadJson, "sd_hash", "6.1.2");

  requireStep(aud == window.location.origin,
    `6.5.4: KB-JWT aud matches RP origin ${window.location.origin}`,
    `6.5.4: KB-JWT aud ${aud} does not match RP origin ${window.location.origin}`);
  requireStep(nonce == expectedNonce(),
    "6.5.5: KB-JWT nonce matches the RP session nonce",
    `6.5.5: KB-JWT nonce ${nonce} does not match expected ${expectedNonce()}`);
  validateIat(iat, KB_IAT_WINDOW_SECONDS, "6.5.6");

  const expectedSdHash = await sha256(evtWithTrailingTilde);
  requireStep(sd_hash == expectedSdHash,
    "6.5.7: KB-JWT sd_hash matches the SHA-256 hash of the EVT including trailing tilde",
    `6.5.7: KB-JWT sd_hash ${sd_hash} does not match computed ${expectedSdHash}`);
}

async function verifyEvt(evt) {
  const {alg, kid, typ} = evt.headerJson;
  const {iss, iat, cnf, email, email_verified} = evt.payloadJson;

  requireClaim(evt.headerJson, "alg", "5.1.1");
  requireStep(alg != "none",
    "5.1.1: EVT alg is not none",
    "5.1.1: EVT alg MUST NOT be none");
  if (hasValue(kid)) {
    pass(`5.1.1: EVT kid is present: ${JSON.stringify(kid)}`);
  } else {
    warn("5.1.1: EVT kid is missing; trying all issuer keys as a compatibility fallback");
  }
  requireClaim(evt.headerJson, "typ", "5.1.1");
  if (typ == "evt+jwt") {
    pass("5.1.1: EVT typ is evt+jwt");
  } else {
    warn(`5.1.1: EVT typ must be evt+jwt, got ${typ}`);
  }

  requireClaim(evt.payloadJson, "iss", "5.1.2");
  requireClaim(evt.payloadJson, "iat", "5.1.2");
  requireClaim(evt.payloadJson, "cnf", "5.1.2");
  requireClaim(evt.payloadJson, "email", "5.1.2");
  requireClaim(evt.payloadJson, "email_verified", "5.1.2");
  validateJwk(cnf?.jwk, "5.1.2");
  validateEmailSyntax(email, "5.1.2");
  validateIat(iat, EVT_IAT_WINDOW_SECONDS, "5.3.7");
  requireStep(email_verified === true,
    "5.3.8: EVT email_verified is true",
    `5.3.8: EVT email_verified must be true, got ${email_verified}`);

  const {metadata} = await discoverIssuer(email, iss);
  const keys = await jwks(metadata.jwks_uri);
  const keyList = Array.isArray(keys) ? keys : [];
  requireStep(keyList.length > 0,
    `5.3.5: fetched ${keyList.length} issuer public key(s) from jwks_uri`,
    "5.3.5: issuer JWKS did not contain any keys");

  const candidateKeys = kid
    ? keyList.filter(({kid: keyID}) => keyID == kid)
    : keyList;

  if (kid) {
    requireStep(candidateKeys.length > 0,
      `5.3.6: found issuer public key identified by kid=${kid}`,
      `5.3.6: no issuer public key matched kid=${kid}`);
  } else {
    warn("5.3.6: no EVT kid was provided, so checking all issuer public keys");
  }

  let matchingKey = false;
  log(`5.3.6: checking the EVT signature with ${candidateKeys.length} candidate key(s)`);
  for (let i = 0; i < candidateKeys.length; i++) {
    const signingJwk = candidateKeys[i];
    const keyLabel = signingJwk.kid ? `kid=${signingJwk.kid}` : `key #${i + 1}`;
    log(`5.3.6: trying issuer signing ${keyLabel}`);
    const verifies = await verify({header: evt.header, payload: evt.payload, signature: evt.signature}, signingJwk);
    if (verifies) {
      matchingKey = signingJwk;
      pass(`5.3.6: EVT signature verified with issuer signing ${keyLabel}`);
      break;
    }
  }

  requireStep(!!matchingKey,
    "5.3.6: EVT signature verified with an issuer public key",
    `5.3.6: no issuer public key verified the EVT signature after trying ${candidateKeys.length} candidate key(s)`);

  return {cnf, matchingKey, payload: evt.payloadJson};
}

async function verifyKbSignature(kb, cnf) {
  validateJwk(cnf?.jwk, "6.5.8");
  return verify({header: kb.header, payload: kb.payload, signature: kb.signature}, cnf.jwk);
}

async function verifyKb({header, payload, signature}) {
  const kb = {header, payload, signature, headerJson: base64decode(header), payloadJson: base64decode(payload)};
  await verifyKbClaims(kb, "");
  return kb;
}

async function verifySdJwt({header, payload, signature}) {
  return verifyEvt({header, payload, signature, headerJson: base64decode(header), payloadJson: base64decode(payload)});
}
  
async function check(token) {
  verificationFailureCount = 0;
  log("We got a verification token!");
  log(token);    

  let parsed;
  let evt;
  let kb;
  let evtResult;
  let kbSignatureVerified = false;
  try {
    parsed = parseEvtKb(token);
    evt = parseJwtStrict(parsed.evtJwt, "5.3.1");
    kb = parseJwtStrict(parsed.kbJwt, "6.5.2");

    let failuresBeforeStep = verificationFailureCount;
    await verifyKbClaims(kb, parsed.evtWithTrailingTilde);
    requireStep(verificationFailureCount == failuresBeforeStep,
      "2.7.1: verified KB-JWT per KB-JWT Verification",
      "2.7.1: KB-JWT verification had one or more errors");

    failuresBeforeStep = verificationFailureCount;
    evtResult = await verifyEvt(evt);
    requireStep(verificationFailureCount == failuresBeforeStep,
      "2.7.2: verified EVT per EVT Verification",
      "2.7.2: EVT verification had one or more errors");

    failuresBeforeStep = verificationFailureCount;
    kbSignatureVerified = await verifyKbSignature(kb, evtResult.cnf);
    requireStep(kbSignatureVerified,
      "6.5.8: KB-JWT signature verified with the public key from EVT cnf.jwk",
      "6.5.8: KB-JWT signature did not verify with the public key from EVT cnf.jwk");
    requireStep(verificationFailureCount == failuresBeforeStep,
      "2.7.3: verified KB-JWT signature using public key from EVT cnf.jwk",
      "2.7.3: failed to verify KB-JWT signature using public key from EVT cnf.jwk");

    if (verificationFailureCount == 0) {
      pass(`2.7: verified control of ${evtResult.payload.email}`);
    } else {
      fail(`2.7: token verification completed with ${verificationFailureCount} error(s)`);
    }
  } catch (e) {
    fail(e.stack || e);
  }

  return {
    success: verificationFailureCount == 0,
    email: evtResult?.payload?.email,
    matchingKey: evtResult?.matchingKey,
    cnf: evtResult?.cnf,
    kbSignatureVerified,
  };
};

function hydrateFormFromQuery(params) {
  const email = params.get("email");
  const evt = params.get("evt") || params.get("token");
  const emailInput = document.getElementsByName("email")[0];
  const tokenInput = verifierTokenInput();
  if (email !== null && emailInput) {
    emailInput.value = email;
  }
  if (evt !== null && tokenInput) {
    tokenInput.value = evt;
  }
}

function cookieAttributes(maxAgeSeconds) {
  const secure = location.protocol == "https:" ? "; Secure" : "";
  return `Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax${secure}`;
}

function setVerificationTokenCookie(token) {
  document.cookie = `${VERIFICATION_TOKEN_COOKIE}=${encodeURIComponent(token)}; ${cookieAttributes(VERIFICATION_TOKEN_COOKIE_MAX_AGE_SECONDS)}`;
}

function clearVerificationTokenCookie() {
  document.cookie = `${VERIFICATION_TOKEN_COOKIE}=; ${cookieAttributes(0)}`;
}

function getVerificationTokenCookie() {
  const prefix = `${VERIFICATION_TOKEN_COOKIE}=`;
  const cookie = document.cookie.split("; ").find((entry) => entry.startsWith(prefix));
  if (!cookie) {
    return "";
  }
  try {
    return decodeURIComponent(cookie.substring(prefix.length));
  } catch (e) {
    return "";
  }
}

function cleanPageUrl() {
  return `${location.origin}${location.pathname}${location.hash}`;
}

function redirectToCleanUrl() {
  const cleanUrl = cleanPageUrl();
  if (location.href == cleanUrl) {
    return false;
  }
  location.replace(cleanUrl);
  return true;
}

function replaceQueryWithCleanUrl() {
  const cleanUrl = cleanPageUrl();
  if (location.href != cleanUrl) {
    history.replaceState(history.state, "", cleanUrl);
  }
}

function renderAuthState(state, title, body, kicker = "") {
  const hero = document.getElementById("auth-hero");
  const result = document.getElementById("auth-result");
  const form = document.getElementById("signup-form");
  const setup = document.getElementById("setup");
  const logout = document.getElementById("logout-button");
  const otp = document.getElementById("otp-fallback");
  const otpError = document.getElementById("otp-error");
  document.getElementById("auth-result-kicker").textContent = kicker;
  document.getElementById("auth-result-title").textContent = title;
  document.getElementById("auth-result-body").textContent = body;
  hero.dataset.state = state;
  result.hidden = false;
  form.hidden = state == "success" || state == "checking" || state == "error";
  setup.hidden = state == "checking" || state == "success" || state == "error";
  logout.hidden = state != "success";
  otp.hidden = true;
  otpError.hidden = true;
  otpError.textContent = "";
  if (state == "error") {
    clearVerifierTokenInput();
  }
}

function renderSignedOut() {
  const hero = document.getElementById("auth-hero");
  const result = document.getElementById("auth-result");
  const form = document.getElementById("signup-form");
  const setup = document.getElementById("setup");
  const logout = document.getElementById("logout-button");
  const otp = document.getElementById("otp-fallback");
  const otpError = document.getElementById("otp-error");
  hero.dataset.state = "signed-out";
  result.hidden = true;
  form.hidden = false;
  setup.hidden = false;
  logout.hidden = true;
  otp.hidden = true;
  otpError.hidden = true;
  otpError.textContent = "";
  clearVerifierTokenInput();
}

function renderChecking(email) {
  renderAuthState(
    "checking",
    "Verifying your email",
    email ? `Checking the signed token for ${email}.` : "Checking the signed verification token.",
    "One moment");
}

function renderWelcome(email) {
  renderAuthState(
    "success",
    `Welcome ${email}`,
    "Your email verification token checked out. You are now signed in to this demo.",
    "Signed in");
}

function renderVerificationError(email) {
  renderAuthState(
    "error",
    "We could not verify this email",
    email ? `The submitted token for ${email} did not pass verification. The log below has the exact failure.` : "The submitted token did not pass verification. The log below has the exact failure.",
    "Verification failed");
}

function renderOtpFallback(email) {
  renderAuthState(
    "error",
    "We did not get an EVT",
    email ? `We did not get an email verification token for ${email}, so we are falling back to email OTPs. Please check your inbox and enter the code we sent here.` : "We did not get an email verification token, so we are falling back to email OTPs. Please check your inbox and enter the code we sent here.",
    "Verification fallback");
  document.getElementById("otp-fallback").hidden = false;
}

function setupLogoutButton() {
  document.getElementById("logout-button").addEventListener("click", () => {
    clearVerificationTokenCookie();
    clearVerifierTokenInput();
    if (!redirectToCleanUrl()) {
      document.getElementById("debug").hidden = true;
      document.getElementById("log").textContent = "";
      renderSignedOut();
    }
  });
}

function setupOtpForm() {
  document.getElementById("otp-form").addEventListener("submit", (event) => {
    event.preventDefault();
    clearVerificationTokenCookie();
    clearVerifierTokenInput();
    const otpError = document.getElementById("otp-error");
    otpError.textContent = "That code did not verify. This demo does not send OTPs, so every code is rejected.";
    otpError.hidden = false;
    document.getElementById("debug").hidden = true;
    document.getElementById("log").textContent = "";
  });
}

async function verifyTokenForPage(token, email = "", options = {}) {
  const {saveTokenOnSuccess = false, cleanUrlOnSuccess = false, cleanUrlOnError = false} = options;
  document.getElementById("debug").hidden = true;
  document.getElementById("log").textContent = "";
  renderChecking(email);
  log("This is a real SD-JWT+KB verifier. ");
  const result = await check(token);
  document.getElementById("debug").hidden = false;
  if (result.success) {
    if (saveTokenOnSuccess) {
      setVerificationTokenCookie(token);
    }
    if (cleanUrlOnSuccess) {
      replaceQueryWithCleanUrl();
    }
    renderWelcome(result.email || email || "there");
  } else {
    clearVerificationTokenCookie();
    if (cleanUrlOnError) {
      replaceQueryWithCleanUrl();
    }
    renderVerificationError(result.email || email);
  }
  return result;
}

async function checkSubmittedEvt() {
  const params = new URLSearchParams(location.search);
  const submitted = params.has("email") || params.has("evt") || params.has("token");
  const submittedToken = params.get("evt") || params.get("token");
  const submittedEmail = params.get("email");
  const savedToken = getVerificationTokenCookie();
  hydrateFormFromQuery(params);

  if (submitted) {
    if (!hasValue(submittedToken)) {
      clearVerificationTokenCookie();
      replaceQueryWithCleanUrl();
      clearVerifierTokenInput();
      document.getElementById("log").textContent = "";
      document.getElementById("debug").hidden = true;
      verificationFailureCount = 0;
      renderOtpFallback(submittedEmail);
      return;
    }
    await verifyTokenForPage(submittedToken, submittedEmail, {
      saveTokenOnSuccess: true,
      cleanUrlOnSuccess: true,
      cleanUrlOnError: true,
    });
    return;
  }

  if (hasValue(savedToken)) {
    await verifyTokenForPage(savedToken);
    return;
  }

  renderSignedOut();
}

setupLogoutButton();
setupOtpForm();
checkSubmittedEvt();

//log("It accepts User Info VCs from Issuers, presented by the browser.");
//log("This verifier will check for the issuer's signature, the key binding signature and the selective disclosure digests.");

//if (window.EmailVerifiedEvent) {
//  log("Your browser supports EVP!!", "success"); 
//} else {
//  log("Your browser does not support EVP!", "failure");
//  log("1) Make sure you have Chrome canaries installed and", "failure");
//  log("2) Make sure you have enabled the 'Email Verifiation Protocol' flag in chrome://flags", "failure");
//}

//check(token);
      
// log("");


</script>

        ]]></description>
        <pubDate>Thu Oct 24 2024 17:00:00 GMT-0700 (Pacific Daylight Time)</pubDate>
        <link>https://code.sgo.to/2024/10/25/verified-email-autocomplete.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2024/10/25/verified-email-autocomplete.html</guid>
      </item>

      <item>
        <title>Zero Knowledge Proofs</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <style>
code, input {
  color: rgb(36, 41, 46);
  border-radius: 6px;
  font-size: 85%;
  margin: 0;
  padding: .2em .4em;
  font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
  display: inline-block;
  vertical-align: text-bottom;
  max-height: 1.4em;
  /**
  background-color: rgba(27, 31, 35, 0.05);
  **/
  background-color: #a8d1ff;
}

.alice, .bob {
  /** max-width: 6em; **/
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.alice {
  background-color: rgba(255, 0, 0, 0.2);
  background-color: #fff2a8;
}

.bob {
  background-color: #4CAF5080;
}

input {
  width: 2.5em;
  text-align: right;
  color: black;
  border: 1px solid rgba(27, 31, 35, 0.05);
  outline: none;
  font-weight: bold;
}

input[disabled] {
}
</style>
<p>This is an interactive explanation of the protocol described <a href="https://www.youtube.com/watch?v=cI5lkif-V1c">here</a>.</p>
<p><code class="alice">Alice</code> wants to prove that it knows <code class="alice">x</code> = <input id="x" class="alice" value="4" type="number" min="1" max="99" onchange="main()"> such that <code>g ^ x mod p = y</code> without revealing to <code class="bob">Bob</code> the value of <code class="alice">x</code>, having agreed ahead of time on a large prime number <code>p</code> = <input id="p" value="5" disabled onchange="main()">, a primitive root <code>g</code> = <input id="g" value="3" disabled onchange="main()"> and an integer  <code>y</code> = <code class="y"></code>.</p>
<p>The way they accomplish that is by:</p>
<ol>
<li><code class="alice">Alice</code> generates a random number <code class="alice">r</code> = <input id="r" class="alice" value="2" type="number" min="1" max="99" onchange="main()"> and commits to it by computing <code class="alice">C</code> = <code class="alice">g ^ r mod p</code> = <code class="g"></code> ^ <code class="alice r"></code> mod <code class="p"></code> = <code class="bob C"></code> = <code class="bob">C</code> and sending it to <code class="bob">Bob</code>.</li>
<li><code class="bob">Bob</code> asks for <code>w</code> = <code>(x + r) mod (p - 1)</code> with the same <code class="alice">r</code> that was committed to <code class="bob">C</code></li>
<li><code class="alice">Alice</code> computes <code class="alice">w</code> = <code class="alice">(x + r) mod (p - 1)</code> = (<code class="alice x"></code> + <code class="alice r"></code>) mod (<code class="p"></code> - 1)= <code class="bob w"></code> = <code class="bob">w</code> and sends it to <code class="bob">Bob</code>.</li>
<li><code class="bob">Bob</code> knows that:
<ol>
<li><code>a ^ x * a ^ y = a ^ (x + y)</code> (arithmetic),</li>
<li><code>a ^ x mod p = a ^ (x mod (p - 1)) mod p</code> (fermat's little theorem)</li>
<li><code>y = g ^ x mod p</code> (definition)</li>
</ol>
</li>
<li>So <code>y * C mod p</code> = <code>g ^ x * g ^ r mod p</code> = <code>g ^ (x + r) mod p</code> = <code>g ^ ((x + r) mod (p - 1))</code> = <code>g ^ w mod p</code>.</li>
<li><code class="bob">Bob</code> computes
<ol>
<li><code>y * C mod p</code> = <code class="y"></code> * <code class="bob C"></code> mod <code class="p"></code> = <code class="bob r1"></code> and</li>
<li><code>g ^ w mod p</code> = <code class="g"></code> ^ <code class="bob w"></code> mod <code class="p"></code> = <code class="bob r2"></code></li>
<li>And if these numbers match, <code class="bob">Bob</code> knows that <code class="alice">Alice</code> knows <code class="alice">x</code> unless <code class="alice">Alice</code> wasn't cheating.</li>
</ol>
</li>
<li><code class="bob">Bob</code> is suspicious that if Alice picks a specific <code class="alice">r</code> = <code class="alice">z</code> = <input id="z" class="alice" value="2" type="number" min="1" max="99" onchange="main()"> and responded with
<ol>
<li><code class="alice">C</code> = <code class="alice">g ^ (z / y) mod p</code> = <code class="g"></code> ^ (<code class="alice z"></code> / <code class="y"></code>) mod <code class="p"></code> = <code class="alice C2"></code> = <code class="bob">C</code> and</li>
<li><code class="alice">w</code> = <code class="alice">z</code> = <code class="alice z"></code> = <code class="bob">w</code>, then</li>
<li>Then <code class="bob">Bob</code> would compute <code class="bob r3"></code> = <code>y * C mod p</code> = <code>y * g ^ (z / y) mod p</code> = <code>g ^ z mod p</code> = <code class="bob r4"></code> without <code class="alice">Alice</code> knowing what <code class="alice">x</code> is.</li>
</ol>
</li>
<li><code class="bob">Bob</code> also knows that:
<ol>
<li>To prove that <code class="alice">Alice</code> chose a random <code class="alice">r</code>, Bob would have to ask for it, so that it can check if <code>g ^ r mod p</code> = <code class="bob">C</code> matches what he got.</li>
<li>But, if <code class="bob">Bob</code> asks for both <code class="bob">(x + r) mod (p - 1)</code> and <code class="bob">r</code>, then that reveals to <code class="bob">Bob</code> what <code class="bob">x</code> is.</li>
</ol>
</li>
<li>So, <code class="bob">Bob</code> asks <code class="alice">Alice</code> repeatedly until it is confident that <code class="alice">Alice</code> isn't cheating
<ol>
<li>Commit and send me a <code class="bob">C</code> and</li>
<li>I'll ask you one of the following requests after you commit to a <code class="bob">C</code>:
<ol>
<li>Send me <code class="alice">r</code> (without revealing me <code class="alice">x</code>) so that I can check if you didn't cheat at constructing <code class="alice">C</code> = <code class="alice">g ^ (z / y) mod p</code> but rather used <code>g ^ r mod p</code> = <code class="bob">C</code> that I got.</li>
<li>Send me <code class="alice">w</code> (without revealing me <code class="alice">r</code>), so that I can check if <code>y * C mod p</code> = <code>g ^ w mod p</code> with the <code class="bob">C</code> that I got.</li>
</ol>
</li>
</ol>
</li>
<li>Every time <code class="bob">Bob</code> repeats this process, he gets exponentially confident that <code class="alice">Alice</code> (a) isn't cheating and (b) knows the value of <code class="alice">x</code>.</li>
</ol>
<script>

const read = (id) => document.getElementById(id).value;
const write = (id, value) => Array.from(document.getElementsByClassName(id)).forEach((x) => {
  x.innerText = value;
});

function main() {
  // public: p is a prime
  const p = BigInt(read("p"));
  // public: g is a primitive root modulo p
  const g = BigInt(read("g"));
  // public: y is an integer
  // const y = BigInt(read("y"));
  const x = BigInt(read("x"));

  // Update other references
  write("g", g);
  write("p", p);
  // write("y", y);
  write("x", x);

   // public: r is a random number selected by Alice.
  const y = g ** x % p;
  write("y", y);
 
   // public: r is a random number selected by Alice.
  const r = BigInt(read("r"));
  write("r", r);
   
  // Alice computes C = g ^ r mod p and sends to Bob.
  const C = g ** r % p;
  write("C", C);

  const w = (x + r) % (p - BigInt(1));
  write("w", w);

  const r1 = y * C % p;
  write("r1", r1);

  const r2 = g ** w % p;
  write("r2", r2); 

  const z = BigInt(read("z"));
  write("z", z); 

  const C2 = g ** (z / y) % p;
  write("C2", C2); 

  const r3 = y * C2 % p;
  write("r3", r3);

  const w2 = z;
 
  const r4 = g ** w2 % p;
  write("r4", r4); 
}

main();

</script>

        ]]></description>
        <pubDate>Sun Oct 06 2024 17:00:00 GMT-0700 (Pacific Daylight Time)</pubDate>
        <link>https://code.sgo.to/2024/10/10/zero-knowledge-proof.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2024/10/10/zero-knowledge-proof.html</guid>
      </item>

      <item>
        <title>Identity Is Who You Are</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <blockquote>
<p>This is a compilation of a series of discussions I had with Dirk Balfanz. It is also based on things I learned from Dick Hardt.</p>
</blockquote>
<blockquote>
<p>TL;DR; Identity Verification is <strong>not</strong> Authentication. Passkeys are great. Use the former for Sign-Up and the later for Sign-In.</p>
</blockquote>
<p>For the last couple of years, I get asked a variation of &quot;Sam, what's your opinion on passkeys?&quot; in relationship to OpenID/SAML and <a href="https://fedidcg.github.io/FedCM">FedCM</a>.</p>
<p>My answer has been consistently &quot;I think it is a wonderful authentication mechanism, but it is not an identity mechanism&quot;.</p>
<p>You see, I subscribe to <a href="https://twitter.com/DickHardt">Dick Hardt</a>'s definition of identity: <a href="https://www.dailymotion.com/video/x5i9c">identity is who you are</a>.</p>
<p>Who am I?</p>
<p>I am ____:</p>
<ul>
<li>Sam</li>
<li>A husband, a parent and an engineer -- in that order</li>
<li>A 40yo</li>
<li>A Brazilian</li>
<li>An engineer at Google</li>
<li>etc</li>
</ul>
<p>Part of <strong>who I am</strong> is <strong>self-asserted</strong> (things I say about myself), part of who I am is <strong>issued</strong> (things that others say about me).</p>
<p>For example, Brazil asserts that I'm brazilian, Google asserts that I'm their employee and my City of birth that I'm 40yo.</p>
<p>The part that of who I am that is <strong>issued</strong> is also <strong>verifiable</strong>.</p>
<p>The great thing about <strong>who I am</strong> is that it is fairly <strong>durable</strong>: it doesn't change very often.</p>
<p>Because my <strong>issued</strong> identity is both <strong>durable</strong> and <strong>verifiable</strong>, it means that it works really well for me to create (and recover) accounts online, to <strong>sign-up</strong>.</p>
<p>My <strong>issued</strong> identity, however, is not very easy to carry around and present.</p>
<p>So, it is often useful to <strong>exchange</strong> my identity into something that is more <strong>portable</strong>, so that I don't have to carry my issued identity all of the time with me.</p>
<p>Cookies are the quintessential example of something that is very <strong>portable</strong> and <strong>ephemeral</strong>: they get carried on every HTTP request without any extra user gesture, and they expire at some point. Much easier than presenting my <strong>issued</strong> credentials everytime.</p>
<p>I think there is a similar relationship with passkeys: they are great (dare I say, better) for <strong>signing-in</strong>, but there is no amount of passkeys that you can throw at the problem of verifying who you are, your identity.</p>
<p>For example:</p>
<ul>
<li>Because I am <strong>a stanford student</strong> I can access a paper on nature.com</li>
<li>Because I am <strong>a google employee</strong> I can access corporate directories on figma.com</li>
<li>Because I am <strong>a twitter blue subscriber</strong> I can read the nytimes.com</li>
</ul>
<p>Obviously, your identity is not always necessary to use websites, so there are many occasions where you can and want to <strong>sign-up</strong> with passkeys alone and operate anonymously on the web.</p>
<p>So, to sum up, I believe that WebAuthn and FedCM (and Cookies, obviously) complement themselves very nicely:</p>
<ul>
<li>Use federation for <strong>signin-up</strong> (and account recovery) <strong>rarely</strong> and</li>
<li>Use passkeys for <strong>signing-in</strong> (and Cookies even more frequently) <strong>frequently</strong>.</li>
</ul>
<p>It is the tension between <strong>durability</strong> with <strong>portability</strong> and the exchange between affordances at different ends of the spectrum.</p>

        ]]></description>
        <pubDate>Sat Sep 02 2023 17:00:00 GMT-0700 (Pacific Daylight Time)</pubDate>
        <link>https://code.sgo.to/2023/09/03/identity.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2023/09/03/identity.html</guid>
      </item>

      <item>
        <title>S and K</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <script src="/static/typograms@ecfc65dec57ebd26.js"></script>
<h1>Context</h1>
<p>Moses Schönfinkel's inspiration came from the recently discovered result that all of the known operators in propositional logic (<code>~a</code>, <code>a &amp;&amp; b</code>, <code>a || b</code>, <code>a =&gt; b</code> and <code>a &lt;=&gt; b</code>) could be reduced to / derived from a single operator: <code>nand</code>.</p>
<h1>Notation</h1>
<p>Notation: a tree <code>(xy)</code> can be thought as a function <code>x</code> applied to an argument <code>y</code>. When evaluated, the tree returns a value. The function, argument or values are either combinators or binary trees.</p>
<script type="text/typogram">
       +---+                  +-++-+                  +----+
       |   |                  |f(x)|                  |f(x)|
       ++-++                  ++--++                  +----+
       /   \      --->        /    \        ---> 
   +--++   ++--+          +--++    ++--+
   | f |   | x |          | f |    | x |
   +---+   +---+          +---+    +---+
</script>
<h1>S and K</h1>
<h2>K</h2>
<p>The <code>k</code> combinator is: <code>k[x][y] = x</code></p>
<script type="text/typogram">
            +---+                   +-------------+                +---+
            |   |                   |"K(x)(y) = x"|                | x |
            ++-++                   +----+---+----+                +---+
            /   \                       /     \
       +--++    ++--+              +---++     ++--+
       |   |    | y |   --->       |K(x)|     | y |        --->
       ++-++    +---+              ++--++     +---+
       /   \                       /    \
   +--++   ++--+               +--++    ++--+
   | K |   | x |               | K |    | x |
   +---+   +---+               +---+    +---+
</script>
<h2>S</h2>
<p>The <code>s</code> and <code>k</code> combinators: <code>s[x][y][z] = x[z][y[z]]</code> and</p>
<script type="text/typogram">
                +---+             +-------------------------+              +---------------+
                |   |             |"S(x)(y)(z) = x(z)(y(z))"|              |   x(z)(y(z))  |
                ++-++             +---------+-----+---------+              +---+-------+---+
                /   \                      /       \                          /         \
           +--++    ++--+             +---+---+    ++--+                 +-+-++         ++---+
           |   |    | z |             |S(x)(y)|    | z |                 |x(z)|         |y(z)|  
           ++-++    +---+             +-+---+-+    +---+                 ++--++         ++--++   
           /   \           --->        /     \               --->        /    \         /    \     
      +--++    ++--+              +---++     ++--+                   +--++    ++--+ +--++    ++--+     
      |   |    | y |              |S(x)|     | y |                   | x |    | z | | y |    | z |    
      ++-++    +---+              ++--++     +---+                   +---+    +---+ +---+    +---+    
      /   \                       /    \                                
  +--++   ++--+               +--++    ++--+                       
  | S |   | x |               | S |    | x |                        
  +---+   +---+               +---+    +---+                        
</script>
<h1>Example</h1>
<p>For example, let's say we start from <code>s[k][s][k]</code>. This evaluates to <code>k[k][s[k]]</code> by applying the <code>s</code> rule with <code>x = k</code>, <code>y = s</code> and <code>z = k</code>.  This then evalutes to <code>k</code> as we apply the <code>k</code> rule with <code>x = k</code> and <code>y = s[k]</code>. Since there is no more rule to apply, it halts.</p>
<script type="text/typogram">
              +---+                +-------------------------+                 +----------+
              |   |                |"S(x)(y)(z) = K(S)(S(K))"|                 |K(S)(S(K))|
              ++-++                +---------+-----+---------+                 ++--------++
              /   \                         /       \                          /          \
          +--++   ++--+                +---+---+    ++--+                 +-+-++          ++---+
          |   |   | K |                |S(K)(S)|    | K |                 |K(S)|          |S(K)|  
          ++-++   +---+                +-+---+-+    +---+                 ++--++          ++--++   
          /    \           --->         /     \               --->        /    \          /    \    
      +--++    ++--+               +---++     ++--+                   +--++    ++--+  +--++    ++--+     
      |   |    | S |               |S(K)|     | S |                   | K |    | S |  | S |    | K |    
      ++-++    +---+               ++--++     +---+                   +---+    +---+  +---+    +---+    
      /   \                        /    \                                
  +--++   ++--+                +--++    ++--+                       
  | S |   | K |                | S |    | K |                        
  +---+   +---+                +---+    +---+                        


      +----------+                  +---+
      |K(S)(S(K))|        --->      | K |
      +-+------+-+                  +---+
       /        \
   +--++        ++---+        
   | K |        |S(K)|
   ++-++        ++--++ 
                /    \
            +--++    ++--+
            | S |    | K |
            +---+    +---+    
</script>
<h1>Boolean logic</h1>
<p>We pick very specific definitions of <code>true</code> and <code>false</code>, such that we can construct an
if-then-else structure: when <code>true</code> follow the <code>then</code>, when <code>false</code> follow the <code>else</code>.</p>
<p>And from that, we define other boolean combinators to perform the task that we want
them to perform.</p>
<h2>True</h2>
<p>We define <code>T[x][y]</code> = <code>T = K</code>, which because <code>K[x][y]</code> = <code>x</code>, and,</p>
<script type="text/typogram">
          +---+                    +-------------+                +---+
          |   |                    |"T(x)(y) = x"|                | x |
          ++-++                    +----+---+----+                +---+
          /   \                        /     \
      +--++   ++--+               +---++     ++--+
      |   |   | y |   --->        |T(x)|     | y |        --->
      ++-++   +---+               ++--++     +---+
      /   \                       /    \
  +--++   ++--+               +--++    ++--+
  | T |   | x |               | T |    | x |
  +---+   +---+               +---+    +---+
</script>
<h2>False</h2>
<p>We define <code>F[x][y]</code> = <code>F[x][y]</code> = <code>S[K][x][y]</code>, which is <code>K[y][x[y]]</code> = <code>y</code>. With that, then,</p>
<script type="text/typogram">
          +---+                  +-------------+                +---+
          |   |                  |"F(x)(y) = y"|                | y |
          ++-++                  +----+---+----+                +---+
          /   \                      /     \
      +--++   ++--+             +---++     ++--+
      |   |   | y |   --->      |F(x)|     | y |        --->
      ++-++   +---+             ++--++     +---+
      /   \                     /    \
  +--++   ++--+             +--++    ++--+
  | F |   | x |             | F |    | x |
  +---+   +---+             +---+    +---+
</script>
<h2>NOT</h2>
<p>We define <code>NOT[x]</code> as <code>S[S[I][K[F]]][K[T]][x]</code>, which leads to <code>S[I][K[F]][x][K[T][x]]</code> = <code>I[x][K[F][x]][T]</code> = <code>x[F][T]</code>.</p>
<script type="text/typogram">
      +---+                       +---+-+---+       
      |   |                       | x(F)(T) |       
      ++-++                       +-+-----+-+       
      /   \         --->           /       \        
  +--++   ++--+               +---++        ++--+
  | ~ |   | x |               |x(f)|        | T |
  +---+   +---+               ++--++        +---+
                              /    \
                          +--++    ++--+
                          | x |    | F |    
                          +---+    +---+    
</script>
<p>For example, <code>NOT[T]</code> = <code>T[F][T]</code> = <code>F</code>, <code>NOT[F]</code> = <code>F[F][T]</code> = <code>T</code>:</p>
<script type="text/typogram">
      +-+-+                       +---+-+---+                +---+
      |   |                       | T(F)(T) |                | F |
      ++-++                       +-+-----+-+                +---+
      /   \         --->           /       \         --->
  +--++   ++--+               +---++       ++--+
  | ~ |   | T |               |T(f)|       | T |
  +---+   +---+               ++--++       +---+
                              /    \
                          +--++    ++--+
                          | T |    | F |    
                          +---+    +---+    
</script>
<h2>OR</h2>
<p><code>OR[x][y]</code> = <code>S[I][K[T]][x][y]</code> = <code>I[x][K[T][x]][y]</code> = <code>x[T][y]</code></p>
<script type="text/typogram">
           +---+              +--------------------+                +---+
           |   |              |"OR(x)(y) = x[T][y]"|                |   |
           ++-++              +-------+----+-------+                ++-++
           /   \                     /      \                       /   \
       +--++   ++--+           +----++      ++--+               +--++   ++--+
       |   |   | y |   --->    |OR(x)|      | y |        --->   |   |   | y |
       ++-++   +---+           ++---++      +---+               ++-++   +---+
       /   \                   /     \                          /   \
  +---++   ++--+          +---++     ++--+                  +--++   ++--+
  | OR |   | x |          | OR |     | x |                  | x |   | T |
  +----+   +---+          +----+     +---+                  +---+   +---+
</script>
<p>For example: <code>OR[T][T]</code> = <code>T[T][T]</code> = <code>T</code>, <code>OR[T][F]</code> = <code>T[T][F]</code> = <code>T</code>, <code>OR[F][T]</code> = <code>F[T][T]</code> = <code>T</code> and <code>OR[F][F]</code> = <code>F[F][F]</code> = <code>F</code>.</p>
<script type="text/typogram">
          +---+                 +---+             +---+
          |   |                 |   |             | T |
          ++-++                 ++-++             +---+
          /   \                 /   \
      +--++   ++--+         +--++    ++--+
      |   |   | y |   --->  | T |    | y |  --->
      ++-++   +---+         ++-++    +---+
      /   \                               
  +--++    ++--+                            
  | T |    | T |                            
  +---+    +---+                            
</script>
<h2>AND</h2>
<p><code>AND[x][y]</code> = <code>S[S][K[K[F]]][x][y]</code> = <code>S[x][K[K[F]][x]][y]</code> = <code>x[y][K[K[F]][x][y]]</code> = <code>x[y][K[F][y]]</code> = <code>x[y][F]</code>, e.g. <code>AND[T][T]</code> = <code>T[T][F]</code> = <code>T</code>, <code>AND[T][F]</code> = <code>T[F][F]</code> = <code>F</code>, <code>AND[F][T]</code> = <code>F[T][F]</code> = <code>F</code> and <code>AND[F][F]</code> = <code>F[F][F]</code> = <code>F</code>.</p>
<h2>NAND</h2>
<p>You can combine combinators (duh) to form composite constructions, for example a <code>NAND[x][y]</code> = <code>NOT[AND[x][y]]</code> = <code>AND[x][y][F][T]</code> = <code>x[y][F][F][T]</code>.</p>
<script type="text/typogram">
            +---+                         +---+
            |   |                         |   |
            ++-++                         ++-++
            /   \                         /   \
       +--++    ++--+                 +--++   ++--+
       |   |    | y |   --->          |   |   | T |
       ++-++    +---+                 ++-++   +---+
       /   \                          /   \
  +---++   ++--+                  +--++   ++--+
  |NAND|   | x |                  |   |   | F |
  +----+   +---+                  ++-++   +---+
                                  /   \
                              +--++   ++--+
                              |   |   | F |
                              ++-++   +---+
                              /   \
                          +--++   ++--+
                          | x |   | y |
                          ++-++   +---+
                              
</script>
<p>For example, <code>NAND[T][T]</code> = <code>T[T][F][F][T]</code> = <code>T[F][T]</code> = <code>F</code>, <code>NAND[F][T]</code> = <code>F[T][F][F][T]</code> = <code>F[F][T]</code> = <code>T</code>, <code>NAND[T][F]</code> = <code>T[F][F][F][T]</code> = <code>F[F][T]</code> = <code>T</code> and <code>NAND[F][F]</code> = <code>F[F][F][F][T]</code> = <code>F[F][T]</code> = <code>T</code>.</p>
<h1>Control</h1>
<h2>Identity</h2>
<p><code>I[x]</code>: <code>S[K][x][y]</code> always evaluates to <code>y</code> in two steps (<code>S[K][x][y]</code> = <code>K[y][x[z]]</code> = <code>y</code>). <code>S[K][x][y]</code> and <code>I[y]</code> are functionally equivalent because they always yield the same result when applied to any <code>y</code>.</p>
<script type="text/typogram">
      +---+                  +---+
      |   |                  | x |
      ++-++                  +---+
      /   \         --->    
  +--++   ++--+             
  | I |   | x |             
  +---+   +---+             
</script>
<h2>Second</h2>
<p><code>N[x][y]</code> is defined as <code>N = K[I]</code> and results in <code>y</code>, since <code>N[x][y]</code> = <code>K[i][x][y]</code> = <code>I[y]</code> = <code>y</code>, which complements <code>K</code> (which selects the first parameter, rather than the second).</p>
<h1>Functions</h1>
<h2>Composition</h2>
<ul>
<li><code>B[f][g][x]</code> = <code>f[g[x]]</code> performs a composition of functions (i.e. <code>f * g (x)</code> = <code>f(g(x))</code>), and is defined as <code>B[f][g][x]</code> = <code>S[K[S]][K][f][g][x]</code> = <code>K[S][f][K[f]][g][x]</code> = <code>S[K[f]][g][x]</code> = <code>K[f][x][g[x]]</code> = <code>f[g[x]]</code>.</li>
</ul>
<h2>Currying</h2>
<ul>
<li><code>C[f][g][x]</code> = <code>f[x][g]</code>, defined as <code>C[f][g][x]</code> = <code>S[B[B][S]][K[K]][f][g][x]</code> = <code>B[B][S][f][K[K][f]][g][x]</code> = <code>B[S[f]][K[K][f]][g][x]</code> = <code>S[f][K[K][f][g]][x]</code> = <code>f[x][K[K][f][g][x]]</code> = <code>f[x][K[g][x]]</code> = <code>f[x][g]</code></li>
</ul>
<h2>W</h2>
<ul>
<li><code>W[x][f]</code> = <code>f[x]</code> takes the second parameter and applies it to the first, which is constructed as <code>W[x][f]</code> = <code>C[I][x][f]</code> = <code>I[f][x]</code> = <code>f[x]</code>.</li>
</ul>
<h2>V</h2>
<p><code>V[x][y][z]</code> = <code>z[x][y]</code> takes the third argument, applies it to the first argument and then to the second. It is is defined as <code>B[C][W][x][y][z]</code> = <code>C[W[x]][y][z]</code> = <code>W[x][z][y]</code> = <code>z[x][y]</code>.</p>
<h1>Data Structures</h1>
<h2>Pair</h2>
<p>Suppose we want to describe ordered pairs, say <code>(x, y)</code>, we can define a combinator <code>Pair[x][y]</code> such that <code>First[Pair[x][y]]</code> = <code>x</code> and <code>Second[Pair[x][y]]</code> = <code>y</code>. If we define <code>Pair[x][y][z]</code> = <code>V[x][y][z]</code> = <code>z[x][y]</code> and <code>First</code> = <code>W[T]</code> and <code>Second</code> = <code>W[F]</code>.</p>
<p>For example, <code>First[Pair[x][y]]</code> = <code>W[T][Pair[x][y]]</code> = <code>Pair[x][y][T]</code> = <code>T[x][y]</code> = <code>x</code> and <code>Second[Pair[x][y]]</code> = <code>W[F][Pair[x][y]]</code> = <code>Pair[x][y][F]</code> = <code>F[x][y]</code> = <code>y</code>.</p>
<h1>Natural Numbers</h1>
<h2>Zero</h2>
<p>We want to make the function <code>Zero[x]</code> return <code>T</code> when <code>x</code> = <code>0</code> and <code>F</code> otherwise, so we define <code>Zero[x]</code> = <code>First[x]</code>. Since we want <code>Zero[0]</code> = <code>First[0]</code> = <code>W[T][0]</code> = <code>0[T]</code>. If we define <code>0</code> = <code>I</code>, then <code>Zero[0]</code> = <code>0[T]</code> = <code>I[T]</code> = <code>T</code>. So, we define <code>0</code> = <code>I</code>.</p>
<h2>1, 2, 3, ...</h2>
<p>With the representation of <code>0</code> in place, we can define <code>1</code> as the pair <code>Pair[F][0]</code>, <code>2</code> = <code>Pair[F][1]</code>, <code>3</code> = <code>Pair[F][2]</code>, and so on.</p>
<p>We can then define <code>Next[n]</code> = <code>Pair[false][n]</code>, which can give us our natural numbers <code>0</code>, <code>Next[0]</code>, <code>Next[Next[0]]</code>, <code>Next[Next[Next[0]]]</code>, etc.</p>
<p>The <code>Previous[n]</code> = <code>Second[n]</code>, for example <code>Previous[2]</code> = <code>Second[2]</code> = <code>Second[Pair[F][1]]</code> = <code>1</code>.</p>
<h1>Recursion</h1>
<h1>Arithmetic</h1>
<h2>Apply</h2>
<p>We'd like to derive a combinator that can compute the application of a function <code>n</code> times, e.g. <code>Apply[0][f][x]</code> = <code>x</code>, <code>Apply[1][f][x]</code> = <code>f(x)</code>, <code>Apply[2][f][x]</code> = <code>f[f[x]]</code>, and so on.</p>
<h2>Add</h2>
<p>For example, adding two numbers, <code>m</code> and <code>n</code> is the same as applying the function <code>Next</code> <code>m</code> times to <code>n</code>. That is, <code>Add[m][n]</code> = <code>Apply[m][Next][n]</code> = <code>Next[Next[Next[...[Next[n]]]]]</code> (embeds <code>m</code> times).</p>
<p>We define <code>Apply[n][f][x]</code> as <code>Zero[n][x][f[Apply[Previous[n]][f][x]]]</code>, which in practice can read as:</p>
<pre class="language-js"><code class="language-js"><span class="token function">Apply</span><span class="token punctuation">(</span><span class="token parameter">n<span class="token punctuation">,</span> f<span class="token punctuation">,</span> x</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">Zero</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token constant">T</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> x<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token function">Apply</span><span class="token punctuation">(</span><span class="token function">Previous</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">,</span> f<span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<h2>Sub</h2>
<p>Much like Add, we can define Sub as <code>Sub[m][n] = Apply[m][Previous][n]</code>.</p>
<h2>Fib</h2>
<p>Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...</p>
<p><code>NextPair[p] = Pair[Second[p]][Add[First[p]][Second[p]]]</code>. For example, <code>NextPair[Pair[0][1]]</code> = <code>Pair[Second[Pair[0][1]]][Add[First[Pair[0][1]]][Second[Pair[0][1]]]] = Pair[1][Add[0][1]]</code> = <code>Pair[1][1]</code>.</p>
<h1>Iota</h1>
<ul>
<li>
<p><code>i[x]</code> = <code>x[S][K]</code></p>
</li>
<li>
<p><code>I[x]</code> = <code>i[i][x]</code> = <code>i[S][K][x]</code> = <code>S[S][K][K][x]</code> = <code>S[K][K[K]][x]</code> = <code>K[x][K[K][x]]</code> = <code>x</code>.</p>
</li>
<li>
<p><code>K[x][y]</code> = <code>i[i[I]][x][y]</code> = <code>i[i[i[i]]][x][y]</code> = <code>i[i[i[S][K]]][x][y]</code> = <code>i[i[S][S][K][K]]][x][y]</code> = <code>i[S[S][K][S][K][K]][x][y]</code> = <code>S[S][K][S][K][K][S][K][x][y]</code> = <code>S[S][K[S]][K][K][S][K][x][y]</code> = <code>S[K][K[S][K]][S][K][x][y]</code> = <code>S[K][S][S][K][x][y]</code> = <code>K[S][S[S]][K][x][y]</code> = <code>S[K][S[S][K]][x][y]</code> = <code>K[x][S[S][K][y]]</code> = <code>x</code>.</p>
</li>
<li>
<p><code>S[x][y][z]</code> = <code>i[K][x][y][z]</code> = <code>K[S][K][x][y][z]</code> = <code>S[x][y][z]</code> = <code>x[z][y[z]]</code></p>
</li>
<li>
<p><code>i[x] =&gt; x[[a][b][c] =&gt; a[c][b[c]]]][[d][e] =&gt; d]</code></p>
</li>
<li>
<p><code>i[i][x] =&gt; i[[a][b][c] =&gt; a[c][b[c]]]][[d][e] =&gt; d][x]</code> = <code>([a][b][c] =&gt; a[c][b[c]])[[a][b][c] =&gt; a[c][b[c]]]][[d][e] =&gt; d]][[d][e] =&gt; d][x]</code> = <code>([a][b][c] =&gt; a[c][b[c]])[[d][e] =&gt; d][([d][e] =&gt; d)[[d][e] =&gt; d]][x]</code> = <code>([d][e] =&gt; d)[x][([d][e] =&gt; d)[[d][e] =&gt; d][x]]</code> = <code>x</code>.</p>
</li>
</ul>
<p>Alternatively, <code>j</code> would also work:</p>
<ul>
<li><code>j[x] = x[K][S]</code></li>
<li><code>S</code> = <code>j[j][j]</code> = <code>j[K][S][j]</code> = <code>K[K][S][S][j]</code> = <code>K[S][j]</code> = <code>S</code></li>
<li><code>K</code> = <code>j[j[j][j][j][j]]</code> = <code>j[S[j][j]]</code> = <code>S[j][j][K][S]</code> = <code>j[K][j[K]][S]</code> = <code>K[K][S][j[K]][S]</code> = <code>K[j[K]][S]</code> = <code>j[K]</code> = <code>K[K][S]</code> = <code>K</code>;</li>
</ul>
<p>Another option is <code>W[x] = x[K][S][K]</code> with <code>K = W[W][W]</code> and <code>S = W[W[W]]</code>.</p>
<ul>
<li><code>K</code> = <code>W[W][W]</code> = <code>W[K][S][K][W]</code> = <code>K[K][S][K][S][K][W]</code> = <code>K[K][S][K][W]</code> = <code>K[K][W]</code> = <code>K</code></li>
<li><code>S</code> = <code>W[W[W]]</code> = <code>W[W[K][S][K]]</code> = <code>W[K[K][S][K][S][K]]</code> = <code>K[K][S][K][S][K][K][S][K]</code> = <code>K[K][S][K][K][S][K]</code> = <code>K[K][K][S][K]</code> = <code>K[S][K]</code> = <code>S</code>.</li>
</ul>
<p>If we define <code>m</code> as:</p>
<ul>
<li>
<p><code>m[x][y][z]</code> = <code>x[z][y[K[z]]]</code></p>
</li>
<li>
<p><code>K = m[m[m]][ m[m[m]][m][m]  [m][m][m]]</code> = <code>m[m[m]][ m[m][m][m[K[m]]]  [m][m][m]]</code> = <code>m[m[m]][ m[m[K[m]]][m[K[m[K[m]]]]]  [m][m][m]]</code></p>
</li>
<li>
<p><code>S = m (m (m m (m m (m m))(m (m (m m (m m)))))) m m</code></p>
</li>
</ul>
<script type="text/typogram">
          +---+                      +---+                
          |   |                      |   |                
          ++-++                      ++-++                
          /   \                      /   \
      +--++   ++--+              +--++   ++--+
      | i |   | x |   --->       |   |   | K |       
      ++-++   +---+              ++-++   +---+
                                 /   \
                             +--++   ++--+
                             | x |   | S |
                             +---+   +---+
</script>
<h1>Unused?</h1>
<h2>Self Application</h2>
<p><code>M[x]</code> (sometimes also called <code>u</code>, e.g. <a href="https://en.wikipedia.org/wiki/SKI_combinator_calculus">here</a>) is a combinator that takes an argument and applies that argument to itself: <code>M[x]</code> = <code>S[I][I][x]</code> = <code>I[x][I[x]]</code> = <code>x[x]</code></p>
<p>An interesting property of the <code>M</code> combinator is that its self-application is irreducible: <code>u[u]</code> = <code>s[i][i][s[i][i]]</code> = <code>i[s[i][i]][i[s[i][i]]]</code> = <code>s[i][i][s[i][i]]</code> = <code>u[u]</code>.</p>
<script type="text/typogram">
      +---+                       +-++-+                      +---+
      |   |                       |U(x)|                      |   |
      ++-++                       ++--++                      ++-++
      /   \         --->          /    \         --->         /   \
  +--++   ++--+               +--++    ++--+              +--++    ++--+
  | U |   | x |               | U |    | x |              | x |    | x |
  +---+   +---+               +---+    +---+              +---+    +---+
</script>
<h2>Reversal</h2>
<p><code>R[x][y]</code> = <code>y[x]</code> can be constructed as <code>R[x][y]</code> = <code>S[K[S[I]]][K][x][y]</code> = <code>K[S[I]][x][K[x]][y]</code> = <code>S[I][K[x]][y]</code> = <code>I[y][K[x][y]]</code> = <code>y[K[x][y]]</code> = <code>y[x]</code>.</p>
<script type="text/typogram">
          +---+                 +---+     
          |   |                 |   |     
          ++-++                 ++-++     
          /   \                 /   \
      +--++   ++--+         +--++   ++--+
      |   |   | y |   --->  | y |   | x |
      ++-++   +---+         ++-++   +---+
      /   \                               
  +--++    ++--+                            
  | R |    | x |                            
  +---+    +---+                            
</script>
<h1>References</h1>
<ul>
<li><a href="https://www.youtube.com/watch?v=gnrSedVucXs&amp;t=1239s">Combinatory Fibonacci</a></li>
<li>TODO: look at <a href="https://en.wikipedia.org/wiki/Iota_and_Jot">Iota and Jot</a> which can derive S and K</li>
<li><a href="https://en.wikipedia.org/wiki/SKI_combinator_calculus">SKI Calculus</a></li>
<li><a href="https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel">Moses Schönfinkel</a></li>
<li><a href="https://writings.stephenwolfram.com/2020/12/combinators-and-the-story-of-computation/">Combinators and the History of Computation</a></li>
<li><a href="https://www.youtube.com/watch?v=PG2G5xSz0NQ">Combinators: A 100-Year Celebration</a></li>
</ul>
<!--

# Axioms

Lets assume for a second that combinators are of the form `F[a][b][...][z]`, that is, they only transform "forward", rather than backwards (e.g. `a[b][... [F] ...][z]`) or embedded (e.g. `F[a[b]]`).

<script type="text/typogram">
                +---+
                |   |
                ++-++
                /   \
            +--++   ++--+
            |   |   | n |
            ++-++   +---+
            /   \
        +--++   ++--+
        |   |   |"."|
        ++-++   +---+
        /   \
    +--++   ++--+
    |   |   | b |
    ++-++   +---+
    /   \
+--++   ++--+
| F |   | a |
++-++   +---+
</script>

Let's start by assuming that the way that the combinator works is constrained by picking a single element:

<script type="text/typogram">
+---+
| x |
++-++
</script>

So, for example, you can increase the number of arguments and choose which to use, but otherwise, you can only try new combinators that way:

1. `F[x] = x`
1. `F[x][y] = x`, `F[x][y] = y`
1. `F[x][y][z] = x`, `F[x][y][z] = y`, `F[x][y][z] = z`
1. `F[x][y][...][n] = x`, `F[x][y][...][n] = y`, `...`, `F[x][y][...][n] = n`

In how many ways can we combine this combinator?

So, if we are to use it just one time, we can only construct: `F`.

If we allow it to appear 2 times, then we can have `F[F]`. 3 Times? `F[F][F]` and `F[F[F]]`.

<script type="text/typogram">
        +--++               +--++      
        |   |               |   |      
        ++-++               ++-++      
        /   \               /   \      
    +--++   ++--+       +--++   ++--+  
    |   |   | F |       | F |   |   |  
    ++-++   +---+       ++-++   ++-++  
    /   \                       /   \          
+--++   ++--+               +--++   ++--+      
| F |   | F |               | F |   | F |      
++-++   +---+               ++-++   +---+      
</script>

Four time leads to `F[F][F][F]`, `F[F[F]][F]`, `F[F][F[F]]` (two different trees result into the same encoding) and `F[F[F[F]]`:

<script type="text/typogram">
          F[F][F][F]             F[F[F]][F]
            +--++                  +--++        
            |   |                  |   |         
            ++-++                  ++-++        
            /   \                  /   \         
        +--++   ++--+          +--++   ++--+    
        |   |   | F |          |   |   | F |    
        ++-++   +---+          ++-++   +---+    
        /   \                  /   \             
    +--++   ++--+          +--++   ++--+         
    |   |   | F |          | F |   |   |          
    ++-++   +---+          ++-++   ++-++       
    /   \                          /   \                        
+--++   ++--+                  +--++   ++--+      
| F |   | F |                  | F |   | F |  
++-++   +---+                  ++-++   +---+              

        F[F][F[F]]
        +--------++            
        |         |            
        ++-------++            
        /         \            
    +--++         ++--+        
    |   |         |   |        
    ++-++         ++-++        
    /   \         /   \        
+--++   ++--+ +--++   ++--+    
| F |   | F | | F |   | F |    
++-++   +---+ ++-++   +---+    

     F[F[F][F]]                      F[F[F[F]]]

       +--++                           +--++                    
       |   |                           |   |                    
       ++-++                           ++-++                    
       /   \                           /   \                    
   +--++   ++--+                   +--++   ++--+                
   | F |   |   |                   | F |   |   |                
   ++-++   ++-++                   ++-++   ++-++                
           /   \                           /   \                
       +--++   ++--+                   +--++   ++--+            
       |   |   | F |                   | F |   |   |            
       ++-++   ++-++                   ++-++   ++-++            
       /   \                                   /   \            
   +--++   ++--+                           +--++   ++--+        
   | F |   | F |                           | F |   | F |        
   ++-++   +---+                           ++-++   +---+        
</script>

How would we know if we got to `K` and `S`? We can add an arbitrary number of variables, and, keep making substitutions, until we either (a) arrive at `K` and `S` or (b) can't make any further transformation or (c) the substitution doesn't converge after `n` steps (how many of these keep growing or shrinking rather than just keep consistently growing?).


<script type="text/typogram">
    +---+
    |   |
    ++-++
    /   \
+--++   ++-++
| x |   | y |
++-++   ++-++



         +-----------+                    +---+           +---+              
         |           |                    |   |           |   |              
         ++---------++                    ++-++           ++-++              
         /           \                    /   \           /   \              
     +--++           ++-++            +--++   ++-++   +--++   ++-++          
     |   |           |   |            |   |   | z |   |   |   | z |          
     +++++           +++++            +++++   +++++   +++++   +++++          
     /   \           /   \            /   \                   /   \          
 +--++   ++-++   +--++   ++-++    +--++   ++-++           +--++   ++-++      
 | x |   | y |   | z |   | w |    | x |   | y |           | x |   | y |      
 ++-++   ++-++   ++-++   ++-++    ++-++   ++-++           ++-++   ++-++      
</script>

-->
        ]]></description>
        <pubDate>Wed Mar 22 2023 17:00:00 GMT-0700 (Pacific Daylight Time)</pubDate>
        <link>https://code.sgo.to/2023/03/23/sk.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2023/03/23/sk.html</guid>
      </item>

      <item>
        <title>Set.mm</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <script src="/static/metamath.js"></script>
<script type="text/babel" src="/static/mm.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<p>Loading and verifying <a href="https://github.com/metamath/set.mm">set.mm</a>.</p>
<div style="background-color: #f1f1f1;">
  <div id="progress" style="background-color: #4CAF50; height:24px; color: white !important; width: 0; padding: 0.01em 0; overflow: hidden; text-align: center!important;">0%</div>
  <span id="status" style="position: relative; float: left;">Downloading and parsing</span>
</div>
<br>
<br>
<div id="metamath"></div>
<script async data-type="module" type="text/babel">
const response = await fetch("/static/set.mm");
const length = response.headers.get('Content-Length');
//console.log(length);
const source = await response.text();
//console.log("hi");
//console.log(source);

const {MM, process} = module;

// console.log(MM);
console.log("parsing");
const mm = process(source);
console.log(mm);
//console.log(mm.theorems());

const theorems = mm.theorems();

const total = theorems.length;
console.log(`Total: ${total}`);

const start = new Date();

function doit() {
  if (theorems.length == 0) {
    return false;
  }
  const [name, proof] = theorems.shift();

  const now = new Date();

  const ellapsed = now - start;

  const time = Math.round(ellapsed / 1000);

  let progress = Math.round((total - theorems.length) / total * 100);
  document.getElementById("progress").style.width = progress + "%";
  document.getElementById("progress").innerText = `${progress}%`;
  console.log(((total - theorems.length) / total));
  document.getElementById("status").innerText =
     `${time}s: Verifying ${name}`
  console.log(`${total - theorems.length} of ${total} in ${time}s: Verifying ${name}`);

  // console.log();
  try {
    proof();
  } catch (e) {
    if (e.message != "proof too long") {
      throw e;
    }
  }

  return true;
}

function pop() {
  for (let i = 0; i < 100; i++) {
    if (!doit()) {
      return;
    }
  }

  setTimeout(pop, 0);
}

pop();

//ReactDOM.render(
//  <Metamath label="2p2e4">{source}</Metamath>,
//  document.querySelector("#metamath"));
</script>

        ]]></description>
        <pubDate>Fri Nov 25 2022 16:00:00 GMT-0800 (Pacific Standard Time)</pubDate>
        <link>https://code.sgo.to/2022/11/26/set.mm.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2022/11/26/set.mm.html</guid>
      </item>

      <item>
        <title>Is 2 + 2 = 4?</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <script src="/static/metamath.js"></script>
<script type="text/babel" src="/static/mm.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<p>This is a client-side exploration of metamath's <a href="https://github.com/metamath/set.mm">set.mm</a>, which is verified <a href="/2022/11/26/set.mm.html">here</a>.</p>
<p>This downloads a 40MB file, which takes a while to load and parse. But once it is parsed, you can navigate the database below:</p>
<div id="metamath"></div>
<script async data-type="module" type="text/babel">
const response = await fetch("/static/set.mm");
const length = response.headers.get('Content-Length');
const source = await response.text();

ReactDOM.render(
  <Metamath label="2p2e4">{source}</Metamath>,
  document.querySelector("#metamath"));
</script>

        ]]></description>
        <pubDate>Fri Nov 25 2022 16:00:00 GMT-0800 (Pacific Standard Time)</pubDate>
        <link>https://code.sgo.to/2022/11/26/2p2e4.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2022/11/26/2p2e4.html</guid>
      </item>

      <item>
        <title>Blind Signatures</title>
        <description><![CDATA[
          <!-- CSS -->
          <link href="{{ '/static/droid.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/index.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <link href="{{ '/static/google-sans.css' | absolute_url }}" media="screen" rel="stylesheet" type="text/css" />
          <script src="https://code.sgo.to/typograms/typograms.js"></script>
<style>
code, input {
  color: rgb(36, 41, 46);
  border-radius: 6px;
  font-size: 85%;
  margin: 0;
  padding: .2em .4em;
  font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
  display: inline-block;
  vertical-align: text-bottom;
  max-height: 1.4em;
  background-color: #a8d1ff;
}

input:not(:disabled) {
  background-color: #a8d1ff;
  background-color: white;
  padding-left: 2em;
  padding-right: 1em;
  border: 2px solid  #a8d1ff;
}

.alice, .bob {
  white-space: nowrap;
  overflow: hidden;
}

.alice {
  background-color: #fff2a8;
}

.bob {
  background-color: #4CAF5080;
}

input {
  width: 2.5em;
  text-align: right;
  color: black;
  border: 1px solid rgba(27, 31, 35, 0.05);
  outline: none;
  font-weight: bold;
}

input[disabled] {
}
</style>
<p>This is an open study of RSA Signatures from <a href="https://www.youtube.com/watch?v=rLR8WcXy03Q">here</a> and <a href="https://www.youtube.com/watch?v=s22eJ1eVLTU">here</a>.</p>
<h1>Signatures</h1>
<p>Alice wants to send a <code>m</code> = <input id="m" value="331" type="number" onchange="process()"> to Bob and prove that the message came from her</p>
<ul>
<li>Alice constructs her public and private keys (<a href="/2022/11/10/rsa.html">how and why?</a>):
<ul>
<li>Alice starts by picking two large prime numbers <code class="alice">p</code> = <input type="number" class="alice" disabled id="p" value="53"> and <code class="alice">q</code> = <input type="number" class="alice" disabled id="q" value="59"></li>
<li>Alice then computes <code class="alice">n</code> = <code class="alice"><span name="n">n</span></code></li>
<li>Alice selects an <code>e</code> = <input type="number" disabled id="e" value="3">.</li>
<li>Alice finally <code class="alice">d</code> = <code class="alice"><span name="n">d</span></code>.</li>
</ul>
</li>
<li>Alice has then a public key <code>e</code> = <span name="e">e</span>, <code>n</code> =  = <span name="n">n</span> and private key <code class="alice">d</code> = <code class="alice"><span name="d">d</span></code>.</li>
<li>If Alice wants to send a <code>public</code> message <code>m</code> = <span name="m">m</span>
<ul>
<li>Alice computes <code>s</code> with her <code class="alice">private</code> key by computing <code class="alice">m ^ d % n</code> = <code class="alice"><span name="m">m</span> ^ <span name="d">d</span> % <span name="n">n</span></code> = <span name="s">s</span></li>
<li>Alice sends both <code>m</code> = <span name="m">m</span> and <code>s</code> = <span name="s">s</span> to Bob</li>
</ul>
</li>
<li>Bob verifies it by:
<ul>
<li>Bob computes <code>r</code> = <code>s ^ e % n</code> = <span name="s">s</span> ^ <span name="e">e</span> % <span name="n">n</span> = <span name="r">r</span>.</li>
<li>Correctness: because encryption and decryption on RSA is invertable this is guaranteed to be correct</li>
<li>Confidentiality: because only someone that possesses <code>d</code> could create the encryption, its provenance is guaranteed.</li>
<li>If <code>check</code> = <code>m</code> == <code>r</code> = <span name="m">m</span> = <span name="r">r</span> = <span name="check">check</span>, then
<ul>
<li><code>m</code> is guaranteed to have been signed by <code class="alice">d</code>.</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>In practice, <code>m</code> tends to be large, so we typically hash it before signing.</p>
</blockquote>
<h1>Blind Signatures</h1>
<p>Bob wants Alice to sign a message <code class="bob">m</code> = <span name="m">m</span> without revealing the it to Alice.</p>
<p>A Blind Signature works as follows (from <a href="https://www.youtube.com/watch?v=CcB9nH78Ths">here</a>):</p>
<ul>
<li>Bob computes a random number <code class="bob">k</code> = <span name="k">k</span> (must be relatively prime to <code>n</code> = <code><span name="n">n</span></code>).</li>
<li>Bob computes <code>t</code> = <code>m * (k ^ e) % n</code> = <code class="bob"><span name="m">m</span></code> * (<code class="bob"><span name="k">k</span></code> ^ <code><span name="e">e</span></code>) % <code><span name="n">n</span></code> = <code><span name="t">t</span></code>.</li>
<li>Bob sends <code>t</code> to Alice and asks her to sign it</li>
<li>Alice computes the signature using her private key <code class="alice">d</code> = <span name="d"></span>.
<ul>
<li>Alice calculates <code>b</code> = <code class="alice">t ^ d % n</code> = <code><span name="t">t</span></code> ^ <code class="alice"><span name="d">d</span></code> % <code><span name="n">n</span></code> = <code><span name="b">b</span></code> and sends that back to Bob</li>
</ul>
</li>
<li>Bob gets <code>b</code> and remembers that <code>b</code> = <code>t ^ d % n</code> = <code>(m * (k ^ e) % n) ^ d % n</code> = <code>(m ^ d) * (k ^ e ^ d) % n</code></li>
<li>Bob knows that <code>k ^ e ^ d</code> = <code>k</code> because encryption and decryption cancel each other out, so it is equal to 1, so we get <code>(m ^ d) * k % n</code></li>
<li>Bob computes <code>s2</code> = <code>b</code> / <code>k</code> = <code><span name="b">b</span></code> / <code><span name="k">k</span></code> = <code><span name="s2">s2</span></code>.</li>
<li>The blind signature <code>s2</code> = <span name="s2">s2</span> is exactly the same (<span name="check2">check2</span>) of the unblinded signature <code>s1</code> = <span name="s">s</span></li>
<li>Bob can use the traditional non-blinded verification of the signature:
<ul>
<li>Bob computes <code>r2 = s2 ^ e % n</code> = <span name="s2">s2</span> ^ <span name="e">e</span> % <span name="n">n</span> = <span name="r2">r2</span> and checks that it matches <code>m</code> = <span name="m">m</span>.</li>
<li><code>m</code> is guaranteed to have been signed by <code class="alice">d</code>.</li>
<li><code class="alice">Alice</code> never learns <code>m</code>.</li>
</ul>
</li>
</ul>
<script>
async function sha256(message) {
    // encode as UTF-8
    const msgBuffer = new TextEncoder().encode(message);                    

    // hash the message
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

    // convert ArrayBuffer to Array
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    // convert bytes to hex string
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

let write = (name, value) => {
  for (const el of document.querySelectorAll(`span[name=${name}]`)) {
    el.innerText = value;
  }
}

async function process() {

  let p = BigInt(document.getElementById("p").value);
  let q = BigInt(document.getElementById("q").value);
  let e = BigInt(document.getElementById("e").value);

  let m = BigInt(document.getElementById("m").value);

  const n = p * q;

  const phi = (p - BigInt(1)) * (q - BigInt(1));

  const d = (BigInt(2) * phi + BigInt(1)) / e;

  const s = m ** d % n;

  const r = s ** e % n;

  const check = r == m;

  write("m", m);
  write("p", p);
  write("q", q);
  write("n", n);
  write("e", e);
  write("phi", phi);
  write("d", d);
  write("s", s);
  write("r", r);
  write("check", check);

  //const k = BigInt(Math.floor(Math.random() * 10000));

  //const k = BitInt(8813);
  const k = BigInt(9393);
  // k has to be relatively prime to n.
  // Based on
  // https://github.com/kevinejohn/blind-signatures/blob/master/rsablind.js

  //let k;
  //do {
  //  const hexString = Array(16)
  //    .fill()
  //    .map(() => Math.round(Math.random() * 0xF).toString(16))
  //    .join('');

  //  k = BigInt(`0x${hexString}`);

  // k = BigInt(Math.floor(Math.random() * n));
  // BigInt(`0x${hexString}`)
  //} while (gcd(k, n) != 1);

  // const k = BigInt(2);
  // const k = BigInt(1);

  // console.log(`${m} ${k} ${e} ${n} ${(m * k) ** e % n}`);
  const t = (m * (k ** e)) % n;

  const b = t ** d % n;

  const s2 = (BigInt(modInverse(k, n)) * b) % n;

  let check2 = s2 == s;

  const r2 = s2 ** e % n;

  write("k", k);
  write("t", t);
  write("b", b);
  write("s2", s2);
  write("r2", r2);
  write("check2", check2);
}

// Based on
// https://www.w3resource.com/javascript-exercises/javascript-math-exercise-8.php
function gcd(x, y) {
  if ((typeof x !== 'number') || (typeof y !== 'number')) 
    return false;
  x = Math.abs(x);
  y = Math.abs(y);
  while(y) {
    var t = y;
    y = x % y;
    x = t;
  }
  return x;
}

// Based on
// https://stackoverflow.com/questions/26985808/calculating-the-modular-inverse-in-javascript
function modInverse(a, m) {
  // validate inputs
  [a, m] = [Number(a), Number(m)]
  if (Number.isNaN(a) || Number.isNaN(m)) {
    return NaN // invalid input
  }
  a = (a % m + m) % m
  if (!a || m < 2) {
    return NaN // invalid input
  }
  // find the gcd
  const s = []
  let b = m
  while(b) {
    [a, b] = [b, a % b]
    s.push({a, b})
  }
  if (a !== 1) {
    return NaN // inverse does not exists
  }
  // find the inverse
  let x = 1
  let y = 0
  for(let i = s.length - 2; i >= 0; --i) {
    [x, y] = [y,  x - y * Math.floor(s[i].a / s[i].b)]
  }
  return (y % m + m) % m
}

process();

</script>

        ]]></description>
        <pubDate>Thu Nov 10 2022 16:00:00 GMT-0800 (Pacific Standard Time)</pubDate>
        <link>https://code.sgo.to/2022/11/11/blind-signatures.html</link>
        <guid isPermaLink="true">https://code.sgo.to/2022/11/11/blind-signatures.html</guid>
      </item>

  </channel>
</rss>