How Transactions Work: Simulate a Crypto Wallet

Blockchain isn’t just about blocks and hashes — at its core, it's a system for managing digital transactions without a central authority. Whether you're sending Bitcoin or NFTs, the magic happens through cryptographic wallets and digitally signed transactions.

In this article, we’ll break down how transactions work and simulate a basic crypto wallet using Go. You’ll learn how to:

  • Generate public/private key pairs
  • Create a simple transaction
  • Digitally sign it
  • Verify the signature
What Is a Crypto Wallet, Really?

A crypto wallet doesn’t store coins — it stores keys:

  • A private key (keep secret!) is used to sign transactions.
  • A public key is shared with others and used to verify signatures

When you "send crypto," you're really creating a digitally signed message that proves:

"I authorize this amount to be sent to this address."

In the following section, you will find a code written in go that illustrate this.

Go example

package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "math/big"
)

type Transaction struct {
    From      string
    To        string
    Amount    float64
    Signature []byte
}

func generateKeyPair() (*ecdsa.PrivateKey, error) {
    return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}

func signTransaction(tx *Transaction, privKey *ecdsa.PrivateKey) error {
    data := tx.From + tx.To + fmt.Sprintf("%f", tx.Amount)
    hash := sha256.Sum256([]byte(data))

    r, s, err := ecdsa.Sign(rand.Reader, privKey, hash[:])
    if err != nil {
        return err
    }

    tx.Signature = append(r.Bytes(), s.Bytes()...)
    return nil
}

func verifyTransaction(tx *Transaction, pubKey *ecdsa.PublicKey) bool {
    data := tx.From + tx.To + fmt.Sprintf("%f", tx.Amount)
    hash := sha256.Sum256([]byte(data))

    r := big.Int{}
    s := big.Int{}
    sigLen := len(tx.Signature)
    r.SetBytes(tx.Signature[:(sigLen / 2)])
    s.SetBytes(tx.Signature[(sigLen / 2):])

    return ecdsa.Verify(pubKey, hash[:], &r, &s)
}

func publicKeyToString(pubKey *ecdsa.PublicKey) string {
    pubBytes := append(pubKey.X.Bytes(), pubKey.Y.Bytes()...)
    return hex.EncodeToString(pubBytes)
}

func main() {
    // Wallets
    senderPriv, _ := generateKeyPair()
    senderPub := &senderPriv.PublicKey
    receiverPriv, _ := generateKeyPair()
    receiverPub := &receiverPriv.PublicKey

    // Create a transaction
    tx := Transaction{
        From:   publicKeyToString(senderPub),
        To:     publicKeyToString(receiverPub),
        Amount: 42.0,
    }

    // Sign it
    signTransaction(&tx, senderPriv)

    // Verify it
    valid := verifyTransaction(&tx, senderPub)
    fmt.Printf("Transaction valid: %v\n", valid)

    if !valid {
        fmt.Println("Transaction failed verification!")
    } else {
        fmt.Printf("From: %s\nTo: %s\nAmount: %.2f\nSignature: %x\n",
            tx.From, tx.To, tx.Amount, tx.Signature)
    }
}

Result:

Transaction valid: true
From: 04e6b4a...23ff2
To:   0424cc1...5a89f
Amount: 42.00
Signature: 3045ab...9fce
How the code works
  • You generate keys for both sender and receiver.
  • The transaction is created and hashed.
  • The sender signs the hash using their private key.
  • Anyone (like miners or validators) can verify it using the public key.

This is the backbone of how cryptocurrencies stay trustless and secure.