424 lines
13 KiB
Dart
424 lines
13 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'dart:convert';
|
|
import 'dart:math' as math;
|
|
import 'package:crypto/crypto.dart';
|
|
|
|
class DrawVerificationWidget extends StatefulWidget {
|
|
final String serverSeed;
|
|
final String serverSeedHash;
|
|
final String clientSeed;
|
|
final int nonce;
|
|
final List<Map<String, dynamic>> eligibleMembers;
|
|
final String winnerId;
|
|
|
|
const DrawVerificationWidget({
|
|
super.key,
|
|
required this.serverSeed,
|
|
required this.serverSeedHash,
|
|
required this.clientSeed,
|
|
required this.nonce,
|
|
required this.eligibleMembers,
|
|
required this.winnerId,
|
|
});
|
|
|
|
@override
|
|
State<DrawVerificationWidget> createState() => _DrawVerificationWidgetState();
|
|
}
|
|
|
|
class _DrawVerificationWidgetState extends State<DrawVerificationWidget> {
|
|
bool _isVerifying = false;
|
|
bool _isVerified = false;
|
|
String? _calculatedWinnerId;
|
|
String? _resultHash;
|
|
String? _verificationError;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_verifyDraw();
|
|
}
|
|
|
|
void _verifyDraw() async {
|
|
setState(() {
|
|
_isVerifying = true;
|
|
_verificationError = null;
|
|
});
|
|
|
|
try {
|
|
// Step 1: Verify server seed hash
|
|
final calculatedServerSeedHash = _generateServerSeedHash(widget.serverSeed);
|
|
if (calculatedServerSeedHash != widget.serverSeedHash) {
|
|
throw Exception('Server seed hash verification failed');
|
|
}
|
|
|
|
// Step 2: Generate result hash
|
|
_resultHash = _generateResultHash();
|
|
|
|
// Step 3: Calculate winner
|
|
_calculatedWinnerId = _calculateWinner();
|
|
|
|
// Step 4: Verify winner matches
|
|
if (_calculatedWinnerId == widget.winnerId) {
|
|
setState(() {
|
|
_isVerified = true;
|
|
_isVerifying = false;
|
|
});
|
|
} else {
|
|
throw Exception('Winner verification failed');
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
_verificationError = e.toString();
|
|
_isVerifying = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
String _generateServerSeedHash(String serverSeed) {
|
|
final bytes = utf8.encode(serverSeed);
|
|
final digest = sha256.convert(bytes);
|
|
return digest.toString();
|
|
}
|
|
|
|
String _generateResultHash() {
|
|
final combinedSeed = '${widget.serverSeed}:${widget.clientSeed}:${widget.nonce}';
|
|
final bytes = utf8.encode(combinedSeed);
|
|
final digest = sha256.convert(bytes);
|
|
return digest.toString();
|
|
}
|
|
|
|
String _calculateWinner() {
|
|
final combinedSeed = '${widget.serverSeed}:${widget.clientSeed}:${widget.nonce}';
|
|
final hash = _generateHash(combinedSeed);
|
|
final randomValue = _hashToNumber(hash);
|
|
final selectedIndex = randomValue % widget.eligibleMembers.length;
|
|
return widget.eligibleMembers[selectedIndex]['id'];
|
|
}
|
|
|
|
String _generateHash(String input) {
|
|
int hash = 0;
|
|
for (int i = 0; i < input.length; i++) {
|
|
hash = ((hash << 5) - hash + input.codeUnitAt(i)) & 0xffffffff;
|
|
}
|
|
return hash.abs().toString();
|
|
}
|
|
|
|
int _hashToNumber(String hash) {
|
|
return int.parse(hash.substring(0, math.min(10, hash.length)));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: EdgeInsets.all(20.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(
|
|
color: _isVerified
|
|
? Colors.green.shade300
|
|
: _verificationError != null
|
|
? Colors.red.shade300
|
|
: Colors.grey.shade300,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10.r,
|
|
offset: Offset(0, 2.h),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
_isVerifying
|
|
? Icons.hourglass_empty
|
|
: _isVerified
|
|
? Icons.verified
|
|
: Icons.error,
|
|
color: _isVerifying
|
|
? Colors.orange.shade600
|
|
: _isVerified
|
|
? Colors.green.shade600
|
|
: Colors.red.shade600,
|
|
size: 24.w,
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Text(
|
|
'Draw Verification',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
if (_isVerifying)
|
|
SizedBox(
|
|
width: 20.w,
|
|
height: 20.w,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.orange.shade600),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Verification Status
|
|
Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: _isVerifying
|
|
? Colors.orange.shade50
|
|
: _isVerified
|
|
? Colors.green.shade50
|
|
: Colors.red.shade50,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(
|
|
color: _isVerifying
|
|
? Colors.orange.shade200
|
|
: _isVerified
|
|
? Colors.green.shade200
|
|
: Colors.red.shade200,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
_isVerifying
|
|
? Icons.hourglass_empty
|
|
: _isVerified
|
|
? Icons.check_circle
|
|
: Icons.cancel,
|
|
color: _isVerifying
|
|
? Colors.orange.shade600
|
|
: _isVerified
|
|
? Colors.green.shade600
|
|
: Colors.red.shade600,
|
|
size: 20.w,
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
_isVerifying
|
|
? 'Verifying draw fairness...'
|
|
: _isVerified
|
|
? 'Draw verified as fair and transparent'
|
|
: 'Verification failed: $_verificationError',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: _isVerifying
|
|
? Colors.orange.shade700
|
|
: _isVerified
|
|
? Colors.green.shade700
|
|
: Colors.red.shade700,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Verification Details
|
|
if (_isVerified || _verificationError != null) ...[
|
|
Text(
|
|
'Verification Details',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
_buildVerificationDetail('Server Seed Hash', widget.serverSeedHash),
|
|
_buildVerificationDetail('Client Seed', widget.clientSeed),
|
|
_buildVerificationDetail('Nonce', widget.nonce.toString()),
|
|
if (_resultHash != null)
|
|
_buildVerificationDetail('Result Hash', _resultHash!),
|
|
if (_calculatedWinnerId != null)
|
|
_buildVerificationDetail('Calculated Winner ID', _calculatedWinnerId!),
|
|
_buildVerificationDetail('Declared Winner ID', widget.winnerId),
|
|
_buildVerificationDetail('Eligible Members', '${widget.eligibleMembers.length}'),
|
|
],
|
|
|
|
// Verification Steps
|
|
if (_isVerified) ...[
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'Verification Steps',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
_buildVerificationStep(
|
|
'1',
|
|
'Server Seed Hash Verified',
|
|
'The provided server seed matches its hash',
|
|
true,
|
|
),
|
|
_buildVerificationStep(
|
|
'2',
|
|
'Result Hash Generated',
|
|
'Combined seeds and nonce to create result hash',
|
|
true,
|
|
),
|
|
_buildVerificationStep(
|
|
'3',
|
|
'Winner Calculated',
|
|
'Used result hash to determine winner index',
|
|
true,
|
|
),
|
|
_buildVerificationStep(
|
|
'4',
|
|
'Winner Verified',
|
|
'Calculated winner matches declared winner',
|
|
true,
|
|
),
|
|
],
|
|
|
|
// Retry Button
|
|
if (_verificationError != null) ...[
|
|
SizedBox(height: 16.h),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _verifyDraw,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Retry Verification',
|
|
style: TextStyle(fontSize: 14.sp),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildVerificationDetail(String label, String value) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 8.h),
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 3,
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade800,
|
|
fontFamily: 'monospace',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildVerificationStep(String step, String title, String description, bool isSuccess) {
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 8.h),
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: isSuccess ? Colors.green.shade50 : Colors.red.shade50,
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
border: Border.all(
|
|
color: isSuccess ? Colors.green.shade200 : Colors.red.shade200,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 24.w,
|
|
height: 24.w,
|
|
decoration: BoxDecoration(
|
|
color: isSuccess ? Colors.green.shade100 : Colors.red.shade100,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
step,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isSuccess ? Colors.green.shade700 : Colors.red.shade700,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isSuccess ? Colors.green.shade800 : Colors.red.shade800,
|
|
),
|
|
),
|
|
Text(
|
|
description,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: isSuccess ? Colors.green.shade700 : Colors.red.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
isSuccess ? Icons.check_circle : Icons.cancel,
|
|
color: isSuccess ? Colors.green.shade600 : Colors.red.shade600,
|
|
size: 20.w,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|