Files
zeromesh/sdk/identity.go

122 lines
2.5 KiB
Go

package sdk
import (
"encoding/json"
"fmt"
"os"
"strings"
)
func LoadOrGenerateIdentity(path string) (*Identity, error) {
data, err := os.ReadFile(path)
if err == nil {
return parseIdentity(strings.TrimSpace(string(data)))
}
if !os.IsNotExist(err) {
return nil, err
}
id := GenerateIdentity()
encoded, err := serializeIdentity(id)
if err != nil {
return nil, err
}
dir := dirname(path)
if dir != "" {
if e := os.MkdirAll(dir, 0755); e != nil {
return nil, e
}
}
if e := os.WriteFile(path, []byte(encoded), 0600); e != nil {
return nil, e
}
return id, nil
}
type identityJSON struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
Address string `json:"address"`
}
func serializeIdentity(id *Identity) (string, error) {
data, err := json.Marshal(identityJSON{
PublicKey: id.PublicKeyHex(),
PrivateKey: id.PrivateKeyHex(),
Address: id.Address.String(),
})
if err != nil {
return "", err
}
return string(data), nil
}
func parseIdentity(data string) (*Identity, error) {
var j identityJSON
if err := json.Unmarshal([]byte(data), &j); err != nil {
return nil, fmt.Errorf("parse identity: %w", err)
}
if j.PrivateKey == "" {
return nil, fmt.Errorf("invalid identity: no private key")
}
pub, err := hexDecode(j.PublicKey)
if err != nil {
return nil, fmt.Errorf("decode public key: %w", err)
}
priv, err := hexDecode(j.PrivateKey)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
return &Identity{
PublicKey: pub,
PrivateKey: priv,
Address: AddressFromPublicKey(pub),
}, nil
}
func hexDecode(s string) ([]byte, error) {
out, err := hexDecodeSimple(s)
if err != nil {
return nil, err
}
return out, nil
}
func hexDecodeSimple(s string) ([]byte, error) {
h := func(c byte) (byte, error) {
switch {
case '0' <= c && c <= '9':
return c - '0', nil
case 'a' <= c && c <= 'f':
return c - 'a' + 10, nil
case 'A' <= c && c <= 'F':
return c - 'A' + 10, nil
default:
return 0, fmt.Errorf("invalid hex")
}
}
if len(s)%2 != 0 {
return nil, fmt.Errorf("odd hex length")
}
out := make([]byte, len(s)/2)
for i := 0; i < len(s); i += 2 {
hi, e1 := h(s[i])
lo, e2 := h(s[i+1])
if e1 != nil || e2 != nil {
return nil, fmt.Errorf("invalid hex char")
}
out[i/2] = hi<<4 | lo
}
return out, nil
}
func dirname(path string) string {
idx := strings.LastIndex(path, "/")
if idx == -1 {
idx = strings.LastIndex(path, "\\")
}
if idx == -1 {
return ""
}
return path[:idx]
}