Files
zeromesh/internal/identity/identity.go

143 lines
2.8 KiB
Go

package identity
import (
"crypto/ed25519"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
)
type Identity struct {
PublicKey ed25519.PublicKey `json:"public_key"`
PrivateKey ed25519.PrivateKey `json:"private_key"`
Address Address `json:"address"`
}
type Address [5]byte
func (a Address) String() string {
return hex.EncodeToString(a[:])
}
func (a Address) MarshalText() ([]byte, error) {
return []byte(a.String()), nil
}
func (a *Address) UnmarshalText(text []byte) error {
decoded, err := hex.DecodeString(string(text))
if err != nil {
return err
}
if len(decoded) != 5 {
return fmt.Errorf("address must be 5 bytes")
}
copy(a[:], decoded)
return nil
}
func AddressFromPublicKey(pubKey []byte) Address {
var addr Address
h := hashBytes(pubKey)
copy(addr[:], h[:5])
return addr
}
func Generate() *Identity {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
return &Identity{
PublicKey: pub,
PrivateKey: priv,
Address: AddressFromPublicKey(pub),
}
}
func (id *Identity) PublicKeyHex() string {
return hex.EncodeToString(id.PublicKey)
}
func (id *Identity) PrivateKeyHex() string {
return hex.EncodeToString(id.PrivateKey)
}
func (id *Identity) Sign(data []byte) []byte {
return ed25519.Sign(id.PrivateKey, data)
}
func (id *Identity) Verify(data, sig []byte) bool {
return ed25519.Verify(id.PublicKey, data, sig)
}
func LoadOrGenerate(path string) (*Identity, error) {
data, err := os.ReadFile(path)
if err == nil {
return Parse(strings.TrimSpace(string(data)))
}
if !os.IsNotExist(err) {
return nil, err
}
id := Generate()
encoded, err := id.Serialize()
if err != nil {
return nil, err
}
if err := os.MkdirAll(dir(path), 0755); err != nil {
return nil, err
}
if err := os.WriteFile(path, []byte(encoded), 0600); err != nil {
return nil, err
}
return id, nil
}
func (id *Identity) Serialize() (string, error) {
data, err := json.Marshal(id)
if err != nil {
return "", err
}
return string(data), nil
}
func Parse(data string) (*Identity, error) {
var id Identity
if err := json.Unmarshal([]byte(data), &id); err != nil {
return nil, fmt.Errorf("parse identity: %w", err)
}
if len(id.PrivateKey) == 0 {
return nil, fmt.Errorf("invalid identity: no private key")
}
id.Address = AddressFromPublicKey(id.PublicKey)
return &id, nil
}
func dir(path string) string {
idx := strings.LastIndex(path, "/")
if idx == -1 {
idx = strings.LastIndex(path, "\\")
}
if idx == -1 {
return "."
}
return path[:idx]
}
func hashBytes(data []byte) []byte {
h := make([]byte, 32)
for i, b := range data {
h[i%32] ^= b
}
// simple hash expansion
for round := 0; round < 3; round++ {
for i := 0; i < 32; i++ {
h[i] = h[i] ^ h[(i+1)%32] ^ h[(i+7)%32]
h[i] = (h[i] << 3) | (h[i] >> 5)
}
}
return h
}