5. Wallet
Building a basic wallet module
간단한 지갑 모듈을 만들건데 블록체인과는 분리해서 만들고 다음 강좌에서 합치려고 한다.
현재는 지갑의 생성, 추가가 모두 간단하게 이루어지고 그 자체로는 의미가 없다.
지갑에서 public key, private key 의 생성, Address를 만드는 과정 등에 집중해서 살펴보자.
공개키 암호화

앨리스는 개인키와 공개키를 생성하고 공개키를 밥에게 전달해야 한다.
밥은 앨리스의 공개키로 메시지를 암호화 하고 앨리스에게 보낸다.
암호화된 메시지를 받은 앨리스는 자신의 개인키로 메시지를 해독한다.
공개키에서는 개인키를 만들 수 없으므로, 앨리스의 공개키로 암호화된 메시지를 읽을 수 있는 것은 대응되는 개인키를 가진 앨리스뿐이다.
ECDSA(Elliptic Curve Digital Signature Algorithm) 타원곡선암호는 비트코인, 이더리움 드에서 거래 시 정당한 소유주만이 자금을 쓸 수 있도록 하는 암호 알고리즘이다.
wallet.go

위의 그림처럼 Private key로부터 Public key를 뽑아내고 그들로부터 Address가 나온다.



address는 위 3개의 그림과 같이 public key부터 많은 과정을 거쳐서 만들어진다.
최종적으로는 version, checksum, public key hash 를 합쳐서 base 58로 인코딩해서 만들어진다.
아래 코드는 지갑의 키페어 생성, 주소 생성 등의 메소드를 구현해놓았다.
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]
}
wallets.go
지갑을 map의 형태로 badgerDB에 저장한다. key : address, value : Wallet의 주소값
지갑의 생성, 추가, 저장 등의 메소드를 구현했다.
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)
}
}
utils.go
Address 만들 때 사용되는 Base58 인코딩, 디코딩 함수이다.
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
}
Result
// pub key의 길이가 가장 길고 pub hash, address로 갈수록 길이가 짧아진다.
// 인코딩을 거쳐 점점 길이가 짧아지는 것 (sha256 -> rmpemd160 -> base58)
// address는 버젼을 1로 설정했기 때문에 맨 앞 숫자가 1이다.
pub key: a5b803c0fc1895a82e9a6a82e4a5a21f5deee034dec81ddd4ccc3f024b11c2a4f4e28b3aef57b6ccca878e70f9d5dfac2b647795d718b666414732b3019ca2b3
pub hash: 0a14615357ecba8bb69bbbffb63e1fca03480ab7
address: 1vJAxZMhEzno17nrUJhUfZqxD82XRVRic
간단하게 지갑을 3개 생성하고 지갑들의 모든 주소를 출력하는 예제이다.
❯ 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
Last updated
Was this helpful?