5. Wallet
Building a basic wallet module
공개키 암호화

wallet.go




wallets.go
utils.go
Result
Last updated
Building a basic wallet module





Last updated
package wallet
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"log"
"golang.org/x/crypto/ripemd160"
)
const (
// checksum length in byte.
// 4byte length
checksumLength = 4
// 0 byte를 16진법으로 표현.
version = byte(0x00)
)
// Wallet : PrivateKey와 PublicKey를 가지고 있는 Wallet structure.
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
// Address : version, publicKeyHash, checksum 3가지를 concatenate한 후에 Base58로 인코딩해서 address를 만든다.
func (w Wallet) Address() []byte {
pubHash := PublicKeyHash(w.PublicKey)
versionedHash := append([]byte{version}, pubHash...)
checksum := Checksum(versionedHash)
fullHash := append(versionedHash, checksum...)
address := Base58Encode(fullHash)
fmt.Printf("pub key: %x\n", w.PublicKey)
fmt.Printf("pub hash: %x\n", pubHash)
fmt.Printf("address: %s\n", address)
return address
}
// NewKeyPair : 새로운 키페어를 만든다.
// ecdsa라는 비대칭키 암호 알고리즘을 사용.
func NewKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
// X, Y가 concatenate 되어 pub이 됨.
pub := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pub
}
// MakeWallet : NewKeyPair를 이용해서 Wallet을 만든다.
func MakeWallet() *Wallet {
private, public := NewKeyPair()
wallet := Wallet{private, public}
return &wallet
}
// PublicKeyHash : sha256, ripemd160을 이용해 퍼블릭키를 해쉬로 변환.
// address 생성에 쓰임.
func PublicKeyHash(pubKey []byte) []byte {
pubHash := sha256.Sum256(pubKey)
hasher := ripemd160.New()
_, err := hasher.Write(pubHash[:])
if err != nil {
log.Panic(err)
}
publicRipMD := hasher.Sum(nil)
return publicRipMD
}
// Checksum : payload를 sha256을 이용해 해쉬로 변환하고 checksumLength만큼의 바이트만 사용한다.
func Checksum(payload []byte) []byte {
firstHash := sha256.Sum256(payload)
secondHash := sha256.Sum256(firstHash[:])
return secondHash[:checksumLength]
}
package wallet
import (
"bytes"
"crypto/elliptic"
"encoding/gob"
"fmt"
"io/ioutil"
"log"
"os"
)
const walletFile = "./tmp/wallets.data"
// Wallets : map형태로 badgerDB에 저장.
// key : address
// value : Wallet의 주소값
type Wallets struct {
Wallets map[string]*Wallet
}
// CreateWallets : Wallets를 생성한다.
// Wallets 껍데기를 만들고 그 안에 기존 Wallets를 로드한다.
func CreateWallets() (*Wallets, error) {
wallets := Wallets{}
wallets.Wallets = make(map[string]*Wallet)
err := wallets.LoadFile()
return &wallets, err
}
// AddWallet : Wallet을 추가한다.
func (ws *Wallets) AddWallet() string {
wallet := MakeWallet()
// byte를 string으로 변환.
address := fmt.Sprintf("%s", wallet.Address())
ws.Wallets[address] = wallet
return address
}
// GetAllAddresses : Wallet 안의 모든 주소들을 반환.
func (ws *Wallets) GetAllAddresses() []string {
var addresses []string
for address := range ws.Wallets {
addresses = append(addresses, address)
}
return addresses
}
// GetWallet : map 이어서 접근하기 쉬움.
func (ws Wallets) GetWallet(address string) Wallet {
return *ws.Wallets[address]
}
// LoadFile : 저장된 Wallets를 불러온다.
func (ws *Wallets) LoadFile() error {
if _, err := os.Stat(walletFile); os.IsNotExist(err) {
return err
}
var wallets Wallets
fileContent, err := ioutil.ReadFile(walletFile)
if err != nil {
return err
}
// elliptic.P256 알고리즘으로 디코딩
gob.Register(elliptic.P256())
decoder := gob.NewDecoder(bytes.NewReader(fileContent))
err = decoder.Decode(&wallets)
if err != nil {
return err
}
ws.Wallets = wallets.Wallets
return nil
}
// SaveFile : elliptic.P256 알고리즘을 이용해서 walletFile에 저장한다.
func (ws *Wallets) SaveFile() {
var content bytes.Buffer
// elliptic.P256 알고리즘으로 인코딩
gob.Register(elliptic.P256())
encoder := gob.NewEncoder(&content)
err := encoder.Encode(ws)
if err != nil {
log.Panic(err)
}
// 0644 : permission, 파일이 이미 존재하지 않으면 생성한다.
err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
if err != nil {
log.Panic(err)
}
}
package wallet
import (
"log"
"github.com/mr-tron/base58"
)
// 0 O l I + /
// Base58 은 64에서 헷갈리는 6개의 단어를 빼고 58개로 이루어진 인코딩 기법이다.
// Wallet 주소를 헷갈려서 잘못 입력하면 원하는 곳으로 코인이나 데이터를 이동시키지 못 한다.
// Base58Encode : Base58로 인코딩
func Base58Encode(input []byte) []byte {
encode := base58.Encode(input)
return []byte(encode)
}
// Base58Decode : Base58로 디코딩
func Base58Decode(input []byte) []byte {
decode, err := base58.Decode(string(input[:]))
if err != nil {
log.Panic(err)
}
return decode
}
// pub key의 길이가 가장 길고 pub hash, address로 갈수록 길이가 짧아진다.
// 인코딩을 거쳐 점점 길이가 짧아지는 것 (sha256 -> rmpemd160 -> base58)
// address는 버젼을 1로 설정했기 때문에 맨 앞 숫자가 1이다.
pub key: a5b803c0fc1895a82e9a6a82e4a5a21f5deee034dec81ddd4ccc3f024b11c2a4f4e28b3aef57b6ccca878e70f9d5dfac2b647795d718b666414732b3019ca2b3
pub hash: 0a14615357ecba8bb69bbbffb63e1fca03480ab7
address: 1vJAxZMhEzno17nrUJhUfZqxD82XRVRic❯ go run main.go createwallet
pub key: 864d6ef68552034f420b6c001d5fa2b866f40f3e59e2c773bea40fddebe5b7036e13a4eee923be836cd3d96448163fe15b516c4ac66ad78426c1c848f5907afb
pub hash: 28b5bae6078d089e8cbefc1b6ef1da283493fe3f
address: 14iFjW13yc8pGhbEfkAEpEAgiB6mW4PH8Y
New address is: 14iFjW13yc8pGhbEfkAEpEAgiB6mW4PH8Y
❯ go run main.go listaddresses
14iFjW13yc8pGhbEfkAEpEAgiB6mW4PH8Y
❯ go run main.go createwallet
pub key: a707acaccc3ff6bab6007c6527b4cfd0f4aad6255dfa0035e1b839ed98bd450e2de0a3f1fc4fbb4f6df7c738513f0723dd92667c97466e472d0a767578bd2bea
pub hash: 02eebb0e91cdd3a7012f866b4c03171b6a7ac402
address: 1GWLgcBJ23bvcXHDrFn2eHHDbHsqaZTXj
New address is: 1GWLgcBJ23bvcXHDrFn2eHHDbHsqaZTXj
❯ go run main.go createwallet
pub key: 32e175364ef5fce20b3c882284ec9f3c37fc3c64476280f0e8ceccdd520dc6c3611c6ec817894c674e776964252d6699038094026ff1fd2294c6bd21653e943a
pub hash: 61d4514f939deca6414a2f72ce20db2aa9294cff
address: 19vGthH7Mu9d5UBLjiijrrPdqoh8QMhwGA
New address is: 19vGthH7Mu9d5UBLjiijrrPdqoh8QMhwGA
❯ go run main.go listaddresses
1GWLgcBJ23bvcXHDrFn2eHHDbHsqaZTXj
19vGthH7Mu9d5UBLjiijrrPdqoh8QMhwGA
14iFjW13yc8pGhbEfkAEpEAgiB6mW4PH8Y