Transparencia de los juegos
Cรณmo funciona
El protocolo separa el compromiso pรบblico de la revelaciรณn de la semilla. Esto impide elegir el resultado despuรฉs de la participaciรณn.
1. Compromiso
Se publica un hash antes del resultado. Funciona como un sobre digital sellado.
2. Sorteo
El resultado se deriva de datos ya comprometidos mediante una funciรณn determinista.
3. Revelaciรณn
Despuรฉs del resultado, la semilla y los parรกmetros necesarios quedan disponibles para su inspecciรณn.
4. Reproducciรณn
El navegador repite los hashes y el sorteo. La prueba solo pasa cuando todo coincide con el registro.
Prueba de la Ruleta
Cรณmo se calcula el resultado
La Ruleta combina una cadena de semillas con el catรกlogo y los pesos conservados al girar.
- Los elementos se ordenan por clave. Cada clave y peso entra en el catรกlogo canรณnico usado por el cรกlculo.
- El compromiso del giro es sha-256 del dominio del protocolo, la semilla del servidor y el catรกlogo canรณnico.
- El compromiso de cadena vincula el hash inicial, el catรกlogo y el tamaรฑo; la posiciรณn prueba que la semilla pertenece a la cadena publicada.
- El hash del resultado se convierte en un nรบmero mรณdulo la suma de pesos. Ese ticket determina el elemento seleccionado.
Cรณdigo de verificaciรณn
Pรฉgalo en la consola del navegador o guรกrdalo como archivo .mjs y ejecรบtalo con Node. El cรณdigo rehace los hashes y el sorteo con los datos de la prueba y muestra si cada parte coincide.
Verifica un compromiso arriba para rellenar el cรณdigo con los datos de la prueba.
const proof = {
"serverSeed": "SERVER_SEED",
"commitmentHash": "COMMITMENT_HASH",
"chainCommitment": "CHAIN_COMMITMENT",
"headHash": "HEAD_HASH",
"index": 0,
"size": 1,
"items": [
{
"key": "ITEM_KEY",
"weight": 1
}
]
}
async function sha256(text) {
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')
}
async function verifyReward(proof) {
const items = [...proof.items]
.sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0))
.map(({ key, weight }) => ({ key, weight }))
const commitment = await sha256(
JSON.stringify(['tgrtslj:reward:commitment:v1', proof.serverSeed, items]),
)
const chain = await sha256(
JSON.stringify(['tgrtslj:reward:commitment-chain:v1', proof.headHash, items, proof.size]),
)
let link = proof.serverSeed
for (let step = 0; step <= proof.index; step += 1) link = await sha256(link)
const resultHash = await sha256(
JSON.stringify(['tgrtslj:reward:weighted-draw:v1', proof.serverSeed, items]),
)
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0)
const ticket = Number(BigInt('0x' + resultHash) % BigInt(totalWeight))
let upperBound = 0
const selected = items.find((item) => ticket < (upperBound += item.weight))
return {
commitmentMatches: commitment === proof.commitmentHash,
chainMatches: chain === proof.chainCommitment,
positionMatches: proof.index < proof.size && link === proof.headHash,
resultHash,
ticket,
item: selected.key,
}
}
console.log(await verifyReward(proof))
Prueba de la Raspadita
Cรณmo se calcula el resultado
La prueba queda disponible solo despuรฉs de revelar, cuando la semilla puede mostrarse de forma segura.
- El catรกlogo y los pesos se congelan al conceder la raspadita, aunque la configuraciรณn cambie despuรฉs.
- El compromiso usa el mismo dominio criptogrรกfico y el mismo sorteo ponderado de la Ruleta.
- La posiciรณn de la semilla se verifica contra el hash inicial y el compromiso de cadena publicado anteriormente.
- El elemento, el hash del resultado y la semilla revelada deben reproducir exactamente el premio registrado.
Cรณdigo de verificaciรณn
Pรฉgalo en la consola del navegador o guรกrdalo como archivo .mjs y ejecรบtalo con Node. El cรณdigo rehace los hashes y el sorteo con los datos de la prueba y muestra si cada parte coincide.
Verifica un compromiso arriba para rellenar el cรณdigo con los datos de la prueba.
const proof = {
"serverSeed": "SERVER_SEED",
"commitmentHash": "COMMITMENT_HASH",
"chainCommitment": "CHAIN_COMMITMENT",
"headHash": "HEAD_HASH",
"index": 0,
"size": 1,
"items": [
{
"key": "ITEM_KEY",
"weight": 1
}
]
}
async function sha256(text) {
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')
}
async function verifyReward(proof) {
const items = [...proof.items]
.sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0))
.map(({ key, weight }) => ({ key, weight }))
const commitment = await sha256(
JSON.stringify(['tgrtslj:reward:commitment:v1', proof.serverSeed, items]),
)
const chain = await sha256(
JSON.stringify(['tgrtslj:reward:commitment-chain:v1', proof.headHash, items, proof.size]),
)
let link = proof.serverSeed
for (let step = 0; step <= proof.index; step += 1) link = await sha256(link)
const resultHash = await sha256(
JSON.stringify(['tgrtslj:reward:weighted-draw:v1', proof.serverSeed, items]),
)
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0)
const ticket = Number(BigInt('0x' + resultHash) % BigInt(totalWeight))
let upperBound = 0
const selected = items.find((item) => ticket < (upperBound += item.weight))
return {
commitmentMatches: commitment === proof.commitmentHash,
chainMatches: chain === proof.chainCommitment,
positionMatches: proof.index < proof.size && link === proof.headHash,
resultHash,
ticket,
item: selected.key,
}
}
console.log(await verifyReward(proof))

