├── internal ├── app │ ├── utxo.go │ ├── tx.go │ ├── wallet.go │ └── block.go ├── infra │ ├── storage │ │ ├── wallet │ │ │ ├── wallet_store.go │ │ │ └── file_wallet_store.go │ │ └── block │ │ │ ├── block_store.go │ │ │ └── file_block_store.go │ └── filesystem │ │ └── fsutil.go └── domain │ ├── tx │ ├── service.go │ └── tx.go │ ├── block │ ├── blockchain.go │ ├── pow.go │ ├── service.go │ └── block.go │ ├── wallet │ ├── wallet.go │ └── service.go │ └── utxo │ └── utxo.go ├── cmd ├── oatcoin │ ├── tx.go │ ├── mine.go │ ├── init.go │ ├── root.go │ ├── wallet.go │ └── block.go └── main.go ├── pkg └── crypto │ ├── keys.go │ ├── hash.go │ └── base58.go ├── scripts └── Makefile ├── go.mod ├── todo.md ├── readme.md └── go.sum /internal/app/utxo.go: -------------------------------------------------------------------------------- 1 | package app -------------------------------------------------------------------------------- /cmd/oatcoin/tx.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | -------------------------------------------------------------------------------- /internal/app/tx.go: -------------------------------------------------------------------------------- 1 | package app 2 | -------------------------------------------------------------------------------- /cmd/oatcoin/mine.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | -------------------------------------------------------------------------------- /pkg/crypto/keys.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import "github.com/decred/dcrd/dcrec/secp256k1/v4" 4 | 5 | func GenerateKey() (*secp256k1.PrivateKey, error) { 6 | return secp256k1.GeneratePrivateKey() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/crypto/hash.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/sha256" 5 | 6 | "golang.org/x/crypto/ripemd160" 7 | ) 8 | 9 | func HashPubKey(pubKey []byte) []byte { 10 | shaHash := sha256.Sum256(pubKey) 11 | 12 | ripemd := ripemd160.New() 13 | ripemd.Write(shaHash[:]) 14 | 15 | return ripemd.Sum(nil) 16 | } 17 | -------------------------------------------------------------------------------- /internal/infra/storage/wallet/wallet_store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | type WalletData struct { 4 | Adress string 5 | PubKey string 6 | PrivKey string 7 | } 8 | 9 | type WalletStore interface { 10 | SaveWallet(wallet WalletData) error 11 | LoadWallet(address string) (WalletData, error) 12 | ListWallets() ([]WalletData, error) 13 | } 14 | -------------------------------------------------------------------------------- /internal/domain/tx/service.go: -------------------------------------------------------------------------------- 1 | package tx 2 | 3 | import "github.com/decred/dcrd/dcrec/secp256k1/v4" 4 | 5 | func CreateCoinbaseTransaction(to string, reward int) (*Transaction, error) { 6 | return NewCoinbaseTx(to, reward) 7 | } 8 | 9 | func SignTransaction(tx *Transaction, priv *secp256k1.PrivateKey) error { 10 | return tx.Sign(priv) 11 | } 12 | -------------------------------------------------------------------------------- /internal/infra/storage/block/block_store.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | type BlockData struct { 4 | Nonce int 5 | Hash string 6 | PrevHash string 7 | Timestamp int64 8 | Transactions []string 9 | } 10 | type BlockStore interface { 11 | SaveBlock(b BlockData) error 12 | LoadBlock(hash string) (BlockData, error) 13 | LoadAllBlocks() ([]BlockData, error) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/crypto/base58.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "crypto/sha256" 5 | 6 | "github.com/btcsuite/btcutil/base58" 7 | ) 8 | 9 | func Base58CheckEncode(payload []byte) string { 10 | checksum := checkSum(payload) 11 | full := append(payload, checksum...) 12 | return base58.Encode(full) 13 | } 14 | 15 | func checkSum(payload []byte) []byte { 16 | first := sha256.Sum256(payload) 17 | second := sha256.Sum256(first[:]) 18 | return second[:4] 19 | } 20 | -------------------------------------------------------------------------------- /internal/domain/block/blockchain.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/isif00/oat-coin/internal/domain/tx" 5 | ) 6 | 7 | type Blockchain struct { 8 | Blocks []*Block 9 | } 10 | 11 | func NewBlockchain() *Blockchain { 12 | genesis := NewBlock([]*tx.Transaction{}, []byte{}) 13 | return &Blockchain{[]*Block{genesis}} 14 | } 15 | 16 | func (bc *Blockchain) AddBlock(txs []*tx.Transaction) { 17 | prev := bc.Blocks[len(bc.Blocks)-1] 18 | newBlock := NewBlock(txs, []byte(prev.Hash)) 19 | bc.Blocks = append(bc.Blocks, newBlock) 20 | } 21 | -------------------------------------------------------------------------------- /scripts/Makefile: -------------------------------------------------------------------------------- 1 | APP_NAME = oatcoin 2 | ROOT_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/..) 3 | BUILD_DIR := $(ROOT_DIR)/build 4 | GOFILES := $(shell find $(ROOT_DIR) -type f -name '*.go' -not -path "$(ROOT_DIR)/vendor/*") 5 | 6 | .PHONY: all build clean fmt 7 | 8 | all: build 9 | 10 | build: 11 | @echo "🔨 Building $(APP_NAME)..." 12 | @mkdir -p $(BUILD_DIR) 13 | cd $(ROOT_DIR) && go build -o build/$(APP_NAME) ./cmd 14 | 15 | fmt: 16 | @echo "🧼 Formatting code..." 17 | cd $(ROOT_DIR) && go fmt ./... 18 | 19 | clean: 20 | @echo "🧹 Cleaning up..." 21 | @rm -rf $(BUILD_DIR) 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isif00/oat-coin 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.10 6 | 7 | require ( 8 | github.com/btcsuite/btcutil v1.0.2 9 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 10 | github.com/fatih/color v1.18.0 11 | github.com/spf13/cobra v1.9.1 12 | golang.org/x/crypto v0.39.0 13 | ) 14 | 15 | require ( 16 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/spf13/pflag v1.0.6 // indirect 20 | golang.org/x/sys v0.33.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/isif00/oat-coin/cmd/oatcoin" 7 | ) 8 | 9 | func main() { 10 | app, err := oatcoin.NewOatCoin(".oatcoin") 11 | if err != nil { 12 | log.Fatalf("Failed to init oatcoin: %v", err) 13 | } 14 | 15 | oatcoin.Register( 16 | oatcoin.NewWalletCmd(app.WalletApp), 17 | oatcoin.LoadWalletCmd(app.WalletApp), 18 | oatcoin.ListWalletsCmd(app.WalletApp), 19 | 20 | oatcoin.InitChainCmd(app.BlockApp), 21 | oatcoin.MineBlockCmd(app.BlockApp), 22 | oatcoin.LatestBlockCmd(app.BlockApp), 23 | oatcoin.ListBlocksCmd(app.BlockApp), 24 | ) 25 | oatcoin.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /internal/app/wallet.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/isif00/oat-coin/internal/domain/wallet" 5 | storage "github.com/isif00/oat-coin/internal/infra/storage/wallet" 6 | ) 7 | 8 | type WalletApp struct { 9 | store storage.WalletStore 10 | } 11 | 12 | func NewWalletApp(store storage.WalletStore) *WalletApp { 13 | return &WalletApp{store: store} 14 | } 15 | 16 | func (wa *WalletApp) CreateWallet() (string, error) { 17 | return wallet.CreateWallet(wa.store) 18 | } 19 | 20 | func (wa *WalletApp) LoadWallet(address string) (storage.WalletData, error) { 21 | return wallet.LoadWallet(address, wa.store) 22 | } 23 | 24 | func (wa *WalletApp) ListWallets() ([]storage.WalletData, error) { 25 | return wallet.ListWallets(wa.store) 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/wallet/wallet.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "github.com/decred/dcrd/dcrec/secp256k1/v4" 5 | "github.com/isif00/oat-coin/pkg/crypto" 6 | ) 7 | 8 | type Wallet struct { 9 | PrivateKey secp256k1.PrivateKey 10 | PublicKey secp256k1.PublicKey 11 | } 12 | 13 | func NewWallet() (*Wallet, error) { 14 | privKey, err := crypto.GenerateKey() 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | pubKey := privKey.PubKey() 20 | 21 | return &Wallet{ 22 | PrivateKey: *privKey, 23 | PublicKey: *pubKey, 24 | }, nil 25 | } 26 | 27 | func (w *Wallet) Address() string { 28 | pubKeyHash := crypto.HashPubKey(w.PublicKey.SerializeCompressed()) 29 | 30 | versionedPayload := append([]byte{0x00}, pubKeyHash...) 31 | 32 | address := crypto.Base58CheckEncode(versionedPayload) 33 | 34 | return address 35 | } 36 | -------------------------------------------------------------------------------- /internal/app/block.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/isif00/oat-coin/internal/domain/block" 5 | "github.com/isif00/oat-coin/internal/domain/tx" 6 | storage "github.com/isif00/oat-coin/internal/infra/storage/block" 7 | ) 8 | 9 | type BlockApp struct { 10 | store storage.BlockStore 11 | } 12 | 13 | func NewBlockApp(store storage.BlockStore) *BlockApp { 14 | return &BlockApp{store: store} 15 | } 16 | 17 | func (b *BlockApp) InitializeBlockchain() ([]*block.Block, error) { 18 | return block.InitializeBlockchain(b.store) 19 | } 20 | 21 | func (b *BlockApp) MineBlock(txs []*tx.Transaction) (*block.Block, error) { 22 | return block.MineBlock(b.store, txs) 23 | } 24 | 25 | func (b *BlockApp) GetLatestBlock() (*block.Block, error) { 26 | return block.GetLatestBlock(b.store) 27 | } 28 | 29 | func (b *BlockApp) GetAllBlocks() ([]*block.Block, error) { 30 | return block.GetAllBlocks(b.store) 31 | } 32 | -------------------------------------------------------------------------------- /internal/domain/wallet/service.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | storage "github.com/isif00/oat-coin/internal/infra/storage/wallet" 7 | ) 8 | 9 | func CreateWallet(store storage.WalletStore) (string, error) { 10 | w, err := NewWallet() 11 | a := w.Address() 12 | if err != nil { 13 | return "", err 14 | } 15 | 16 | data := storage.WalletData{ 17 | Adress: a, 18 | PubKey: hex.EncodeToString(w.PublicKey.SerializeCompressed()), 19 | PrivKey: hex.EncodeToString(w.PrivateKey.Serialize()), 20 | } 21 | 22 | if err := store.SaveWallet(data); err != nil { 23 | return "", err 24 | } 25 | return a, nil 26 | } 27 | 28 | func LoadWallet(address string, store storage.WalletStore) (storage.WalletData, error) { 29 | return store.LoadWallet(address) 30 | 31 | } 32 | 33 | func ListWallets(store storage.WalletStore) ([]storage.WalletData, error) { 34 | return store.ListWallets() 35 | } 36 | -------------------------------------------------------------------------------- /internal/domain/block/pow.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "fmt" 7 | "math/big" 8 | ) 9 | 10 | const Difficulty = 18 11 | 12 | type ProofOfWork struct { 13 | Block *Block 14 | Target *big.Int 15 | } 16 | 17 | func NewProofOfWork(b *Block) *ProofOfWork { 18 | target := big.NewInt(1) 19 | target.Lsh(target, 256-Difficulty) 20 | return &ProofOfWork{b, target} 21 | } 22 | 23 | func (pow *ProofOfWork) prepareData(nonce int) []byte { 24 | return bytes.Join( 25 | [][]byte{ 26 | pow.Block.PrevHash, 27 | fmt.Appendf(nil, "%d", pow.Block.Timestamp), 28 | fmt.Appendf(nil, "%d", nonce), 29 | }, 30 | []byte{}, 31 | ) 32 | } 33 | func (pow *ProofOfWork) Run() (int, []byte) { 34 | for nonce := 0; ; nonce++ { 35 | data := pow.prepareData(nonce) 36 | hash := sha256.Sum256(data) 37 | 38 | if new(big.Int).SetBytes(hash[:]).Cmp(pow.Target) < 0 { 39 | return nonce, hash[:] 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /cmd/oatcoin/init.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/isif00/oat-coin/internal/app" 7 | "github.com/isif00/oat-coin/internal/infra/filesystem" 8 | blockstore "github.com/isif00/oat-coin/internal/infra/storage/block" 9 | walletstore "github.com/isif00/oat-coin/internal/infra/storage/wallet" 10 | ) 11 | 12 | type OatCoin struct { 13 | WalletApp *app.WalletApp 14 | BlockApp *app.BlockApp 15 | } 16 | 17 | func NewOatCoin(dataDir string) (*OatCoin, error) { 18 | fs, err := filesystem.NewFileSystem(dataDir) 19 | if err != nil { 20 | return nil, fmt.Errorf("failed to init filesystem: %w", err) 21 | } 22 | 23 | walletStore, err := walletstore.NewFileWalletStore(fs) 24 | if err != nil { 25 | return nil, fmt.Errorf("failed to init wallet store: %w", err) 26 | } 27 | 28 | blockStore, err := blockstore.NewFileBlockStore(fs) 29 | if err != nil { 30 | return nil, fmt.Errorf("failed to init block store: %w", err) 31 | } 32 | 33 | return &OatCoin{ 34 | WalletApp: app.NewWalletApp(walletStore), 35 | BlockApp: app.NewBlockApp(blockStore), 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/infra/storage/wallet/file_wallet_store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/isif00/oat-coin/internal/infra/filesystem" 9 | ) 10 | 11 | type FileWalletStore struct { 12 | fs *filesystem.FileSystem 13 | folder string 14 | } 15 | 16 | func NewFileWalletStore(fs *filesystem.FileSystem) (*FileWalletStore, error) { 17 | store := &FileWalletStore{ 18 | fs: fs, 19 | folder: "wallets", 20 | } 21 | 22 | dirPath := filepath.Join(fs.BasePath, "wallets") 23 | if err := os.MkdirAll(dirPath, os.ModePerm); err != nil { 24 | return nil, err 25 | } 26 | 27 | return store, nil 28 | } 29 | 30 | func (s *FileWalletStore) SaveWallet(wallet WalletData) error { 31 | data, err := json.Marshal(wallet) 32 | if err != nil { 33 | return err 34 | } 35 | return s.fs.Write(s.folder, wallet.Adress+".json", data) 36 | } 37 | 38 | func (s *FileWalletStore) LoadWallet(address string) (WalletData, error) { 39 | data, err := s.fs.Read(s.folder, address+".json") 40 | if err != nil { 41 | return WalletData{}, err 42 | } 43 | var wallet WalletData 44 | if err := json.Unmarshal(data, &wallet); err != nil { 45 | return WalletData{}, err 46 | } 47 | return wallet, nil 48 | } 49 | 50 | func (s *FileWalletStore) ListWallets() ([]WalletData, error) { 51 | files, err := s.fs.ListFiles(s.folder) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | var wallets []WalletData 57 | for _, addr := range files { 58 | wallet, err := s.LoadWallet(addr) 59 | if err == nil { 60 | wallets = append(wallets, wallet) 61 | } 62 | } 63 | return wallets, nil 64 | } 65 | -------------------------------------------------------------------------------- /internal/domain/utxo/utxo.go: -------------------------------------------------------------------------------- 1 | package utxo 2 | 3 | import ( 4 | "fmt" 5 | "github.com/decred/dcrd/dcrec/secp256k1/v4" 6 | 7 | "github.com/isif00/oat-coin/internal/domain/block" 8 | "github.com/isif00/oat-coin/internal/domain/tx" 9 | ) 10 | 11 | func CreateUTXOTransaction(fromPriv *secp256k1.PrivateKey, to string, amount int, utxos map[string][]tx.TxOutput) (*tx.Transaction, error) { 12 | from := string(fromPriv.PubKey().SerializeCompressed()) 13 | 14 | tx, err := tx.NewUTXOTransaction(from, to, amount, utxos) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | err = tx.Sign(fromPriv) 20 | if err != nil { 21 | return nil, fmt.Errorf("failed to sign transaction: %v", err) 22 | } 23 | 24 | for _, input := range tx.Inputs { 25 | txID := string(input.TxID) 26 | delete(utxos, txID) 27 | } 28 | 29 | utxos[string(tx.ID)] = tx.Outputs 30 | 31 | return tx, nil 32 | } 33 | 34 | func GetUTXOSet(blocks []*block.Block) map[string][]tx.TxOutput { 35 | utxos := make(map[string][]tx.TxOutput) 36 | spent := make(map[string]map[int]bool) 37 | 38 | for _, block := range blocks { 39 | for _, tx := range block.Transactions { 40 | txID := string(tx.ID) 41 | 42 | // Skip spent outputs 43 | for idx, out := range tx.Outputs { 44 | if spent[txID][idx] { 45 | continue 46 | } 47 | utxos[txID] = append(utxos[txID], out) 48 | } 49 | 50 | // Track spent outputs 51 | for _, in := range tx.Inputs { 52 | inTxID := string(in.TxID) 53 | if spent[inTxID] == nil { 54 | spent[inTxID] = make(map[int]bool) 55 | } 56 | spent[inTxID][in.OutputIdx] = true 57 | } 58 | } 59 | } 60 | return utxos 61 | } 62 | -------------------------------------------------------------------------------- /internal/infra/storage/block/file_block_store.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/isif00/oat-coin/internal/infra/filesystem" 9 | ) 10 | 11 | type FileBlockStore struct { 12 | fs *filesystem.FileSystem 13 | folder string 14 | } 15 | 16 | func NewFileBlockStore(fs *filesystem.FileSystem) (*FileBlockStore, error) { 17 | store := &FileBlockStore{ 18 | fs: fs, 19 | folder: "blocks", 20 | } 21 | 22 | dirPath := filepath.Join(fs.BasePath, "blocks") 23 | if err := os.MkdirAll(dirPath, os.ModePerm); err != nil { 24 | return nil, err 25 | } 26 | 27 | return store, nil 28 | } 29 | 30 | func (s *FileBlockStore) SaveBlock(b BlockData) error { 31 | data, err := json.Marshal(b) 32 | if err != nil { 33 | return err 34 | } 35 | filename := b.Hash + ".json" 36 | err = s.fs.Write(s.folder, filename, data) 37 | if err != nil { 38 | return err 39 | } 40 | return err 41 | } 42 | 43 | func (s *FileBlockStore) LoadBlock(hash string) (BlockData, error) { 44 | data, err := s.fs.Read(s.folder, hash+".json") 45 | if err != nil { 46 | return BlockData{}, err 47 | } 48 | 49 | var bd BlockData 50 | err = json.Unmarshal(data, &bd) 51 | if err != nil { 52 | return BlockData{}, err 53 | } 54 | return bd, err 55 | } 56 | 57 | func (s *FileBlockStore) LoadAllBlocks() ([]BlockData, error) { 58 | names, err := s.fs.ListFiles(s.folder) 59 | if err != nil { 60 | return nil, err 61 | } 62 | var blocks []BlockData 63 | for _, name := range names { 64 | blk, err := s.LoadBlock(name) 65 | if err != nil { 66 | return nil, err 67 | } 68 | blocks = append(blocks, blk) 69 | } 70 | return blocks, nil 71 | } 72 | -------------------------------------------------------------------------------- /internal/infra/filesystem/fsutil.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | type FileSystem struct { 10 | BasePath string 11 | } 12 | 13 | func NewFileSystem(root string) (*FileSystem, error) { 14 | home, err := os.UserHomeDir() 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | fullPath := filepath.Join(home, root) 20 | if err := os.MkdirAll(fullPath, 0700); err != nil { 21 | return nil, err 22 | } 23 | return &FileSystem{BasePath: fullPath}, nil 24 | } 25 | 26 | func (fs *FileSystem) Write(folder, filename string, data []byte) error { 27 | path := filepath.Join(fs.BasePath, folder) 28 | 29 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 30 | return err 31 | } 32 | 33 | fullPath := filepath.Join(path, filename) 34 | return os.WriteFile(fullPath, data, 0644) 35 | } 36 | func (fs *FileSystem) Read(folder, file string) ([]byte, error) { 37 | return os.ReadFile(filepath.Join(fs.BasePath, folder, file)) 38 | } 39 | 40 | func (fs *FileSystem) ListDirs() ([]string, error) { 41 | files, err := os.ReadDir(fs.BasePath) 42 | if err != nil { 43 | return nil, err 44 | } 45 | var dirs []string 46 | for _, f := range files { 47 | if f.IsDir() { 48 | dirs = append(dirs, f.Name()) 49 | } 50 | } 51 | return dirs, nil 52 | } 53 | 54 | func (f *FileSystem) ListFiles(folder string) ([]string, error) { 55 | fullPath := filepath.Join(f.BasePath, folder) 56 | entries, err := os.ReadDir(fullPath) 57 | if err != nil { 58 | return nil, err 59 | } 60 | var files []string 61 | for _, e := range entries { 62 | if !e.IsDir() && strings.HasSuffix(e.Name(), ".json") { 63 | files = append(files, strings.TrimSuffix(e.Name(), ".json")) 64 | } 65 | } 66 | return files, nil 67 | } 68 | -------------------------------------------------------------------------------- /cmd/oatcoin/root.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/fatih/color" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var rootCmd = &cobra.Command{ 12 | Use: "oatcoin", 13 | Short: "🥣 Oatcoin - A Bitcoin clone in Go", 14 | Long: banner() + ` 15 | Oatcoin is a v0.1 Bitcoin reimplementation. 16 | 17 | Available commands include: 18 | 💳 Wallet 🔗 Transactions ⛏ Mining 19 | 📦 Blocks 📡 P2P Network 🔧 Dev Tools 20 | `, 21 | Run: func(cmd *cobra.Command, args []string) { 22 | color.Cyan("Try running `oatcoin --help` to get started 🛠") 23 | }, 24 | } 25 | 26 | func banner() string { 27 | c := color.New(color.FgHiGreen).Add(color.Bold) 28 | return c.Sprint(` 29 | ___ ___ ___ ___ ___ 30 | / /\ / /\ ___ / /\ / /\ ___ /__/\ 31 | / /::\ / /::\ / /\ / /:/ / /::\ / /\ \ \:\ 32 | / /:/\:\ / /:/\:\ / /:/ / /:/ / /:/\:\ / /:/ \ \:\ 33 | / /:/ \:\ / /:/~/::\ / /:/ / /:/ ___ / /:/ \:\/__/::\ _____\__\:\ 34 | /__/:/ \__\:\/__/:/ /:/\:\/ /::\/__/:/ / /\/__/:/ \__\:\__\/\:\__/__/::::::::\ 35 | \ \:\ / /:/\ \:\/:/__\/__/:/\:\ \:\ / /:/\ \:\ / /:/ \ \:\/\ \:\~~\~~\/ 36 | \ \:\ /:/ \ \::/ \__\/ \:\ \:\ /:/ \ \:\ /:/ \__\::/\ \:\ ~~~ 37 | \ \:\/:/ \ \:\ \ \:\ \:\/:/ \ \:\/:/ /__/:/ \ \:\ 38 | \ \::/ \ \:\ \__\/\ \::/ \ \::/ \__\/ \ \:\ 39 | \__\/ \__\/ \__\/ \__\/ \__\/ 40 | `) 41 | } 42 | 43 | func Execute() { 44 | if err := rootCmd.Execute(); err != nil { 45 | fmt.Println("Error:", err) 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func Register(cmd ...*cobra.Command) { 51 | for _, c := range cmd { 52 | rootCmd.AddCommand(c) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/domain/block/service.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/isif00/oat-coin/internal/domain/tx" 5 | storage "github.com/isif00/oat-coin/internal/infra/storage/block" 6 | ) 7 | 8 | func InitializeBlockchain(store storage.BlockStore) ([]*Block, error) { 9 | blocks, err := store.LoadAllBlocks() 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | domainBlocks := make([]*Block, 0, len(blocks)) 15 | for _, bd := range blocks { 16 | domainBlocks = append(domainBlocks, ToDomain(bd)) 17 | } 18 | 19 | if len(domainBlocks) == 0 { 20 | genesis := NewBlock([]*tx.Transaction{}, []byte{}) 21 | genesisData := genesis.ToStorage() 22 | if err := store.SaveBlock(genesisData); err != nil { 23 | return nil, err 24 | } 25 | domainBlocks = append(domainBlocks, genesis) 26 | } 27 | 28 | return domainBlocks, nil 29 | } 30 | 31 | func MineBlock(store storage.BlockStore, txs []*tx.Transaction) (*Block, error) { 32 | currentBlocks, err := store.LoadAllBlocks() 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | var prevHash []byte 38 | if len(currentBlocks) > 0 { 39 | prevHash = []byte(currentBlocks[len(currentBlocks)-1].Hash) 40 | } 41 | 42 | newBlock := NewBlock(txs, prevHash) 43 | storageBlock := newBlock.ToStorage() 44 | 45 | if err := store.SaveBlock(storageBlock); err != nil { 46 | return nil, err 47 | } 48 | return newBlock, nil 49 | } 50 | 51 | func GetLatestBlock(store storage.BlockStore) (*Block, error) { 52 | blocks, err := store.LoadAllBlocks() 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if len(blocks) == 0 { 58 | return nil, nil 59 | } 60 | 61 | latest := blocks[len(blocks)-1] 62 | return ToDomain(latest), nil 63 | } 64 | 65 | func GetAllBlocks(store storage.BlockStore) ([]*Block, error) { 66 | rawBlocks, err := store.LoadAllBlocks() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | domainBlocks := make([]*Block, 0, len(rawBlocks)) 72 | for _, bd := range rawBlocks { 73 | domainBlocks = append(domainBlocks, ToDomain(bd)) 74 | } 75 | 76 | return domainBlocks, nil 77 | } 78 | -------------------------------------------------------------------------------- /cmd/oatcoin/wallet.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | "github.com/isif00/oat-coin/internal/app" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func NewWalletCmd(walletApp *app.WalletApp) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "newwallet", 14 | Short: "🔐 Generate a new wallet address", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | walletAddress, err := walletApp.CreateWallet() 17 | if err != nil { 18 | color.Red("Failed to generate wallet: %v", err) 19 | return 20 | } 21 | color.Yellow("Wallet Created Successfully: %s", color.GreenString(walletAddress)) 22 | }, 23 | } 24 | return cmd 25 | } 26 | 27 | func LoadWalletCmd(walletApp *app.WalletApp) *cobra.Command { 28 | cmd := &cobra.Command{ 29 | Use: "loadwallet [address]", 30 | Short: "🔐 Load an existing wallet", 31 | Args: cobra.ExactArgs(1), 32 | Run: func(cmd *cobra.Command, args []string) { 33 | a := args[0] 34 | walletData, err := walletApp.LoadWallet(a) 35 | if err != nil { 36 | color.Red("Failed to load wallet: %v", err) 37 | return 38 | } 39 | color.Cyan("🔓 Wallet Loaded Successfully") 40 | fmt.Println() 41 | fmt.Printf("📬 Address : %s\n", color.GreenString(walletData.Adress)) 42 | fmt.Printf("🔑 Public Key : %s\n", color.GreenString(walletData.PubKey)) 43 | fmt.Printf("🕵️ Private Key : %s\n", color.GreenString(walletData.PrivKey)) 44 | 45 | }, 46 | } 47 | return cmd 48 | } 49 | 50 | func ListWalletsCmd(walletApp *app.WalletApp) *cobra.Command { 51 | cmd := &cobra.Command{ 52 | Use: "listwallets", 53 | Short: "🔐 List all existing wallets", 54 | Run: func(cmd *cobra.Command, args []string) { 55 | wallets, err := walletApp.ListWallets() 56 | if err != nil { 57 | color.Red("Failed to list wallets: %v", err) 58 | return 59 | } 60 | color.Cyan("🔓 Wallets Feteched Successfully") 61 | for _, wallet := range wallets { 62 | fmt.Printf("📬 Address : %s\n", color.GreenString(wallet.Adress)) 63 | } 64 | }, 65 | } 66 | return cmd 67 | } 68 | -------------------------------------------------------------------------------- /internal/domain/block/block.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "encoding/hex" 5 | "time" 6 | 7 | "github.com/isif00/oat-coin/internal/domain/tx" 8 | "github.com/isif00/oat-coin/internal/infra/storage/block" 9 | ) 10 | 11 | type Block struct { 12 | Nonce int 13 | Hash []byte 14 | PrevHash []byte 15 | Timestamp int64 16 | Transactions []*tx.Transaction 17 | } 18 | 19 | func NewBlock(txs []*tx.Transaction, prevHash []byte) *Block { 20 | block := &Block{ 21 | Timestamp: time.Now().Unix(), 22 | Transactions: txs, 23 | PrevHash: prevHash, 24 | Hash: []byte{}, 25 | } 26 | pow := NewProofOfWork(block) 27 | nonce, hash := pow.Run() 28 | block.Hash = hash[:] 29 | block.Nonce = nonce 30 | return block 31 | } 32 | 33 | func (b *Block) ToStorage() block.BlockData { 34 | txIDs := make([]string, len(b.Transactions)) 35 | for i, tx := range b.Transactions { 36 | txIDs[i] = hex.EncodeToString([]byte(tx.ID)) 37 | } 38 | return block.BlockData{ 39 | Hash: hex.EncodeToString(b.Hash), 40 | PrevHash: hex.EncodeToString(b.PrevHash), 41 | Timestamp: b.Timestamp, 42 | Nonce: b.Nonce, 43 | Transactions: txIDs, 44 | } 45 | } 46 | 47 | func BlockDataToDomain(bd block.BlockData) *Block { 48 | txHashes := make([]*tx.Transaction, len(bd.Transactions)) 49 | for i, id := range bd.Transactions { 50 | id, err := hex.DecodeString(id) 51 | if err != nil { 52 | panic(err) 53 | } 54 | txHashes[i] = &tx.Transaction{ID: id} 55 | } 56 | return &Block{ 57 | Hash: []byte(bd.Hash), 58 | PrevHash: []byte(bd.PrevHash), 59 | Timestamp: bd.Timestamp, 60 | Nonce: bd.Nonce, 61 | Transactions: txHashes, 62 | } 63 | } 64 | 65 | func ToDomain(bd block.BlockData) *Block { 66 | txList := make([]*tx.Transaction, len(bd.Transactions)) 67 | for i, id := range bd.Transactions { 68 | txList[i] = &tx.Transaction{ 69 | ID: []byte(id), 70 | } 71 | } 72 | 73 | return &Block{ 74 | Hash: []byte(bd.Hash), 75 | PrevHash: []byte(bd.PrevHash), 76 | Timestamp: bd.Timestamp, 77 | Nonce: bd.Nonce, 78 | Transactions: txList, 79 | } 80 | } 81 | 82 | func (b *Block) HashHex() string { 83 | return hex.EncodeToString(b.Hash) 84 | } 85 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | ### 1. **Wallets** 2 | 3 | * Keypair generation (ECDSA, secp256k1) ✅ 4 | * Address generation (compressed pubkey + hashing + Base58Check) ✅ 5 | * Signing transactions ✅ 6 | * Verifying signatures ✅ 7 | * Local storage ✅ 8 | * Wallet management (list, load, save) ✅ 9 | * Recovery mechanism (mnemonics / seeds) 10 | 11 | --- 12 | 13 | ### 2. **Transactions & UTXO** 14 | 15 | * Create and validate transactions ✅ 16 | * Implement UTXO set tracking ✅ 17 | * Transaction inputs referencing UTXOs ✅ 18 | * Outputs creating new UTXOs 19 | * Double-spend prevention 20 | * Fee calculation 21 | * Signature verification on tx inputs 22 | * Mempool for unconfirmed txs 23 | 24 | --- 25 | 26 | ### 3. **Blockchain Core** 27 | 28 | * Block structure (header + txs) ✅ 29 | * Chain linking via prev block hash ✅ 30 | * Block validation (PoW, timestamp, tx validation) ✅ 31 | * Maintain chain state (tip, height) 32 | * Persistence (flat file or DB) ✅ 33 | * Fork handling (longest chain rule) 34 | * Difficulty adjustment (future) 35 | * Genesis block creation ✅ 36 | 37 | --- 38 | 39 | ### 4. **Proof-of-Work Mining** 40 | 41 | * Nonce iteration to find valid hash 42 | * Adjustable difficulty target 43 | * Coinbase transaction creation (block reward) 44 | * Mining loop and interrupt support 45 | * Validation of PoW on incoming blocks 46 | 47 | --- 48 | 49 | ### 5. **Networking / P2P** 50 | 51 | * Peer discovery and connection management 52 | * Message serialization and deserialization 53 | * Propagation of blocks and transactions 54 | * Syncing chain on node startup 55 | * Handling forks and conflicting blocks 56 | * Security and DoS prevention (basic rate limiting) 57 | * Future: encryption, handshakes, node reputation 58 | 59 | --- 60 | 61 | ### 6. **Storage** 62 | 63 | * Flat file storage for blocks (json) ✅ 64 | * Indexing for fast lookup (block height, hashes) 65 | * UTXO set persistence (if stored separately) 66 | * Data corruption detection and recovery 67 | * Backup and restore functionality 68 | 69 | --- 70 | 71 | ### 7. **CLI & User Interface** 72 | 73 | * Wallet creation and management commands ✅ 74 | * Sending transactions and querying balance 75 | * Viewing blockchain info (printchain) 76 | * Node control commands (startnode, stop) 77 | * Clear error reporting and user feedback 78 | * Future: REST API / Web UI 79 | 80 | --- 81 | 82 | ### 8. **Security & Testing** 83 | 84 | * Comprehensive unit tests for core logic 85 | * Integration tests covering node sync and transactions 86 | * Handling malformed / malicious input gracefully 87 | * Private key safety and secure random number generation 88 | * Code audits and reviews 89 | -------------------------------------------------------------------------------- /cmd/oatcoin/block.go: -------------------------------------------------------------------------------- 1 | package oatcoin 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/fatih/color" 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/isif00/oat-coin/internal/app" 11 | "github.com/isif00/oat-coin/internal/domain/tx" 12 | ) 13 | 14 | func InitChainCmd(blockApp *app.BlockApp) *cobra.Command { 15 | return &cobra.Command{ 16 | Use: "initchain", 17 | Short: "🚀 Initialize the blockchain with a genesis block", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | blocks, err := blockApp.InitializeBlockchain() 20 | if err != nil { 21 | color.Red("❌ Failed to initialize blockchain: %v", err) 22 | return 23 | } 24 | color.Green("✅ Blockchain initialized with %d block(s)", len(blocks)) 25 | }, 26 | } 27 | } 28 | 29 | func MineBlockCmd(blockApp *app.BlockApp) *cobra.Command { 30 | return &cobra.Command{ 31 | Use: "mineblock", 32 | Short: "⛏️ Mine a new block with dummy transactions", 33 | Run: func(cmd *cobra.Command, args []string) { 34 | txs := []*tx.Transaction{ 35 | { 36 | ID: []byte("0x1234"), 37 | Inputs: []tx.TxInput{ 38 | { 39 | TxID: []byte("0x1234"), 40 | OutputIdx: 0, 41 | Signature: []byte("signature"), 42 | PubKey: []byte("public key"), 43 | }, 44 | }, 45 | Outputs: []tx.TxOutput{ 46 | { 47 | Amount: 50, 48 | PubKeyHash: []byte("public key hash"), 49 | }, 50 | }, 51 | }, 52 | } 53 | block, err := blockApp.MineBlock(txs) 54 | if err != nil { 55 | color.Red("❌ Failed to mine block: %v", err) 56 | return 57 | } 58 | color.Green("✅ Block mined successfully!") 59 | fmt.Printf("🔗 Hash: %s\n", color.YellowString(block.HashHex())) 60 | }, 61 | } 62 | } 63 | 64 | func LatestBlockCmd(blockApp *app.BlockApp) *cobra.Command { 65 | return &cobra.Command{ 66 | Use: "latestblock", 67 | Short: "📦 Fetch the latest block", 68 | Run: func(cmd *cobra.Command, args []string) { 69 | block, err := blockApp.GetLatestBlock() 70 | if err != nil { 71 | color.Red("❌ Failed to fetch latest block: %v", err) 72 | return 73 | } 74 | color.Cyan("🧱 Latest Block:") 75 | fmt.Printf("🔗 Hash: %s\n", block.Hash) 76 | fmt.Printf("⏱️ Time: %s\n", time.Unix(block.Timestamp, 0).Format("2006-01-02 15:04:05 MST")) 77 | fmt.Printf("📦 Tx Count: %d\n", len(block.Transactions)) 78 | }, 79 | } 80 | } 81 | 82 | func ListBlocksCmd(blockApp *app.BlockApp) *cobra.Command { 83 | return &cobra.Command{ 84 | Use: "listblocks", 85 | Short: "📚 List all blocks in the blockchain", 86 | Run: func(cmd *cobra.Command, args []string) { 87 | blocks, err := blockApp.GetAllBlocks() 88 | if err != nil { 89 | color.Red("❌ Failed to list blocks: %v", err) 90 | return 91 | } 92 | color.Cyan("🧱 All Blocks:") 93 | for i, blk := range blocks { 94 | fmt.Printf("[%d] 🔗 %s | ⏱️ %d | 📦 %d txs\n", i, blk.Hash, blk.Timestamp, len(blk.Transactions)) 95 | } 96 | }, 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /internal/domain/tx/tx.go: -------------------------------------------------------------------------------- 1 | package tx 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/gob" 7 | "fmt" 8 | 9 | "github.com/decred/dcrd/dcrec/secp256k1/v4" 10 | "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 11 | ) 12 | 13 | type TxInput struct { 14 | TxID []byte 15 | OutputIdx int 16 | Signature []byte 17 | PubKey []byte 18 | } 19 | 20 | type TxOutput struct { 21 | Amount int 22 | PubKeyHash []byte 23 | } 24 | 25 | type Transaction struct { 26 | ID []byte 27 | Inputs []TxInput 28 | Outputs []TxOutput 29 | } 30 | 31 | func NewCoinbaseTx(to string, reward int) (*Transaction, error) { 32 | output := TxOutput{ 33 | Amount: reward, 34 | PubKeyHash: []byte(to), 35 | } 36 | tx := &Transaction{ 37 | Inputs: []TxInput{}, 38 | Outputs: []TxOutput{output}, 39 | } 40 | 41 | id, err := tx.Hash() 42 | if err != nil { 43 | return nil, err 44 | } 45 | tx.ID = id 46 | 47 | return tx, nil 48 | } 49 | 50 | func NewUTXOTransaction(from, to string, amount int, utxos map[string][]TxOutput) (*Transaction, error) { 51 | var inputs []TxInput 52 | var outputs []TxOutput 53 | 54 | accumulated := 0 55 | spentOutputs := make(map[string]int) 56 | 57 | for txid, outs := range utxos { 58 | for idx, out := range outs { 59 | if string(out.PubKeyHash) == from { 60 | accumulated += out.Amount 61 | inputs = append(inputs, TxInput{ 62 | TxID: []byte(txid), 63 | OutputIdx: idx, 64 | Signature: nil, // to be signed 65 | PubKey: []byte(from), // simplified pubkey 66 | }) 67 | spentOutputs[txid] = idx 68 | if accumulated >= amount { 69 | break 70 | } 71 | } 72 | } 73 | if accumulated >= amount { 74 | break 75 | } 76 | } 77 | 78 | if accumulated < amount { 79 | return nil, fmt.Errorf("❌ not enough funds") 80 | } 81 | 82 | outputs = append(outputs, TxOutput{ 83 | Amount: amount, 84 | PubKeyHash: []byte(to), 85 | }) 86 | 87 | if accumulated > amount { 88 | outputs = append(outputs, TxOutput{ 89 | Amount: accumulated - amount, 90 | PubKeyHash: []byte(from), 91 | }) 92 | } 93 | 94 | tx := &Transaction{Inputs: inputs, Outputs: outputs} 95 | id, err := tx.Hash() 96 | if err != nil { 97 | return nil, err 98 | } 99 | tx.ID = id 100 | 101 | return tx, nil 102 | } 103 | 104 | func (tx *Transaction) Sign(priv *secp256k1.PrivateKey) error { 105 | txHash, err := tx.Hash() 106 | if err != nil { 107 | return err 108 | } 109 | 110 | for i := range tx.Inputs { 111 | sig := ecdsa.Sign(priv, txHash) 112 | tx.Inputs[i].Signature = sig.Serialize() 113 | tx.Inputs[i].PubKey = priv.PubKey().SerializeCompressed() 114 | } 115 | return nil 116 | } 117 | 118 | func (tx *Transaction) Verify() bool { 119 | txHash, err := tx.Hash() 120 | if err != nil { 121 | return false 122 | } 123 | 124 | for _, in := range tx.Inputs { 125 | pubKey, err := secp256k1.ParsePubKey(in.PubKey) 126 | if err != nil { 127 | return false 128 | } 129 | 130 | sig, err := ecdsa.ParseDERSignature(in.Signature) 131 | if err != nil { 132 | return false 133 | } 134 | 135 | if !sig.Verify(txHash, pubKey) { 136 | return false 137 | } 138 | } 139 | return true 140 | } 141 | 142 | func (tx *Transaction) Hash() ([]byte, error) { 143 | var buf bytes.Buffer 144 | copyTx := *tx 145 | for i := range copyTx.Inputs { 146 | copyTx.Inputs[i].Signature = nil 147 | copyTx.Inputs[i].PubKey = nil 148 | } 149 | _ = gob.NewEncoder(&buf).Encode(copyTx) 150 | hash := sha256.Sum256(buf.Bytes()) 151 | return hash[:], nil 152 | } 153 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 🥣 Oatcoin (v0.1) 2 | 3 | > A minimalist reimplementation of the original Bitcoin (v0.1) using Go. 4 | > Inspired by Satoshi Nakamoto’s whitepaper. No smart contracts. No bloat. Just pure UTXO, Proof-of-Work, and P2P vibes. 5 | 6 | --- 7 | 8 | ## ✨ Features 9 | 10 | * 🔐 ECDSA-based keypair wallet 11 | * 💸 UTXO-based transaction model 12 | * ⛓ Block & blockchain validation logic 13 | * ⚒ Proof-of-Work mining (SHA-256) 14 | * 📡 P2P network for block & transaction propagation 15 | * 🧠 Blockchain persistence using flat files 16 | * 🧾 Coinbase transactions (block rewards) 17 | * ⚙️ CLI wallet & node control 18 | 19 | --- 20 | 21 | ## 🔧 How It Works 22 | 23 | ### ⛓ Blockchain 24 | 25 | Each block contains: 26 | 27 | * A list of transactions 28 | * A timestamp 29 | * The previous block hash 30 | * A nonce (for mining) 31 | * A hash (block ID) 32 | 33 | Blocks are stored to disk using flat files, just like the original Bitcoin client. 34 | 35 | ### 💸 UTXO Model 36 | 37 | Each transaction references outputs of previous transactions (unspent outputs), creating a chain of value ownership. 38 | 39 | ### ⚒ Mining 40 | 41 | * Simple SHA-256 double hashing 42 | * Adjustable difficulty via leading-zero target 43 | * Coinbase transactions create new coins 44 | 45 | ### 🔐 Wallets 46 | 47 | * ECDSA (secp256k1) 48 | * Public key hash = wallet address 49 | * Supports signing and verifying transactions 50 | 51 | ### 📡 P2P 52 | 53 | * Nodes communicate over TCP 54 | * Blocks and transactions are serialized and propagated 55 | * New peers can sync the chain from others 56 | 57 | --- 58 | 59 | ## 💻 CLI Commands 60 | 61 | These are the commands supported by the CLI: 62 | 63 | ## 🔐 Wallet 64 | ``` 65 | oatcoin newaddress # Generate a new wallet address 66 | oatcoin getbalance # Show wallet balance 67 | oatcoin listutxos # List all unspent outputs 68 | oatcoin dumpwallet # Dump private keys (dev/debug use) 69 | oatcoin importkey [privkey] # Import a private key 70 | ``` 71 | ## 💸 Transactions 72 | ``` 73 | oatcoin send [to] [amount] # Send coins to an address 74 | oatcoin sendfrom [from] [to] [amount] # Send coins from a specific address 75 | oatcoin gettx [txid] # Get transaction details 76 | oatcoin listtxs # List recent transactions 77 | ``` 78 | ## ⚒ Mining 79 | ``` 80 | oatcoin mine start # Start CPU mining 81 | oatcoin mine stop # Stop mining 82 | oatcoin mine status # Show mining info (hashrate, difficulty) 83 | ``` 84 | ## 📦 Blocks 85 | ``` 86 | oatcoin getblock [height|hash] # View a block's data 87 | oatcoin getblockhash [height] # Get block hash by height 88 | oatcoin getlatestblock # Get tip of the chain 89 | ``` 90 | --- 91 | 92 | ## 🛠️ To Do (Future Ideas) 93 | 94 | * [ ] Mempool support 95 | * [ ] Difficulty adjustment 96 | * [ ] Node discovery & handshake 97 | * [ ] Web UI / JSON-RPC server 98 | * [ ] P2P encryption (TLS/libp2p) 99 | * [ ] Lightweight explorer 100 | * [ ] Enhanced wallet backup and restore 101 | 102 | --- 103 | 104 | ## 🧠 Based On 105 | 106 | * [Bitcoin Whitepaper](https://bitcoin.org/bitcoin.pdf) 107 | * Satoshi’s [original source code](https://github.com/benjiqq/bitcoinArchive) 108 | * [btcsuite/btcd](https://github.com/btcsuite/btcd) 109 | 110 | --- 111 | 112 | ## 🕳 Philosophy 113 | 114 | > *“If you don’t believe me or don’t get it, I don’t have time to try to convince you, sorry.”* 115 | > — Satoshi Nakamoto 116 | 117 | --- 118 | 119 | ## 🪪 License 120 | 121 | MIT — free as in freedom. 122 | 123 | --- 124 | 125 | ## 🧱 Want to Contribute? 126 | 127 | This project is for educational purposes but built to be extensible. If you’re interested in hacking on the protocol, reach out or fork it! 128 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 2 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 3 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 4 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 5 | github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= 6 | github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= 7 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 8 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 9 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 10 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 11 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 13 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= 15 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 16 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 17 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 18 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 19 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 20 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 21 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 22 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 23 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 24 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 25 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 26 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 27 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 28 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 29 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 30 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 31 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 32 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 33 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 34 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 35 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 36 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 37 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 38 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 39 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= 43 | golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= 44 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 45 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 46 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 53 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 57 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 58 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 59 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 60 | --------------------------------------------------------------------------------