122 lines
2.5 KiB
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]
|
|
}
|