A Cadeia de Transparência Dalanga
Toda rifa na Dalanga é respaldada por um registro imutável e publicamente verificável.
Nosso Compromisso com a Transparência
A Dalanga foi construída sobre um princípio simples: toda rifa deve ser comprovadamente justa. Quando você compra um bilhete, merece saber que o sorteio foi honesto, que as regras não foram alteradas após o fato, e que o vencedor foi escolhido pela matemática — não pela decisão de alguém.
Para cumprir essa promessa, construímos a Cadeia de Transparência: um registro digital imutável que registra permanentemente cada evento significativo no ciclo de vida de uma rifa. Uma vez que um registro é gravado, ele não pode ser alterado ou excluído — nem por nós, nem por ninguém.
Você não precisa confiar em nós. Pode verificar por conta própria.
O que é a Cadeia de Transparência?
Pense na Cadeia de Transparência como um caderno digital onde cada página é conectada à anterior usando uma impressão digital matemática (chamada hash).
Cada página — chamamos de blocos — contém a descrição de algo que aconteceu: uma rifa foi criada, um bilhete foi comprado, um preço foi alterado ou um vencedor foi sorteado.
Cada bloco inclui o que aconteceu, quando aconteceu, quem fez e um vínculo matemático com o bloco anterior.
Como a impressão digital de cada bloco depende do bloco anterior, alterar qualquer registro passado quebraria a cadeia desse ponto em diante. Isso torna a adulteração imediatamente detectável.
Todos os eventos em toda a plataforma compartilham uma única cadeia sequencial. Percorrer a cadeia do início ao fim verifica o histórico completo da plataforma.
O que é Registrado
Cada ação significativa na plataforma cria um registro permanente.
Criação e Configuração de Rifas
- check Rifa criada (título, limite de bilhetes, tipo de sorteio)
- check Rifa publicada, pausada, despublicada ou cancelada
- check Alterações de configuração (rastreadas campo por campo)
- check Atualizações de parâmetros de equidade (ex.: expiração de reserva)
Bilhetes
- check Bilhete comprado (online ou papel)
- check Pagamento de bilhete confirmado
- check Bilhete cancelado
Preços e Mídia
- check Preço adicionado, alterado ou removido (com valores antigos e novos)
- check Imagens de prêmios adicionadas ou removidas
O Sorteio
- check Compromisso do sorteio publicado (parâmetros bloqueados antes do sorteio)
- check Sorteio revelado (semente tornada pública, derivação do vencedor mostrada)
- check Bilhete vencedor selecionado
Lojas e Criadores
- check Loja criada, atualizada, mudanças de status
- check Perfil de criador criado, atualizado, mudanças de verificação
- check Alterações de logotipo da loja
Financeiro e Moderação
- check Pagamentos a criadores criados e mudanças de status
- check Denúncias de lojas registradas e mudanças de status
- check Transações de carteira
Ciclo de Vida do Usuário
- check Registro de conta e verificação de e-mail
- check Atualizações de perfil e solicitações de exclusão de conta
Informações pessoais são armazenadas separadamente da cadeia permanente e podem ser removidas mediante solicitação conforme a lei de privacidade (LGPD).
Como o Sorteio Funciona
Todo sorteio na Dalanga usa um processo de compromisso-revelação que torna matematicamente impossível que qualquer pessoa — incluindo nós — influencie o resultado.
Passo 1: Compromisso
- arrow_right Gera uma semente secreta aleatória (64 caracteres aleatórios)
- arrow_right Captura um instantâneo de todos os bilhetes elegíveis (pagos), em ordem
- arrow_right Combina a semente com a lista de bilhetes e produz um hash de compromisso
- arrow_right Publica o hash de compromisso na Cadeia de Transparência
- arrow_right Tranca a semente, criptografada, até o sorteio
Neste ponto, o resultado já está determinado — mas ninguém pode vê-lo ainda, porque a semente está criptografada.
Passo 2: Revelação
- arrow_right Descriptografa e publica a semente
- arrow_right Executa a fórmula de seleção do vencedor usando hash SHA-256, aritmética modular e a lista ordenada de bilhetes
- arrow_right Registra o cálculo completo na Cadeia de Transparência
Por que Isso Importa
Como o hash de compromisso foi publicado antes da semente ser revelada, qualquer pessoa pode verificar que a semente não foi alterada, o vencedor foi derivado exatamente como descrito, e nenhum humano fez a escolha — apenas a matemática.
Como Verificar por Conta Própria
Fácil: Navegue pelo Explorador de Cadeia
Cada página de rifa inclui um link para seu Explorador de Cadeia — um navegador visual onde você pode ver cada evento registrado, quando aconteceu, e as provas matemáticas conectando cada bloco. Nenhum conhecimento técnico necessário.
A plataforma também oferece um Explorador de Cadeia do Sistema para navegar pela cadeia global completa.
Médio: Baixe a Exportação da Cadeia
Na página de cadeia de qualquer rifa, você pode baixar a cadeia completa como arquivo JSON. Este arquivo contém cada bloco, cada hash e cada registro de âncora. Você pode abri-lo em qualquer editor de texto ou visualizador JSON para inspecionar os dados brutos.
Avançado: Execute a Ferramenta de Verificação
Fornecemos scripts de verificação de código aberto em PHP, Python, Bash e Node.js que verificam toda a cadeia offline. Eles verificam cada hash, cada vínculo, re-derivam vencedores de sorteios e verificam hashes de compromisso — nenhuma conta necessária.
Privacidade e Direitos de Dados
Como Separamos os Dados
A cadeia em si contém apenas resumos não pessoais: tipos de eventos, mudanças de status, números de bilhetes, timestamps e provas matemáticas. Informações pessoais são armazenadas em uma camada separada e removível.
Um compromisso matemático (hash) em cada bloco da cadeia prova que os dados completos existiam no momento do registro — sem expor detalhes pessoais na cadeia permanente.
Seu Direito à Exclusão
Pela LGPD, você pode solicitar a exclusão dos seus dados pessoais. Quando isso acontece, suas informações pessoais são removidas da camada de dados separada, os blocos permanentes da cadeia permanecem inalterados, registros limpos são marcados de forma transparente, e o compromisso matemático ainda prova quais eram os dados originais.
Este design nos permite honrar seus direitos de privacidade mantendo a integridade da cadeia para todos.
Explorar a Cadeia
Toda rifa na Dalanga é respaldada por um registro imutável e publicamente verificável.
Apêndice Técnico
sequence INTEGER Auto-incrementing block number event_type VARCHAR Category of recorded event event_data JSON Structured event payload actor VARCHAR Who triggered the event hash_input TEXT Canonical input fed to SHA-256 hash CHAR(64) SHA-256 digest of hash_input prev_hash CHAR(64) Hash of the preceding block (null for genesis) payload_hash CHAR(64) SHA-256 of the full event payload created_at TIMESTAMP When the block was recorded
SHA-256 com codificação JSON canônica (barras e unicode não escapados)
hash_input = sequence|event_type|json(event_data)|actor|previous_hash hash = sha256(hash_input)
Cadeia de blocos global única — todos os eventos em todos os domínios compartilham uma cadeia sequencial. Sem particionamento por rifa.
draw_hash = sha256(seed + sorted_ticket_numbers) winner_index = hex_to_int(draw_hash[0:12]) mod ticket_count winner = sorted_tickets[winner_index]
Fornecemos scripts de verificação de código aberto em vários idiomas. Cada script é independente e sem dependências externas (Bash requer jq, sha256sum e bc). Baixe ou copie qualquer script abaixo e execute contra um arquivo de exportação da cadeia para verificar independentemente cada bloco.
Exemplo:
# Download the chain and verify:
curl -s https://dalanga.com/api/system-chain/export -o chain.json
php verify-chain.php chain.json
#!/usr/bin/env php
<?php
/**
* Dalanga Transparency Chain Verification Tool
*
* A standalone, zero-dependency PHP script that verifies the integrity of a
* downloaded Dalanga system chain export file.
*
* Requirements: PHP 8.0+ with JSON and hash extensions (standard in all PHP installs).
* No Composer, no autoloader, no framework dependencies.
*
* Usage:
* php verify-chain.php <path-to-export.json>
*
* Examples:
* php verify-chain.php system-chain-export.json
* php verify-chain.php /tmp/chain-export.json
*
* What it verifies:
* 1. Consecutive sequence numbers starting from 0
* 2. Hash linking: each block's previous_hash matches the preceding block's hash
* 3. Hash recomputation: re-hashes each block's hash_input and compares to stored hash
* 4. Genesis block validation: sequence 0 must have previous_hash = null
* 5. Draw fairness: for draw.revealed events, re-derives the winner using the
* algorithm_version from event_data and confirms the recorded winner matches
*
* Exit codes:
* 0 = Chain is valid (all checks passed)
* 1 = Chain is invalid (one or more checks failed)
* 2 = Usage error (missing file, invalid JSON, etc.)
*/
// ANSI color codes
define('GREEN', "\033[32m");
define('RED', "\033[31m");
define('RESET', "\033[0m");
define('BOLD', "\033[1m");
function success(string $message): void
{
echo GREEN.'✓'.RESET.' '.$message.PHP_EOL;
}
function failure(string $message): void
{
echo RED.'✗'.RESET.' '.$message.PHP_EOL;
}
function canonical_json_encode(mixed $data): string
{
return json_encode(sort_keys_recursive($data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
function sort_keys_recursive(mixed $data): mixed
{
if (! is_array($data)) {
return $data;
}
if (array_is_list($data)) {
return array_map('sort_keys_recursive', $data);
}
ksort($data);
return array_map('sort_keys_recursive', $data);
}
// ─── Input Validation ────────────────────────────────────────────────
if ($argc < 2) {
echo 'Dalanga Transparency Chain Verification Tool'.PHP_EOL;
echo PHP_EOL;
echo 'Usage: php '.basename($argv[0]).' <path-to-export.json>'.PHP_EOL;
echo PHP_EOL;
echo 'Verifies the cryptographic integrity of a chain export file.'.PHP_EOL;
echo 'For draw events, re-derives the winner to confirm fairness.'.PHP_EOL;
exit(2);
}
$path = $argv[1];
if (! file_exists($path)) {
failure("File not found: {$path}");
exit(2);
}
$content = file_get_contents($path);
$data = json_decode($content, true);
if ($data === null) {
failure('Invalid JSON: '.json_last_error_msg());
exit(2);
}
if (! isset($data['blocks']) || ! is_array($data['blocks'])) {
failure('Invalid export format: missing "blocks" array');
exit(2);
}
// ─── Chain Verification ──────────────────────────────────────────────
$blocks = $data['blocks'];
$blockCount = count($blocks);
$errors = 0;
echo BOLD.'Dalanga Transparency Chain Verifier'.RESET.PHP_EOL;
if (isset($data['chain'])) {
echo 'Chain: '.$data['chain'].PHP_EOL;
}
if (isset($data['exported_at'])) {
echo 'Exported: '.$data['exported_at'].PHP_EOL;
}
echo 'Blocks: '.$blockCount.PHP_EOL;
echo PHP_EOL;
if ($blockCount === 0) {
success('Chain is empty (0 blocks)');
exit(0);
}
// Chain integrity checks
for ($i = 0; $i < $blockCount; $i++) {
$block = $blocks[$i];
// Check consecutive sequences
if ($block['sequence'] !== $i) {
failure("Block {$i}: expected sequence {$i}, got {$block['sequence']}");
$errors++;
continue;
}
// Genesis block validation
if ($i === 0 && $block['previous_hash'] !== null) {
failure('Block 0: genesis block must have previous_hash = null');
$errors++;
}
// Hash linking
if ($i > 0 && $block['previous_hash'] !== $blocks[$i - 1]['hash']) {
failure("Block {$i}: previous_hash does not match preceding block's hash");
$errors++;
}
// Hash recomputation
$recomputedHash = hash('sha256', $block['hash_input']);
if ($block['hash'] !== $recomputedHash) {
failure("Block {$i}: hash does not match recomputed hash from hash_input");
$errors++;
} else {
$eventType = $block['event_type'] ?? 'unknown';
success("Block {$i}: hash valid ({$eventType})");
}
}
// ─── Payload Hash Verification ──────────────────────────────────────
$hasPayloadHashes = false;
foreach ($blocks as $block) {
if (isset($block['payload_hash'])) {
$hasPayloadHashes = true;
break;
}
}
if ($hasPayloadHashes) {
echo PHP_EOL;
echo BOLD.'Payload Hash Verification'.RESET.PHP_EOL;
$payloadChecked = 0;
$payloadSkipped = 0;
foreach ($blocks as $block) {
if (! isset($block['payload_hash'])) {
continue;
}
if (! isset($block['payload'])) {
$payloadSkipped++;
continue;
}
$payloadChecked++;
$recomputedPayloadHash = hash('sha256', canonical_json_encode($block['payload']));
if ($block['payload_hash'] !== $recomputedPayloadHash) {
failure("Block {$block['sequence']}: payload_hash does not match recomputed hash from payload");
$errors++;
} else {
success("Block {$block['sequence']}: payload hash valid ({$block['event_type']})");
}
}
if ($payloadSkipped > 0) {
echo "(Skipped {$payloadSkipped} block(s) without payload data — blocks-only export)".PHP_EOL;
}
echo "Payload hashes checked: {$payloadChecked}, skipped: {$payloadSkipped}".PHP_EOL;
}
// ─── Anchor Event Verification ───────────────────────────────────────
$anchorConfiguredCount = 0;
$anchoredBlocks = [];
foreach ($blocks as $block) {
$eventType = $block['event_type'] ?? null;
if ($eventType === 'chain.anchor_configured') {
$anchorConfiguredCount++;
$eventData = $block['event_data'] ?? [];
if (! isset($eventData['anchor_service'])) {
failure("Block {$block['sequence']}: chain.anchor_configured missing anchor_service");
$errors++;
} else {
success("Block {$block['sequence']}: anchor configured ({$eventData['anchor_service']})");
}
if ($anchorConfiguredCount > 1) {
failure("Block {$block['sequence']}: duplicate chain.anchor_configured (expected at most 1)");
$errors++;
}
}
if ($eventType === 'chain.anchored') {
$eventData = $block['event_data'] ?? [];
if (! isset($eventData['anchor_service'])) {
failure("Block {$block['sequence']}: chain.anchored missing anchor_service");
$errors++;
}
if (! isset($eventData['anchor_reference'])) {
failure("Block {$block['sequence']}: chain.anchored missing anchor_reference");
$errors++;
}
if (! isset($eventData['chain_state_hash'])) {
failure("Block {$block['sequence']}: chain.anchored missing chain_state_hash");
$errors++;
}
if (! isset($eventData['block_count'])) {
failure("Block {$block['sequence']}: chain.anchored missing block_count");
$errors++;
}
if (! isset($eventData['block_sequence'])) {
failure("Block {$block['sequence']}: chain.anchored missing block_sequence");
$errors++;
} else {
// anchor's block_sequence must reference a block that exists before this anchor block
if ($eventData['block_sequence'] >= $block['sequence']) {
failure("Block {$block['sequence']}: chain.anchored references block_sequence {$eventData['block_sequence']} which is not before this block");
$errors++;
} else {
success("Block {$block['sequence']}: anchor record valid ({$eventData['anchor_service']} @ seq {$eventData['block_sequence']})");
}
}
$anchoredBlocks[] = $block;
}
}
if ($anchoredBlocks !== []) {
echo PHP_EOL;
echo BOLD.'Anchor History'.RESET.PHP_EOL;
echo 'Anchor configuration events: '.$anchorConfiguredCount.PHP_EOL;
echo 'Anchor records on-chain: '.count($anchoredBlocks).PHP_EOL;
foreach ($anchoredBlocks as $ab) {
$ed = $ab['event_data'] ?? [];
$service = $ed['anchor_service'] ?? '?';
$ref = $ed['anchor_reference'] ?? '';
$seq = $ed['block_sequence'] ?? '?';
echo " [{$service}] seq {$seq} → {$ref}".PHP_EOL;
}
}
// ─── Draw Event Verification ─────────────────────────────────────────
$committedBlocks = [];
foreach ($blocks as $block) {
if (($block['event_type'] ?? null) === 'draw.committed') {
$committedBlocks[] = $block;
}
}
foreach ($blocks as $block) {
if (($block['event_type'] ?? null) !== 'draw.revealed') {
continue;
}
$eventData = $block['event_data'] ?? [];
$algorithmVersion = $eventData['algorithm_version'] ?? null;
if ($algorithmVersion === null) {
failure("Block {$block['sequence']}: draw.revealed missing algorithm_version");
$errors++;
continue;
}
if ($algorithmVersion !== 'sha256-modulo-v1') {
failure("Block {$block['sequence']}: unknown algorithm version '{$algorithmVersion}'");
$errors++;
continue;
}
// Find matching draw.committed block
$committed = null;
foreach ($committedBlocks as $cb) {
$cbData = $cb['event_data'] ?? [];
if (isset($cbData['eligible_tickets_snapshot'])) {
$committed = $cb;
}
}
if ($committed === null) {
failure("Block {$block['sequence']}: draw.revealed but no matching draw.committed found");
$errors++;
continue;
}
$committedData = $committed['event_data'];
$seed = $eventData['seed'];
$eligibleTickets = $committedData['eligible_tickets_snapshot'];
$totalEligible = count($eligibleTickets);
// Re-derive winner using sha256-modulo-v1
$winnerIndex = hexdec(substr(hash('sha256', $seed), 0, 12)) % $totalEligible;
$winnerTicketNumber = $eligibleTickets[$winnerIndex];
if ($winnerIndex !== $eventData['winner_index']) {
failure("Block {$block['sequence']}: draw winner_index mismatch (expected {$winnerIndex}, got {$eventData['winner_index']})");
$errors++;
} elseif ($winnerTicketNumber !== $eventData['winner_ticket_number']) {
failure("Block {$block['sequence']}: draw winner_ticket_number mismatch (expected {$winnerTicketNumber}, got {$eventData['winner_ticket_number']})");
$errors++;
} else {
success("Block {$block['sequence']}: draw winner independently verified (sha256-modulo-v1)");
}
// Verify commitment hash
$commitmentHash = $committedData['commitment_hash'] ?? null;
if ($commitmentHash !== null) {
$recomputedCommitment = hash('sha256', $seed.json_encode($eligibleTickets));
if ($commitmentHash !== $recomputedCommitment) {
failure("Block {$committed['sequence']}: commitment hash mismatch");
$errors++;
} else {
success("Block {$committed['sequence']}: commitment hash verified");
}
}
}
// ─── Summary ─────────────────────────────────────────────────────────
echo PHP_EOL;
if ($errors === 0) {
echo GREEN."Chain valid: {$blockCount} blocks verified, 0 errors".RESET.PHP_EOL;
exit(0);
}
echo RED."Chain INVALID: {$blockCount} blocks verified, {$errors} error(s)".RESET.PHP_EOL;
exit(1);
php verify-chain.php export.json
download
Baixar
#!/usr/bin/env python3
"""
Dalanga Transparency Chain Verification Tool
A standalone, zero-dependency Python script that verifies the integrity of a
downloaded Dalanga system chain export file.
Requirements: Python 3.6+ (standard library only).
Usage:
python3 verify-chain.py <path-to-export.json>
Examples:
python3 verify-chain.py system-chain-export.json
python3 verify-chain.py /tmp/chain-export.json
What it verifies:
1. Consecutive sequence numbers starting from 0
2. Hash linking: each block's previous_hash matches the preceding block's hash
3. Hash recomputation: re-hashes each block's hash_input and compares to stored hash
4. Genesis block validation: sequence 0 must have previous_hash = null
5. Payload hash verification: re-hashes payload and compares to stored payload_hash
6. Draw fairness: for draw.revealed events, re-derives the winner using the
algorithm_version from event_data and confirms the recorded winner matches
Exit codes:
0 = Chain is valid (all checks passed)
1 = Chain is invalid (one or more checks failed)
2 = Usage error (missing file, invalid JSON, etc.)
"""
import hashlib
import json
import sys
# ANSI color codes
GREEN = "\033[32m"
RED = "\033[31m"
RESET = "\033[0m"
BOLD = "\033[1m"
def success(message: str) -> None:
print(f"{GREEN}\u2713{RESET} {message}")
def failure(message: str) -> None:
print(f"{RED}\u2717{RESET} {message}")
# --- Input Validation -------------------------------------------------------
if len(sys.argv) < 2:
print("Dalanga Transparency Chain Verification Tool")
print()
print(f"Usage: python3 {sys.argv[0]} <path-to-export.json>")
print()
print("Verifies the cryptographic integrity of a chain export file.")
print("For draw events, re-derives the winner to confirm fairness.")
sys.exit(2)
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
failure(f"File not found: {path}")
sys.exit(2)
except OSError as e:
failure(f"Cannot read file: {e}")
sys.exit(2)
try:
data = json.loads(content)
except json.JSONDecodeError as e:
failure(f"Invalid JSON: {e}")
sys.exit(2)
if not isinstance(data.get("blocks"), list):
failure('Invalid export format: missing "blocks" array')
sys.exit(2)
# --- Chain Verification ------------------------------------------------------
blocks = data["blocks"]
block_count = len(blocks)
errors = 0
print(f"{BOLD}Dalanga Transparency Chain Verifier{RESET}")
if "chain" in data:
print(f"Chain: {data['chain']}")
if "exported_at" in data:
print(f"Exported: {data['exported_at']}")
print(f"Blocks: {block_count}")
print()
if block_count == 0:
success("Chain is empty (0 blocks)")
sys.exit(0)
# Chain integrity checks
for i in range(block_count):
block = blocks[i]
# Check consecutive sequences
if block["sequence"] != i:
failure(f"Block {i}: expected sequence {i}, got {block['sequence']}")
errors += 1
continue
# Genesis block validation
if i == 0 and block["previous_hash"] is not None:
failure("Block 0: genesis block must have previous_hash = null")
errors += 1
# Hash linking
if i > 0 and block["previous_hash"] != blocks[i - 1]["hash"]:
failure(f"Block {i}: previous_hash does not match preceding block's hash")
errors += 1
# Hash recomputation
recomputed_hash = hashlib.sha256(block["hash_input"].encode("utf-8")).hexdigest()
if block["hash"] != recomputed_hash:
failure(f"Block {i}: hash does not match recomputed hash from hash_input")
errors += 1
else:
event_type = block.get("event_type", "unknown")
success(f"Block {i}: hash valid ({event_type})")
# --- Payload Hash Verification -----------------------------------------------
has_payload_hashes = any("payload_hash" in b for b in blocks)
if has_payload_hashes:
print()
print(f"{BOLD}Payload Hash Verification{RESET}")
payload_checked = 0
payload_skipped = 0
for block in blocks:
if "payload_hash" not in block:
continue
if "payload" not in block:
payload_skipped += 1
continue
payload_checked += 1
payload_json = json.dumps(block["payload"], ensure_ascii=False, separators=(",", ":"), sort_keys=True)
recomputed_payload_hash = hashlib.sha256(payload_json.encode("utf-8")).hexdigest()
if block["payload_hash"] != recomputed_payload_hash:
failure(f"Block {block['sequence']}: payload_hash does not match recomputed hash from payload")
errors += 1
else:
success(f"Block {block['sequence']}: payload hash valid ({block['event_type']})")
if payload_skipped > 0:
print(f"(Skipped {payload_skipped} block(s) without payload data \u2014 blocks-only export)")
print(f"Payload hashes checked: {payload_checked}, skipped: {payload_skipped}")
# --- Draw Event Verification -------------------------------------------------
committed_blocks = [b for b in blocks if b.get("event_type") == "draw.committed"]
for block in blocks:
if block.get("event_type") != "draw.revealed":
continue
event_data = block.get("event_data", {})
algorithm_version = event_data.get("algorithm_version")
if algorithm_version is None:
failure(f"Block {block['sequence']}: draw.revealed missing algorithm_version")
errors += 1
continue
if algorithm_version != "sha256-modulo-v1":
failure(f"Block {block['sequence']}: unknown algorithm version '{algorithm_version}'")
errors += 1
continue
# Find matching draw.committed block
committed = None
for cb in committed_blocks:
cb_data = cb.get("event_data", {})
if "eligible_tickets_snapshot" in cb_data:
committed = cb
if committed is None:
failure(f"Block {block['sequence']}: draw.revealed but no matching draw.committed found")
errors += 1
continue
committed_data = committed["event_data"]
seed = event_data["seed"]
eligible_tickets = committed_data["eligible_tickets_snapshot"]
total_eligible = len(eligible_tickets)
# Re-derive winner using sha256-modulo-v1
winner_index = int(hashlib.sha256(seed.encode("utf-8")).hexdigest()[:12], 16) % total_eligible
winner_ticket_number = eligible_tickets[winner_index]
if winner_index != event_data["winner_index"]:
failure(f"Block {block['sequence']}: draw winner_index mismatch (expected {winner_index}, got {event_data['winner_index']})")
errors += 1
elif winner_ticket_number != event_data["winner_ticket_number"]:
failure(f"Block {block['sequence']}: draw winner_ticket_number mismatch (expected {winner_ticket_number}, got {event_data['winner_ticket_number']})")
errors += 1
else:
success(f"Block {block['sequence']}: draw winner independently verified (sha256-modulo-v1)")
# Verify commitment hash
commitment_hash = committed_data.get("commitment_hash")
if commitment_hash is not None:
recomputed_commitment = hashlib.sha256(
(seed + json.dumps(eligible_tickets, separators=(",", ":"))).encode("utf-8")
).hexdigest()
if commitment_hash != recomputed_commitment:
failure(f"Block {committed['sequence']}: commitment hash mismatch")
errors += 1
else:
success(f"Block {committed['sequence']}: commitment hash verified")
# --- Summary -----------------------------------------------------------------
print()
if errors == 0:
print(f"{GREEN}Chain valid: {block_count} blocks verified, 0 errors{RESET}")
sys.exit(0)
print(f"{RED}Chain INVALID: {block_count} blocks verified, {errors} error(s){RESET}")
sys.exit(1)
python3 verify-chain.py export.json
download
Baixar
#!/usr/bin/env bash
set -euo pipefail
# Dalanga Transparency Chain Verification Tool
#
# A standalone Bash script that verifies the integrity of a downloaded
# Dalanga system chain export file.
#
# Requirements: jq, sha256sum (or shasum), bc
#
# Usage:
# bash verify-chain.sh <path-to-export.json>
#
# Examples:
# bash verify-chain.sh system-chain-export.json
# bash verify-chain.sh /tmp/chain-export.json
#
# What it verifies:
# 1. Consecutive sequence numbers starting from 0
# 2. Hash linking: each block's previous_hash matches the preceding block's hash
# 3. Hash recomputation: re-hashes each block's hash_input and compares to stored hash
# 4. Genesis block validation: sequence 0 must have previous_hash = null
# 5. Payload hash verification: re-hashes payload and compares to stored payload_hash
# 6. Draw fairness: for draw.revealed events, re-derives the winner using the
# algorithm_version from event_data and confirms the recorded winner matches
#
# Exit codes:
# 0 = Chain is valid (all checks passed)
# 1 = Chain is invalid (one or more checks failed)
# 2 = Usage error (missing file, invalid JSON, etc.)
# ANSI color codes
GREEN="\033[32m"
RED="\033[31m"
RESET="\033[0m"
BOLD="\033[1m"
success() { echo -e "${GREEN}✓${RESET} $1"; }
failure() { echo -e "${RED}✗${RESET} $1"; }
# --- Dependency Checks -------------------------------------------------------
check_deps() {
local missing=()
if ! command -v jq &>/dev/null; then
missing+=("jq")
fi
if ! command -v sha256sum &>/dev/null && ! command -v shasum &>/dev/null; then
missing+=("sha256sum or shasum")
fi
if ! command -v bc &>/dev/null; then
missing+=("bc")
fi
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Missing required dependencies: ${missing[*]}"
echo ""
echo "Install instructions:"
echo " macOS (Homebrew): brew install ${missing[*]}"
echo " Ubuntu/Debian: sudo apt-get install ${missing[*]}"
echo " RHEL/Fedora: sudo yum install ${missing[*]}"
exit 2
fi
}
check_deps
# SHA-256 wrapper (Linux vs macOS)
sha256_hash() {
if command -v sha256sum &>/dev/null; then
printf '%s' "$1" | sha256sum | cut -d' ' -f1
else
printf '%s' "$1" | shasum -a 256 | cut -d' ' -f1
fi
}
# --- Input Validation --------------------------------------------------------
if [[ $# -lt 1 ]]; then
echo "Dalanga Transparency Chain Verification Tool"
echo ""
echo "Usage: bash $(basename "$0") <path-to-export.json>"
echo ""
echo "Verifies the cryptographic integrity of a chain export file."
echo "For draw events, re-derives the winner to confirm fairness."
exit 2
fi
FILE="$1"
if [[ ! -f "$FILE" ]]; then
failure "File not found: $FILE"
exit 2
fi
if ! jq empty "$FILE" 2>/dev/null; then
failure "Invalid JSON in file: $FILE"
exit 2
fi
if ! jq -e '.blocks | type == "array"' "$FILE" >/dev/null 2>&1; then
failure 'Invalid export format: missing "blocks" array'
exit 2
fi
# --- Chain Verification ------------------------------------------------------
BLOCK_COUNT=$(jq '.blocks | length' "$FILE")
ERRORS=0
echo -e "${BOLD}Dalanga Transparency Chain Verifier${RESET}"
CHAIN=$(jq -r '.chain // empty' "$FILE")
[[ -n "$CHAIN" ]] && echo "Chain: $CHAIN"
EXPORTED_AT=$(jq -r '.exported_at // empty' "$FILE")
[[ -n "$EXPORTED_AT" ]] && echo "Exported: $EXPORTED_AT"
echo "Blocks: $BLOCK_COUNT"
echo ""
if [[ "$BLOCK_COUNT" -eq 0 ]]; then
success "Chain is empty (0 blocks)"
exit 0
fi
# Chain integrity checks
PREV_HASH=""
for (( i=0; i<BLOCK_COUNT; i++ )); do
BLOCK=$(jq -c ".blocks[$i]" "$FILE")
SEQ=$(echo "$BLOCK" | jq -r '.sequence')
BLOCK_PREV_HASH=$(echo "$BLOCK" | jq -r '.previous_hash // "null"')
BLOCK_HASH=$(echo "$BLOCK" | jq -r '.hash')
HASH_INPUT=$(echo "$BLOCK" | jq -r '.hash_input')
EVENT_TYPE=$(echo "$BLOCK" | jq -r '.event_type // "unknown"')
# Check consecutive sequences
if [[ "$SEQ" != "$i" ]]; then
failure "Block $i: expected sequence $i, got $SEQ"
ERRORS=$((ERRORS + 1))
continue
fi
# Genesis block validation
if [[ "$i" -eq 0 && "$BLOCK_PREV_HASH" != "null" ]]; then
failure "Block 0: genesis block must have previous_hash = null"
ERRORS=$((ERRORS + 1))
fi
# Hash linking
if [[ "$i" -gt 0 && "$BLOCK_PREV_HASH" != "$PREV_HASH" ]]; then
failure "Block $i: previous_hash does not match preceding block's hash"
ERRORS=$((ERRORS + 1))
fi
# Hash recomputation
RECOMPUTED_HASH=$(sha256_hash "$HASH_INPUT")
if [[ "$BLOCK_HASH" != "$RECOMPUTED_HASH" ]]; then
failure "Block $i: hash does not match recomputed hash from hash_input"
ERRORS=$((ERRORS + 1))
else
success "Block $i: hash valid ($EVENT_TYPE)"
fi
PREV_HASH="$BLOCK_HASH"
done
# --- Payload Hash Verification -----------------------------------------------
HAS_PAYLOAD_HASHES=$(jq '[.blocks[] | has("payload_hash")] | any' "$FILE")
if [[ "$HAS_PAYLOAD_HASHES" == "true" ]]; then
echo ""
echo -e "${BOLD}Payload Hash Verification${RESET}"
PAYLOAD_CHECKED=0
PAYLOAD_SKIPPED=0
for (( i=0; i<BLOCK_COUNT; i++ )); do
BLOCK=$(jq -c ".blocks[$i]" "$FILE")
HAS_PH=$(echo "$BLOCK" | jq 'has("payload_hash")')
[[ "$HAS_PH" != "true" ]] && continue
HAS_PAYLOAD=$(echo "$BLOCK" | jq 'has("payload")')
if [[ "$HAS_PAYLOAD" != "true" ]]; then
PAYLOAD_SKIPPED=$((PAYLOAD_SKIPPED + 1))
continue
fi
PAYLOAD_CHECKED=$((PAYLOAD_CHECKED + 1))
PAYLOAD_HASH=$(echo "$BLOCK" | jq -r '.payload_hash')
PAYLOAD_JSON=$(echo "$BLOCK" | jq -cS '.payload')
RECOMPUTED_PH=$(sha256_hash "$PAYLOAD_JSON")
BLOCK_SEQ=$(echo "$BLOCK" | jq -r '.sequence')
BLOCK_ET=$(echo "$BLOCK" | jq -r '.event_type')
if [[ "$PAYLOAD_HASH" != "$RECOMPUTED_PH" ]]; then
failure "Block $BLOCK_SEQ: payload_hash does not match recomputed hash from payload"
ERRORS=$((ERRORS + 1))
else
success "Block $BLOCK_SEQ: payload hash valid ($BLOCK_ET)"
fi
done
if [[ "$PAYLOAD_SKIPPED" -gt 0 ]]; then
echo "(Skipped $PAYLOAD_SKIPPED block(s) without payload data — blocks-only export)"
fi
echo "Payload hashes checked: $PAYLOAD_CHECKED, skipped: $PAYLOAD_SKIPPED"
fi
# --- Draw Event Verification -------------------------------------------------
# Collect committed blocks
COMMITTED_BLOCKS=$(jq -c '[.blocks[] | select(.event_type == "draw.committed")]' "$FILE")
for (( i=0; i<BLOCK_COUNT; i++ )); do
BLOCK=$(jq -c ".blocks[$i]" "$FILE")
EVENT_TYPE=$(echo "$BLOCK" | jq -r '.event_type // empty')
[[ "$EVENT_TYPE" != "draw.revealed" ]] && continue
BLOCK_SEQ=$(echo "$BLOCK" | jq -r '.sequence')
ALG_VERSION=$(echo "$BLOCK" | jq -r '.event_data.algorithm_version // empty')
if [[ -z "$ALG_VERSION" ]]; then
failure "Block $BLOCK_SEQ: draw.revealed missing algorithm_version"
ERRORS=$((ERRORS + 1))
continue
fi
if [[ "$ALG_VERSION" != "sha256-modulo-v1" ]]; then
failure "Block $BLOCK_SEQ: unknown algorithm version '$ALG_VERSION'"
ERRORS=$((ERRORS + 1))
continue
fi
# Find matching draw.committed block with eligible_tickets_snapshot
COMMITTED=$(echo "$COMMITTED_BLOCKS" | jq -c '[.[] | select(.event_data.eligible_tickets_snapshot != null)] | last')
if [[ "$COMMITTED" == "null" || -z "$COMMITTED" ]]; then
failure "Block $BLOCK_SEQ: draw.revealed but no matching draw.committed found"
ERRORS=$((ERRORS + 1))
continue
fi
SEED=$(echo "$BLOCK" | jq -r '.event_data.seed')
ELIGIBLE_TICKETS=$(echo "$COMMITTED" | jq -c '.event_data.eligible_tickets_snapshot')
TOTAL_ELIGIBLE=$(echo "$COMMITTED" | jq '.event_data.eligible_tickets_snapshot | length')
RECORDED_WINNER_INDEX=$(echo "$BLOCK" | jq -r '.event_data.winner_index')
RECORDED_WINNER_TICKET=$(echo "$BLOCK" | jq -r '.event_data.winner_ticket_number')
# Re-derive winner using sha256-modulo-v1
DRAW_HASH=$(printf '%s' "$SEED" | sha256sum 2>/dev/null | cut -d' ' -f1 || printf '%s' "$SEED" | shasum -a 256 | cut -d' ' -f1)
HEX_PREFIX="${DRAW_HASH:0:12}"
HEX_UPPER=$(echo "$HEX_PREFIX" | tr 'a-f' 'A-F')
WINNER_INDEX=$(echo "ibase=16; $HEX_UPPER % $TOTAL_ELIGIBLE" | bc)
WINNER_TICKET=$(echo "$ELIGIBLE_TICKETS" | jq -r ".[$WINNER_INDEX]")
if [[ "$WINNER_INDEX" != "$RECORDED_WINNER_INDEX" ]]; then
failure "Block $BLOCK_SEQ: draw winner_index mismatch (expected $WINNER_INDEX, got $RECORDED_WINNER_INDEX)"
ERRORS=$((ERRORS + 1))
elif [[ "$WINNER_TICKET" != "$RECORDED_WINNER_TICKET" ]]; then
failure "Block $BLOCK_SEQ: draw winner_ticket_number mismatch (expected $WINNER_TICKET, got $RECORDED_WINNER_TICKET)"
ERRORS=$((ERRORS + 1))
else
success "Block $BLOCK_SEQ: draw winner independently verified (sha256-modulo-v1)"
fi
# Verify commitment hash
COMMITTED_SEQ=$(echo "$COMMITTED" | jq -r '.sequence')
COMMITMENT_HASH=$(echo "$COMMITTED" | jq -r '.event_data.commitment_hash // empty')
if [[ -n "$COMMITMENT_HASH" ]]; then
COMMITMENT_INPUT="${SEED}${ELIGIBLE_TICKETS}"
RECOMPUTED_COMMITMENT=$(sha256_hash "$COMMITMENT_INPUT")
if [[ "$COMMITMENT_HASH" != "$RECOMPUTED_COMMITMENT" ]]; then
failure "Block $COMMITTED_SEQ: commitment hash mismatch"
ERRORS=$((ERRORS + 1))
else
success "Block $COMMITTED_SEQ: commitment hash verified"
fi
fi
done
# --- Summary -----------------------------------------------------------------
echo ""
if [[ "$ERRORS" -eq 0 ]]; then
echo -e "${GREEN}Chain valid: $BLOCK_COUNT blocks verified, 0 errors${RESET}"
exit 0
fi
echo -e "${RED}Chain INVALID: $BLOCK_COUNT blocks verified, $ERRORS error(s)${RESET}"
exit 1
bash verify-chain.sh export.json
download
Baixar
#!/usr/bin/env node
/**
* Dalanga Transparency Chain Verification Tool
*
* A standalone, zero-dependency Node.js script that verifies the integrity of a
* downloaded Dalanga system chain export file.
*
* Requirements: Node.js 14+ (standard library only).
*
* Usage:
* node verify-chain.js <path-to-export.json>
*
* Examples:
* node verify-chain.js system-chain-export.json
* node verify-chain.js /tmp/chain-export.json
*
* What it verifies:
* 1. Consecutive sequence numbers starting from 0
* 2. Hash linking: each block's previous_hash matches the preceding block's hash
* 3. Hash recomputation: re-hashes each block's hash_input and compares to stored hash
* 4. Genesis block validation: sequence 0 must have previous_hash = null
* 5. Payload hash verification: re-hashes payload and compares to stored payload_hash
* 6. Draw fairness: for draw.revealed events, re-derives the winner using the
* algorithm_version from event_data and confirms the recorded winner matches
*
* Exit codes:
* 0 = Chain is valid (all checks passed)
* 1 = Chain is invalid (one or more checks failed)
* 2 = Usage error (missing file, invalid JSON, etc.)
*/
const crypto = require('crypto');
const fs = require('fs');
const process = require('process');
// ANSI color codes
const GREEN = '\x1b[32m';
const RED = '\x1b[31m';
const RESET = '\x1b[0m';
const BOLD = '\x1b[1m';
function success(message) {
console.log(`${GREEN}\u2713${RESET} ${message}`);
}
function failure(message) {
console.log(`${RED}\u2717${RESET} ${message}`);
}
function sha256(input) {
return crypto.createHash('sha256').update(input, 'utf8').digest('hex');
}
function canonicalJsonEncode(data) {
if (data === null || typeof data !== 'object') {
return JSON.stringify(data);
}
if (Array.isArray(data)) {
return '[' + data.map(item => canonicalJsonEncode(item)).join(',') + ']';
}
const sortedKeys = Object.keys(data).sort();
const pairs = sortedKeys.map(key => JSON.stringify(key) + ':' + canonicalJsonEncode(data[key]));
return '{' + pairs.join(',') + '}';
}
// --- Input Validation -------------------------------------------------------
if (process.argv.length < 3) {
console.log('Dalanga Transparency Chain Verification Tool');
console.log('');
console.log(`Usage: node ${process.argv[1]} <path-to-export.json>`);
console.log('');
console.log('Verifies the cryptographic integrity of a chain export file.');
console.log('For draw events, re-derives the winner to confirm fairness.');
process.exit(2);
}
const filePath = process.argv[2];
if (!fs.existsSync(filePath)) {
failure(`File not found: ${filePath}`);
process.exit(2);
}
let content;
try {
content = fs.readFileSync(filePath, 'utf8');
} catch (e) {
failure(`Cannot read file: ${e.message}`);
process.exit(2);
}
let data;
try {
data = JSON.parse(content);
} catch (e) {
failure(`Invalid JSON: ${e.message}`);
process.exit(2);
}
if (!Array.isArray(data.blocks)) {
failure('Invalid export format: missing "blocks" array');
process.exit(2);
}
// --- Chain Verification -----------------------------------------------------
const blocks = data.blocks;
const blockCount = blocks.length;
let errors = 0;
console.log(`${BOLD}Dalanga Transparency Chain Verifier${RESET}`);
if (data.chain) {
console.log(`Chain: ${data.chain}`);
}
if (data.exported_at) {
console.log(`Exported: ${data.exported_at}`);
}
console.log(`Blocks: ${blockCount}`);
console.log('');
if (blockCount === 0) {
success('Chain is empty (0 blocks)');
process.exit(0);
}
// Chain integrity checks
for (let i = 0; i < blockCount; i++) {
const block = blocks[i];
// Check consecutive sequences
if (block.sequence !== i) {
failure(`Block ${i}: expected sequence ${i}, got ${block.sequence}`);
errors++;
continue;
}
// Genesis block validation
if (i === 0 && block.previous_hash !== null) {
failure('Block 0: genesis block must have previous_hash = null');
errors++;
}
// Hash linking
if (i > 0 && block.previous_hash !== blocks[i - 1].hash) {
failure(`Block ${i}: previous_hash does not match preceding block's hash`);
errors++;
}
// Hash recomputation
const recomputedHash = sha256(block.hash_input);
if (block.hash !== recomputedHash) {
failure(`Block ${i}: hash does not match recomputed hash from hash_input`);
errors++;
} else {
const eventType = block.event_type || 'unknown';
success(`Block ${i}: hash valid (${eventType})`);
}
}
// --- Payload Hash Verification ----------------------------------------------
const hasPayloadHashes = blocks.some(b => 'payload_hash' in b);
if (hasPayloadHashes) {
console.log('');
console.log(`${BOLD}Payload Hash Verification${RESET}`);
let payloadChecked = 0;
let payloadSkipped = 0;
for (const block of blocks) {
if (!('payload_hash' in block)) {
continue;
}
if (!('payload' in block)) {
payloadSkipped++;
continue;
}
payloadChecked++;
const payloadJson = canonicalJsonEncode(block.payload);
const recomputedPayloadHash = sha256(payloadJson);
if (block.payload_hash !== recomputedPayloadHash) {
failure(`Block ${block.sequence}: payload_hash does not match recomputed hash from payload`);
errors++;
} else {
success(`Block ${block.sequence}: payload hash valid (${block.event_type})`);
}
}
if (payloadSkipped > 0) {
console.log(`(Skipped ${payloadSkipped} block(s) without payload data \u2014 blocks-only export)`);
}
console.log(`Payload hashes checked: ${payloadChecked}, skipped: ${payloadSkipped}`);
}
// --- Draw Event Verification ------------------------------------------------
const committedBlocks = blocks.filter(b => b.event_type === 'draw.committed');
for (const block of blocks) {
if (block.event_type !== 'draw.revealed') {
continue;
}
const eventData = block.event_data || {};
const algorithmVersion = eventData.algorithm_version;
if (!algorithmVersion) {
failure(`Block ${block.sequence}: draw.revealed missing algorithm_version`);
errors++;
continue;
}
if (algorithmVersion !== 'sha256-modulo-v1') {
failure(`Block ${block.sequence}: unknown algorithm version '${algorithmVersion}'`);
errors++;
continue;
}
// Find matching draw.committed block
let committed = null;
for (const cb of committedBlocks) {
const cbData = cb.event_data || {};
if (cbData.eligible_tickets_snapshot) {
committed = cb;
}
}
if (!committed) {
failure(`Block ${block.sequence}: draw.revealed but no matching draw.committed found`);
errors++;
continue;
}
const committedData = committed.event_data;
const seed = eventData.seed;
const eligibleTickets = committedData.eligible_tickets_snapshot;
const totalEligible = eligibleTickets.length;
// Re-derive winner using sha256-modulo-v1
const drawHash = sha256(seed);
const hexPrefix = drawHash.substring(0, 12);
const winnerIndex = parseInt(hexPrefix, 16) % totalEligible;
const winnerTicketNumber = eligibleTickets[winnerIndex];
if (winnerIndex !== eventData.winner_index) {
failure(`Block ${block.sequence}: draw winner_index mismatch (expected ${winnerIndex}, got ${eventData.winner_index})`);
errors++;
} else if (winnerTicketNumber !== eventData.winner_ticket_number) {
failure(`Block ${block.sequence}: draw winner_ticket_number mismatch (expected ${winnerTicketNumber}, got ${eventData.winner_ticket_number})`);
errors++;
} else {
success(`Block ${block.sequence}: draw winner independently verified (sha256-modulo-v1)`);
}
// Verify commitment hash
const commitmentHash = committedData.commitment_hash;
if (commitmentHash) {
const recomputedCommitment = sha256(seed + JSON.stringify(eligibleTickets));
if (commitmentHash !== recomputedCommitment) {
failure(`Block ${committed.sequence}: commitment hash mismatch`);
errors++;
} else {
success(`Block ${committed.sequence}: commitment hash verified`);
}
}
}
// --- Summary ----------------------------------------------------------------
console.log('');
if (errors === 0) {
console.log(`${GREEN}Chain valid: ${blockCount} blocks verified, 0 errors${RESET}`);
process.exit(0);
}
console.log(`${RED}Chain INVALID: ${blockCount} blocks verified, ${errors} error(s)${RESET}`);
process.exit(1);
node verify-chain.js export.json
download
Baixar
O estado da cadeia é periodicamente ancorado em serviços externos, criando provas de timestamp de terceiros.
Todo registro na blockchain usa hooks pós-commit — blocos são criados somente após a transação de negócio primária ser bem-sucedida. Falhas de registro nunca afetam operações primárias.
Todos os dados da blockchain estão disponíveis através de endpoints públicos e sem autenticação — nenhuma conta ou chave de API é necessária. Os endpoints são limitados a 10 requisições por minuto por IP para acesso justo.
| Método | Endpoint | Descrição |
|---|---|---|
GET |
/api/system-chain/export | Exportação completa da cadeia global. Contém todos os blocos registrados na plataforma. Use ?after={sequence} para downloads incrementais — a primeira chamada retorna a cadeia completa, chamadas subsequentes retornam apenas blocos novos. |
GET |
/api/system-chain/verify-data | Dados de verificação leves para a cadeia global. Retorna apenas hashes e números de sequência — ideal para verificações automatizadas de integridade. Suporta ?after={sequence} para polling incremental. |
GET |
/api/system-chain/verify-tools | Manifesto das ferramentas de verificação. Lista todos os scripts de verificação disponíveis com checksums, tamanhos e URLs de download. |
GET |
/api/system-chain/verify-tool/{lang} | Download de um script de verificação por idioma. Valores aceitos: php, python, bash, node. |
Exemplo:
curl -s https://dalanga.com/api/system-chain/export -o chain.json
# Incremental download (only new blocks since sequence 42):
curl -s "https://dalanga.com/api/system-chain/export?after=42" -o chain-update.json
Esses endpoints são projetados para acesso contínuo e programático — construa scripts de monitoramento automatizado, integre com ferramentas de auditoria de terceiros ou desenvolva seu próprio pipeline de verificação. Esta é a base para um futuro ecossistema aberto onde qualquer pessoa pode verificar independentemente cada rifa na Dalanga.