<div class="w-full max-w-lg flex flex-col mx-auto my-4">
  <div class="flex items-center gap-2">
    <svg width="1.25rem" height="1.25rem" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.7156 1.53348C8.89223 1.43415 9.10778 1.43415 9.2844 1.53348L15.2057 4.86421L9.00045 8.3547L2.79461 4.86404L8.7156 1.53348ZM2.23242 5.83859V12.4805C2.23242 12.69 2.34546 12.8833 2.52811 12.986L8.43795 16.3103V9.3291L2.23242 5.83859ZM9.56295 16.3099L15.4719 12.986C15.6545 12.8833 15.7676 12.69 15.7676 12.4805V5.83891L9.56295 9.3291V16.3099Z" fill="currentColor"></path></svg>
    <h1 class="text-lg font-bold">Lootbox.com Provably Fair - Boxes</h1>
  </div>
  
  <div class="flex flex-col gap-1.5 mt-4">
    <label for="hashedServerSeed">Hashed Server Seed</label>
    <input id="hashedServerSeed">
    <div class="msg text-sm" id="hashedServerSeed-msg"></div>
  </div>
  
  <div class="flex flex-col gap-1.5 mt-3">
    <label for="serverSeed">Server Seed</label>
    <input id="serverSeed">
    <div class="msg text-sm" id="serverSeed-msg"></div>
  </div>
  
  <div class="flex flex-col gap-1.5 mt-3">
    <label for="clientSeed">Client Seed</label>
    <input id="clientSeed">
    <div class="msg text-sm" id="clientSeed-msg"></div>
  </div>
  
  <div class="flex flex-col gap-1.5 mt-3">
    <label for="nonce">Nonce</label>
    <input id="nonce" type="number">
    <div class="msg text-sm" id="nonce-msg"></div>
  </div>
  
  <button id="verify" class="mt-6">Verify</button>
  
  <div class="msg text-sm mt-4" id="result"></div>
 
</div>
html {
  width: 100%;
  height: 100%;
  overflow: hidden;
  color-scheme: dark;
}

body {
  width: 100%;
  height: 100%;
  color: #9da0c0;
  font-weight: 500;
  background: #0f111d;
  padding: 0 1rem;
  overflow-y: auto;
}

h1 {
  color: #f3faff;
}

label {
  width: fit-content;
}

label > span {
  color: #515474;
}

input {
  height: 2.5rem;
  color: #f3faff;
  font-size: .875rem;
  line-height: calc(1.25/.875);
  background: #191b2b;
  border: 1px solid transparent;
  border-radius: 0.5rem;
  padding: 0 1rem;
  outline: none !important;
  transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

input:hover {
  border-color: #2d2f46;
}

input:focus {
  border-color: #ffc300;
}

button {
  height: 2.625rem;
  color: #13141b;
  font-weight: 700;
  font-size: 0.875rem;
  background: #21d35b;
  border-top: 1px solid #49e578;
  border-bottom: 2px solid #2e7e40;
  border-radius: 0.5rem;
  padding: 0 1rem;
  user-select: none;
  transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

button:hover {
  filter: brightness(0.9);
}

button:active {
  filter: brightness(0.8);
}
import { sha256 } from 'https://esm.sh/js-sha256'
import { sha512 } from 'https://esm.sh/js-sha512'

document.getElementById('verify').onclick = () => {
  resetMessages()
 
  const hashedServerSeed = document.getElementById('hashedServerSeed').value
  const serverSeed = document.getElementById('serverSeed').value
  const clientSeed = document.getElementById('clientSeed').value
  const nonce = document.getElementById('nonce').value
  
  let error = false
  if (!hashedServerSeed) error = message('error', 'This field is required', 'hashedServerSeed')
  if (!serverSeed) error = message('error', 'This field is required', 'serverSeed')
  if (!clientSeed) error = message('error', 'This field is required', 'clientSeed')
  if (!nonce) error = message('error', 'This field is required', 'nonce')
  if (error) return
  
  const hashedServerSeedReplica = sha256(serverSeed)
  if (hashedServerSeedReplica !== hashedServerSeed) return message('error', "Hashed Server Seed doesn't resolve to Server Seed")
  
  const MAX_TICKET = 100_000_000
  const resultHash = sha512(`${clientSeed}:${serverSeed}:${nonce}`)
  const subHash = resultHash.slice(0, 13)
  const valueFromHash = parseInt(subHash, 16)
  const e = Math.pow(2, 52)
  const normalizedValue = valueFromHash / e
  const ticketResult = Math.floor(normalizedValue * MAX_TICKET) + 1
  
  const formattedTicketResult = Intl.NumberFormat('en-US', {
    style: 'decimal'
  }).format(ticketResult)
  
  message('success', `Ticket Result: ${formattedTicketResult}`)
}










function message(type, msg, elemId) {
  if (type === 'error') {
    if (elemId) {
      document.getElementById(`${elemId}-msg`).innerHTML = `<div class="text-red-500 font-normal">${msg}</div>`
      document.getElementById(elemId).classList.add('border-red-500')
    } else {
      document.getElementById('result').innerHTML = `
        <div class="text-red-500 text-lg font-bold">UNBOXING IS INVALID</div>
        <div class="text-white font-normal">${msg}</div>
      `
    }
  } else {
    document.getElementById('result').innerHTML = `
      <div class="text-green-500 text-lg font-bold">UNBOXING IS VALID</div>
      <div class="text-white font-normal">${msg}</div>
    `
  }
  
  return type === 'error'
}


function resetMessages() {
  const messages = document.querySelectorAll('.msg')
  messages.forEach(msg => {
    msg.innerHTML = ''
  })
  const inputs = document.querySelectorAll('input')
  inputs.forEach(msg => {
    msg.classList.remove('border-red-500')
  })
}

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.