143 lines
2.8 KiB
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
|
|
}
|