blockchain_go/wallet.go

171 lines
3.8 KiB
Go

package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/gob"
"fmt"
"log"
"math/big"
"golang.org/x/crypto/ripemd160"
)
const version = byte(0x00)
const addressChecksumLen = 4
// Wallet stores private and public keys
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
// NewWallet creates and returns a Wallet
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
// GetAddress returns wallet address
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
// HashPubKey hashes public key
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
if err != nil {
log.Panic(err)
}
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
// ValidateAddress check if address if valid
func ValidateAddress(address string) bool {
pubKeyHash := Base58Decode([]byte(address))
actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
version := pubKeyHash[0]
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen]
targetChecksum := checksum(append([]byte{version}, pubKeyHash...))
return bytes.Equal(actualChecksum, targetChecksum)
}
// Checksum generates a checksum for a public key
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}
func (w *Wallet) GobEncode() ([]byte, error) {
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
// Encode the curve name
curveName := "P256"
if err := encoder.Encode(curveName); err != nil {
return nil, err
}
// Encode the private key components
privateKeyBytes := w.PrivateKey.D.Bytes()
if err := encoder.Encode(privateKeyBytes); err != nil {
return nil, err
}
// Encode the public key components
xBytes := w.PrivateKey.PublicKey.X.Bytes()
yBytes := w.PrivateKey.PublicKey.Y.Bytes()
if err := encoder.Encode(xBytes); err != nil {
return nil, err
}
if err := encoder.Encode(yBytes); err != nil {
return nil, err
}
// Encode the public key
if err := encoder.Encode(w.PublicKey); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (w *Wallet) GobDecode(data []byte) error {
buffer := bytes.NewBuffer(data)
decoder := gob.NewDecoder(buffer)
// Decode the curve name
var curveName string
if err := decoder.Decode(&curveName); err != nil {
return err
}
// Set the curve based on the name
var curve elliptic.Curve
switch curveName {
case "P256":
curve = elliptic.P256()
default:
return fmt.Errorf("unsupported curve: %s", curveName)
}
// Decode the private key components
var privateKeyBytes []byte
if err := decoder.Decode(&privateKeyBytes); err != nil {
return err
}
w.PrivateKey.PublicKey.Curve = curve
w.PrivateKey.D = new(big.Int).SetBytes(privateKeyBytes)
// Decode the public key components
var xBytes, yBytes []byte
if err := decoder.Decode(&xBytes); err != nil {
return err
}
if err := decoder.Decode(&yBytes); err != nil {
return err
}
w.PrivateKey.PublicKey.X = new(big.Int).SetBytes(xBytes)
w.PrivateKey.PublicKey.Y = new(big.Int).SetBytes(yBytes)
// Decode the public key
if err := decoder.Decode(&w.PublicKey); err != nil {
return err
}
return nil
}