feat: added wallet service
This commit is contained in:
49
server/Makefile
Normal file
49
server/Makefile
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Go parameters
|
||||||
|
GOCMD=go
|
||||||
|
GOBUILD=$(GOCMD) build
|
||||||
|
GOCLEAN=$(GOCMD) clean
|
||||||
|
GOTEST=$(GOCMD) test
|
||||||
|
GOGET=$(GOCMD) get
|
||||||
|
GOMOD=$(GOCMD) mod
|
||||||
|
GOFMT=gofmt
|
||||||
|
GOLINT=golangci-lint
|
||||||
|
|
||||||
|
# Binary names
|
||||||
|
BINARY_NAME=ccoin
|
||||||
|
BINARY_LINUX=$(BINARY_NAME)-linux-x64
|
||||||
|
BINARY_MAC=$(BINARY_NAME)-macos-arm64
|
||||||
|
BINARY_WINDOWS=$(BINARY_NAME)-windows-x64.exe
|
||||||
|
|
||||||
|
# Build directory
|
||||||
|
BUILD_DIR=build
|
||||||
|
|
||||||
|
# Version info (can be overridden)
|
||||||
|
VERSION?=1.0.0
|
||||||
|
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||||
|
COMMIT?=$(shell git rev-parse HEAD)
|
||||||
|
|
||||||
|
# Linker flags to embed version info
|
||||||
|
LDFLAGS=-ldflags "-X ccoin/config/server.version=$(VERSION) -X ccoin/config/server.buildTime=$(BUILD_TIME) -X ccoin/config/server.commit=$(COMMIT) -s -w" -trimpath
|
||||||
|
|
||||||
|
.PHONY: all build clean test coverage deps fmt lint vet help
|
||||||
|
.PHONY: build-linux build-windows build-darwin run dev
|
||||||
|
.PHONY: docker-build install uninstall
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
all: clean deps fmt vet test build
|
||||||
|
|
||||||
|
# Build the binary
|
||||||
|
build:
|
||||||
|
$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) -v
|
||||||
|
|
||||||
|
# Build for Linux
|
||||||
|
build-linux:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_LINUX) -v
|
||||||
|
|
||||||
|
# Build for MacOS
|
||||||
|
build-macos:
|
||||||
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_MAC) -v
|
||||||
|
|
||||||
|
# Build for windows
|
||||||
|
build-windows:
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_WINDOWS) -v
|
||||||
@@ -8,10 +8,15 @@ import (
|
|||||||
"ccoin/utils"
|
"ccoin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var version = "dev"
|
||||||
|
var buildTime = time.Now().Format(time.RFC3339)
|
||||||
|
var commit = "none"
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
// App details
|
// App details
|
||||||
Version string
|
Version string
|
||||||
BuildTime string
|
BuildTime string
|
||||||
|
Commit string
|
||||||
BaseURL string
|
BaseURL string
|
||||||
|
|
||||||
// Server settings
|
// Server settings
|
||||||
@@ -66,9 +71,10 @@ func NewServerConfig() *ServerConfig {
|
|||||||
// Loads configuration from environment variables with defaults
|
// Loads configuration from environment variables with defaults
|
||||||
func (c *ServerConfig) loadConfig() {
|
func (c *ServerConfig) loadConfig() {
|
||||||
// App details (these would typically come from build-time variables)
|
// App details (these would typically come from build-time variables)
|
||||||
c.Version = utils.GetEnvOrDefault("VERSION", "dev")
|
c.Version = version
|
||||||
c.BuildTime = utils.GetEnvOrDefault("BUILD_TIME", time.Now().Format(time.RFC3339))
|
c.BuildTime = utils.GetEnvOrDefault("BUILD_TIME", buildTime)
|
||||||
c.BaseURL = utils.GetEnvOrDefault("BASE_URL", "http://localhost:8080")
|
c.BaseURL = utils.GetEnvOrDefault("BASE_URL", "http://localhost:8080")
|
||||||
|
c.Commit = commit
|
||||||
|
|
||||||
// Server settings
|
// Server settings
|
||||||
c.Host = utils.GetEnvOrDefault("SERVER_HOST", "0.0.0.0")
|
c.Host = utils.GetEnvOrDefault("SERVER_HOST", "0.0.0.0")
|
||||||
@@ -109,6 +115,7 @@ func (c *ServerConfig) logConfig() {
|
|||||||
c.logger.Info("Server configuration loaded:")
|
c.logger.Info("Server configuration loaded:")
|
||||||
c.logger.Info("Host", "value", c.Host)
|
c.logger.Info("Host", "value", c.Host)
|
||||||
c.logger.Info("Port", "value", c.Port)
|
c.logger.Info("Port", "value", c.Port)
|
||||||
|
c.logger.Info("Version", "value", c.Version)
|
||||||
c.logger.Info("Development mode", "value", c.DevelopmentMode)
|
c.logger.Info("Development mode", "value", c.DevelopmentMode)
|
||||||
c.logger.Info("Mining difficulty", "value", c.MiningDifficulty)
|
c.logger.Info("Mining difficulty", "value", c.MiningDifficulty)
|
||||||
c.logger.Info("Mining reward", "value", c.MiningReward)
|
c.logger.Info("Mining reward", "value", c.MiningReward)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ go 1.25.5
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.2
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Wallet struct {
|
||||||
|
Address string `db:"address" json:"address"`
|
||||||
|
Label *string `db:"label" json:"label,omitempty"`
|
||||||
|
PasswordHash string `db:"password_hash" json:"password_hash"`
|
||||||
|
Balance float64 `db:"balance" json:"balance"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||||
|
LastActivity *time.Time `db:"last_activity" json:"last_activity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateWalletRequest struct {
|
type CreateWalletRequest struct {
|
||||||
Label *string `json:"label,omitempty"`
|
Label *string `json:"label,omitempty"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|||||||
35
server/routes/wallet/routes.go
Normal file
35
server/routes/wallet/routes.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ccoin/models/wallet"
|
||||||
|
ws "ccoin/services/wallet"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sets up routes for wallet operations
|
||||||
|
func WalletRoutes(router *mux.Router, walletService *ws.WalletService) {
|
||||||
|
walletRouter := router.PathPrefix("/wallet").Subrouter()
|
||||||
|
|
||||||
|
// Create a new wallet
|
||||||
|
walletRouter.HandleFunc("/create", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req wallet.CreateWalletRequest
|
||||||
|
|
||||||
|
// Parse the request
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the wallet using the service
|
||||||
|
walletResponse, err := walletService.CreateWallet(req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to create wallet: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(walletResponse)
|
||||||
|
}).Methods("POST")
|
||||||
|
}
|
||||||
@@ -1,12 +1,35 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ccoin/config/database"
|
||||||
"ccoin/config/server"
|
"ccoin/config/server"
|
||||||
"ccoin/config/database"
|
"ccoin/routes/wallet"
|
||||||
|
ws "ccoin/services/wallet"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Initialize server
|
||||||
server.NewServerConfig()
|
server.NewServerConfig()
|
||||||
dc := database.NewDatabaseConfig()
|
|
||||||
dc.Init()
|
// Initialize database
|
||||||
|
dbConfig := database.NewDatabaseConfig()
|
||||||
|
if err := dbConfig.Init(); err != nil {
|
||||||
|
log.Fatal("Database initialization failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
walletService := ws.NewWalletService(dbConfig.GetDB())
|
||||||
|
|
||||||
|
// Set up router
|
||||||
|
r := mux.NewRouter()
|
||||||
|
routes.WalletRoutes(r, walletService)
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
log.Info("Starting server on :8080")
|
||||||
|
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||||
|
log.Fatal("Failed to start server:", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
219
server/services/wallet/service.go
Normal file
219
server/services/wallet/service.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ccoin/utils/crypto"
|
||||||
|
"database/sql"
|
||||||
|
"ccoin/models/wallet"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletService struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new instance of WalletService
|
||||||
|
func NewWalletService(db *sql.DB) *WalletService {
|
||||||
|
return &WalletService{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new wallet with optional label
|
||||||
|
func (ws *WalletService) CreateWallet(req wallet.CreateWalletRequest) (wallet.WalletResponse, error) {
|
||||||
|
address := crypto.GenerateWalletAddress()
|
||||||
|
timestamp := time.Now()
|
||||||
|
var passwordHash string
|
||||||
|
if req.Password != "" {
|
||||||
|
passwordHash = crypto.HashPassword(req.Password)
|
||||||
|
} else {
|
||||||
|
return wallet.WalletResponse{}, fmt.Errorf("password field is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ws.db.Exec(`
|
||||||
|
INSERT INTO wallets (address, balance, label, password_hash, created_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)`,
|
||||||
|
address, 0.0, req.Label, passwordHash, timestamp.Unix(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return wallet.WalletResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallet.WalletResponse{
|
||||||
|
Address: address,
|
||||||
|
Balance: 0.0,
|
||||||
|
Label: req.Label,
|
||||||
|
PasswordHash: passwordHash,
|
||||||
|
CreatedAt: timestamp.Unix(),
|
||||||
|
LastActivity: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets wallet by address
|
||||||
|
func (ws *WalletService) GetWallet(address string) (*wallet.WalletResponse, error) {
|
||||||
|
var w wallet.Wallet
|
||||||
|
row := ws.db.QueryRow(`
|
||||||
|
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||||
|
FROM wallets WHERE address = $1`, address)
|
||||||
|
|
||||||
|
err := row.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wallet.WalletResponse{
|
||||||
|
Address: w.Address,
|
||||||
|
Balance: w.Balance,
|
||||||
|
Label: w.Label,
|
||||||
|
PasswordHash: w.PasswordHash,
|
||||||
|
CreatedAt: w.CreatedAt.Unix(),
|
||||||
|
LastActivity: ptrTime(w.LastActivity),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets wallet balance
|
||||||
|
func (ws *WalletService) GetWalletBalance(address string) (wallet.BalanceResponse, error) {
|
||||||
|
var balance float64
|
||||||
|
err := ws.db.QueryRow(`SELECT balance FROM wallets WHERE address = $1`, address).Scan(&balance)
|
||||||
|
if err != nil {
|
||||||
|
return wallet.BalanceResponse{}, errors.New("wallet not found")
|
||||||
|
}
|
||||||
|
return wallet.BalanceResponse{
|
||||||
|
Address: address,
|
||||||
|
Balance: balance,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates wallet balance
|
||||||
|
func (ws *WalletService) UpdateBalance(address string, amount float64) (bool, error) {
|
||||||
|
_, err := ws.db.Exec(`UPDATE wallets SET balance = balance + $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets wallet balance to specific amount
|
||||||
|
func (ws *WalletService) SetBalance(address string, amount float64) (bool, error) {
|
||||||
|
_, err := ws.db.Exec(`UPDATE wallets SET balance = $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates wallet label
|
||||||
|
func (ws *WalletService) UpdateLabel(address string, req wallet.UpdateWalletRequest) (bool, error) {
|
||||||
|
result, err := ws.db.Exec(`UPDATE wallets SET label = $1 WHERE address = $2`, address, req.Label)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
rowsAffected, _ := result.RowsAffected()
|
||||||
|
return rowsAffected > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if wallet exists
|
||||||
|
func (ws *WalletService) WalletExists(address string) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets WHERE address = $1`, address).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets all wallets with pagination
|
||||||
|
func (ws *WalletService) GetAllWallets(limit, offset int) ([]wallet.WalletResponse, error) {
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||||
|
FROM wallets ORDER BY created_at DESC LIMIT %d OFFSET %d`, limit, offset)
|
||||||
|
|
||||||
|
rows, err := ws.db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var wallets []wallet.WalletResponse
|
||||||
|
for rows.Next() {
|
||||||
|
var w wallet.Wallet
|
||||||
|
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash,
|
||||||
|
&w.CreatedAt, &w.LastActivity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets = append(wallets, wallet.WalletResponse{
|
||||||
|
Address: w.Address,
|
||||||
|
Balance: w.Balance,
|
||||||
|
Label: w.Label,
|
||||||
|
PasswordHash: w.PasswordHash,
|
||||||
|
CreatedAt: w.CreatedAt.Unix(),
|
||||||
|
LastActivity: ptrTime(w.LastActivity),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets total number of wallets
|
||||||
|
func (ws *WalletService) GetTotalWalletCount() (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets`).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets wallet with balance greater than specified amount
|
||||||
|
func (ws *WalletService) GetWalletsWithBalance(minBalance float64) ([]wallet.WalletResponse, error) {
|
||||||
|
rows, err := ws.db.Query(`SELECT address, balance, label, password_hash, created_at, last_activity FROM wallets WHERE balance > $1 ORDER BY balance DESC`, minBalance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var wallets []wallet.WalletResponse
|
||||||
|
for rows.Next() {
|
||||||
|
var w wallet.Wallet
|
||||||
|
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets = append(wallets, wallet.WalletResponse{
|
||||||
|
Address: w.Address,
|
||||||
|
Balance: w.Balance,
|
||||||
|
Label: w.Label,
|
||||||
|
PasswordHash: w.PasswordHash,
|
||||||
|
CreatedAt: w.CreatedAt.Unix(),
|
||||||
|
LastActivity: ptrTime(w.LastActivity),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return wallets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates last activity timestamp
|
||||||
|
func (ws *WalletService) UpdateLastActivity(address string) (bool, error) {
|
||||||
|
_, err := ws.db.Exec(`UPDATE wallets SET last_activity = $1 WHERE address = $2`, time.Now().Unix(), address)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets total supply across all wallets
|
||||||
|
func (ws *WalletService) GetTotalSupply() (float64, error) {
|
||||||
|
var totalSupply float64
|
||||||
|
err := ws.db.QueryRow(`SELECT SUM(balance) FROM wallets`).Scan(&totalSupply)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return totalSupply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert *time.Time to *int64 for JSON response
|
||||||
|
func ptrTime(t *time.Time) *int64 {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
unixTime := t.Unix()
|
||||||
|
return &unixTime
|
||||||
|
}
|
||||||
@@ -1,2 +1,131 @@
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"ccoin/utils"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var words = []string{
|
||||||
|
"phoenix", "dragon", "tiger", "eagle", "wolf", "lion", "bear", "shark",
|
||||||
|
"falcon", "raven", "cobra", "viper", "panther", "jaguar", "leopard", "cheetah",
|
||||||
|
"thunder", "lightning", "storm", "blizzard", "tornado", "hurricane", "cyclone", "tempest",
|
||||||
|
"crystal", "diamond", "emerald", "ruby", "sapphire", "topaz", "amethyst", "opal",
|
||||||
|
"shadow", "ghost", "phantom", "spirit", "wraith", "specter", "demon", "angel",
|
||||||
|
"fire", "ice", "earth", "wind", "water", "metal", "wood", "void",
|
||||||
|
"star", "moon", "sun", "comet", "meteor", "galaxy", "nebula", "cosmos",
|
||||||
|
"blade", "sword", "arrow", "spear", "shield", "armor", "crown", "throne",
|
||||||
|
"mountain", "ocean", "forest", "desert", "valley", "river", "lake", "cave",
|
||||||
|
"knight", "warrior", "mage", "archer", "rogue", "paladin", "wizard", "sage",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a wallet address in format: random_word:random_6_digits
|
||||||
|
func GenerateWalletAddress() string {
|
||||||
|
randomWord := words[utils.RandInt(0, len(words))]
|
||||||
|
randomDigits := fmt.Sprintf("%06d", utils.RandInt(0, 1000000))
|
||||||
|
return fmt.Sprintf("%s:%s", randomWord, randomDigits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates if an address follows the correct format
|
||||||
|
func IsValidAddress(address string) bool {
|
||||||
|
parts := strings.Split(address, ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
word := parts[0]
|
||||||
|
digits := parts[1]
|
||||||
|
|
||||||
|
return len(word) > 0 &&
|
||||||
|
utils.IsAlpha(word) &&
|
||||||
|
len(digits) == 6 &&
|
||||||
|
utils.IsDigit(digits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates SHA-256 hash of input string
|
||||||
|
func SHA256(input string) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(input))
|
||||||
|
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashes password
|
||||||
|
func HashPassword(password string) string {
|
||||||
|
return SHA256(fmt.Sprintf("ccoin_password_%s", password))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a transaction hash
|
||||||
|
func GenerateTransactionHash(fromAddress *string, toAddress string, amount float64, timestamp int64, nonce int64) string {
|
||||||
|
from := "genesis"
|
||||||
|
if fromAddress != nil {
|
||||||
|
from = *fromAddress
|
||||||
|
}
|
||||||
|
input := fmt.Sprintf("%s:%s:%f:%d:%d", from, toAddress, amount, timestamp, nonce)
|
||||||
|
return SHA256(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a block hash
|
||||||
|
func GenerateBlockHash(previousHash string, merkleRoot string, timestamp int64, difficulty int, nonce int64) string {
|
||||||
|
input := fmt.Sprintf("%s:%s:%d:%d:%d", previousHash, merkleRoot, timestamp, difficulty, nonce)
|
||||||
|
return SHA256(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates if a hash meets the mining difficulty requirement
|
||||||
|
func IsValidHash(hash string, difficulty int) bool {
|
||||||
|
target := strings.Repeat("0", difficulty)
|
||||||
|
return strings.HasPrefix(hash, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates mining job id
|
||||||
|
func GenerateJobId() string {
|
||||||
|
return SHA256(fmt.Sprintf("job:%d:%d", utils.GetCurrentTimeMillis(), utils.RandInt(0, 1<<63-1)))[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate merkle root from transaction hashes
|
||||||
|
func CalculateMerkleRoot(transactionHashes []string) string {
|
||||||
|
if len(transactionHashes) == 0 {
|
||||||
|
return SHA256("empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(transactionHashes) == 1 {
|
||||||
|
return transactionHashes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes := transactionHashes
|
||||||
|
|
||||||
|
for len(hashes) > 1 {
|
||||||
|
var newHashes []string
|
||||||
|
for i := 0; i < len(hashes); i += 2 {
|
||||||
|
left := hashes[i]
|
||||||
|
right := left // Default to left if there's no right
|
||||||
|
if i+1< len(hashes) {
|
||||||
|
right = hashes[i+1]
|
||||||
|
}
|
||||||
|
newHashes = append(newHashes, SHA256(fmt.Sprintf("%s:%s", left, right)))
|
||||||
|
}
|
||||||
|
hashes = newHashes
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a random nonce for mining
|
||||||
|
func GenerateNonce() int64 {
|
||||||
|
return int64(utils.RandInt(0, 1<<63-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates transaction hash format
|
||||||
|
func IsValidTransactionHash(hash string) bool {
|
||||||
|
return len(hash) == 64 && utils.IsHex(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validates block hash format
|
||||||
|
func IsValidBlockHash(hash string) bool {
|
||||||
|
return len(hash) == 64 && utils.IsHex(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifies password hash
|
||||||
|
func VerifyPassword(password string, storedHash string) bool {
|
||||||
|
return HashPassword(password) == storedHash
|
||||||
|
}
|
||||||
|
|||||||
48
server/utils/general.go
Normal file
48
server/utils/general.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generates a random integer in the range (min, max)
|
||||||
|
func RandInt(min, max int) int {
|
||||||
|
n, _ := rand.Int(rand.Reader, big.NewInt(int64(max-min)))
|
||||||
|
return int(n.Int64()) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current time in milliseconds
|
||||||
|
func GetCurrentTimeMillis() int64 {
|
||||||
|
return time.Now().UnixNano() / int64(time.Millisecond) // Convert nanoseconds to milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlpha checks if the string contains only alphabetic characters
|
||||||
|
func IsAlpha(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if !('a' <= r && r <= 'z') && !('A' <= r && r <= 'Z') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDigit checks if the string contains only digits
|
||||||
|
func IsDigit(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if !(r >= '0' && r <= '9') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHex checks if the string is a valid hexadecimal representation
|
||||||
|
func IsHex(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user