chitfund/luckychit/lib/shared/widgets/draw_verification_widget.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,
),
],
),
);
}
}