package identity import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/sha512" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "strings" "time" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/nacl/box" "go-blockchain/blockchain" ) // Identity holds an Ed25519 keypair and a Curve25519 (X25519) keypair. // Ed25519 is used for signing transactions and consensus messages. // X25519 is used for NaCl box (E2E) message encryption. type Identity struct { PubKey ed25519.PublicKey PrivKey ed25519.PrivateKey // X25519 keypair for NaCl box encryption. // Generated together with Ed25519; stored alongside in key files. X25519Pub [32]byte X25519Priv [32]byte } // Generate creates a fresh Ed25519 + X25519 keypair. func Generate() (*Identity, error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, fmt.Errorf("generate ed25519: %w", err) } xpub, xpriv, err := box.GenerateKey(rand.Reader) if err != nil { return nil, fmt.Errorf("generate x25519: %w", err) } return &Identity{ PubKey: pub, PrivKey: priv, X25519Pub: *xpub, X25519Priv: *xpriv, }, nil } // PubKeyHex returns the hex-encoded Ed25519 public key. func (id *Identity) PubKeyHex() string { return hex.EncodeToString(id.PubKey) } // PrivKeyHex returns the hex-encoded Ed25519 private key. func (id *Identity) PrivKeyHex() string { return hex.EncodeToString(id.PrivKey) } // X25519PubHex returns the hex-encoded Curve25519 public key. func (id *Identity) X25519PubHex() string { return hex.EncodeToString(id.X25519Pub[:]) } // X25519PrivHex returns the hex-encoded Curve25519 private key. func (id *Identity) X25519PrivHex() string { return hex.EncodeToString(id.X25519Priv[:]) } // Sign returns an Ed25519 signature over msg. func (id *Identity) Sign(msg []byte) []byte { return ed25519.Sign(id.PrivKey, msg) } // Verify returns true if sig is a valid Ed25519 signature over msg by pubKeyHex. func Verify(pubKeyHex string, msg, sig []byte) (bool, error) { pubBytes, err := hex.DecodeString(pubKeyHex) if err != nil { return false, fmt.Errorf("invalid pub key hex: %w", err) } return ed25519.Verify(ed25519.PublicKey(pubBytes), msg, sig), nil } // MineRegistration performs a lightweight proof-of-work so that // identity registration has a CPU cost (Sybil barrier). func MineRegistration(pubKeyHex string, difficulty int) (nonce uint64, target string, err error) { prefix := strings.Repeat("0", difficulty/4) pubBytes, err := hex.DecodeString(pubKeyHex) if err != nil { return 0, "", err } buf := make([]byte, len(pubBytes)+8) copy(buf, pubBytes) for nonce = 0; ; nonce++ { binary.BigEndian.PutUint64(buf[len(pubBytes):], nonce) h := sha256.Sum256(buf) hexHash := hex.EncodeToString(h[:]) if strings.HasPrefix(hexHash, prefix) { return nonce, hexHash, nil } } } // RegisterTx builds and signs a REGISTER_KEY transaction. // It includes the X25519 public key so recipients can look it up on-chain. func RegisterTx(id *Identity, nickname string, powDifficulty int) (*blockchain.Transaction, error) { nonce, target, err := MineRegistration(id.PubKeyHex(), powDifficulty) if err != nil { return nil, err } payload := blockchain.RegisterKeyPayload{ PubKey: id.PubKeyHex(), Nickname: nickname, PowNonce: nonce, PowTarget: target, X25519PubKey: id.X25519PubHex(), } payloadBytes, err := json.Marshal(payload) if err != nil { return nil, err } tx := &blockchain.Transaction{ ID: txID(id.PubKeyHex(), blockchain.EventRegisterKey), Type: blockchain.EventRegisterKey, From: id.PubKeyHex(), Payload: payloadBytes, Fee: blockchain.RegistrationFee, Timestamp: time.Now().UTC(), } tx.Signature = id.Sign(txSignBytes(tx)) return tx, nil } // SignMessage returns a detached Ed25519 signature over an arbitrary message. func (id *Identity) SignMessage(msg []byte) []byte { return id.Sign(msg) } // VerifyMessage verifies a detached signature (see SignMessage). func VerifyMessage(pubKeyHex string, msg, sig []byte) (bool, error) { return Verify(pubKeyHex, msg, sig) } // FromHex reconstructs an Identity from hex-encoded Ed25519 keys. // X25519 fields are left zeroed; use FromHexFull for complete identity. func FromHex(pubHex, privHex string) (*Identity, error) { return FromHexFull(pubHex, privHex, "", "") } // deriveX25519 deterministically derives a Curve25519 keypair from an Ed25519 // private key using the standard Ed25519→X25519 conversion (SHA-512 of seed + // X25519 clamping). This matches libsodium's crypto_sign_ed25519_sk_to_curve25519. func deriveX25519(privKey ed25519.PrivateKey) (pub [32]byte, priv [32]byte) { // Ed25519 private key = seed (32 bytes) || public key (32 bytes). seed := privKey[:32] h := sha512.Sum512(seed) // Apply X25519 scalar clamping. h[0] &= 248 h[31] &= 127 h[31] |= 64 copy(priv[:], h[:32]) pubSlice, _ := curve25519.X25519(priv[:], curve25519.Basepoint) copy(pub[:], pubSlice) return pub, priv } // FromHexFull reconstructs a complete Identity including X25519 keys. // When x25519PubHex/x25519PrivHex are empty, X25519 is derived deterministically // from the Ed25519 private key using the standard Ed25519→X25519 conversion. func FromHexFull(pubHex, privHex, x25519PubHex, x25519PrivHex string) (*Identity, error) { pubBytes, err := hex.DecodeString(pubHex) if err != nil { return nil, fmt.Errorf("decode pub key: %w", err) } privBytes, err := hex.DecodeString(privHex) if err != nil { return nil, fmt.Errorf("decode priv key: %w", err) } id := &Identity{ PubKey: ed25519.PublicKey(pubBytes), PrivKey: ed25519.PrivateKey(privBytes), } if x25519PubHex != "" && x25519PrivHex != "" { b, err := hex.DecodeString(x25519PubHex) if err != nil || len(b) != 32 { return nil, fmt.Errorf("decode x25519 pub key: %w", err) } copy(id.X25519Pub[:], b) b, err = hex.DecodeString(x25519PrivHex) if err != nil || len(b) != 32 { return nil, fmt.Errorf("decode x25519 priv key: %w", err) } copy(id.X25519Priv[:], b) } else { // Derive X25519 deterministically from Ed25519 private key. id.X25519Pub, id.X25519Priv = deriveX25519(id.PrivKey) } return id, nil } // txID generates a deterministic transaction ID. func txID(fromPubHex string, eventType blockchain.EventType) string { h := sha256.Sum256([]byte(fromPubHex + string(eventType) + fmt.Sprint(time.Now().UnixNano()))) return hex.EncodeToString(h[:16]) } // TxSignBytes returns the canonical bytes that must be signed (and verified) // for a transaction. Use this whenever building a transaction outside of the // identity package — signing json.Marshal(tx) instead is a common mistake // that produces signatures VerifyTx will always reject. func TxSignBytes(tx *blockchain.Transaction) []byte { return txSignBytes(tx) } // txSignBytes returns the canonical bytes that are signed for a transaction. func txSignBytes(tx *blockchain.Transaction) []byte { data, _ := json.Marshal(struct { ID string `json:"id"` Type blockchain.EventType `json:"type"` From string `json:"from"` To string `json:"to"` Amount uint64 `json:"amount"` Fee uint64 `json:"fee"` Payload []byte `json:"payload"` Timestamp time.Time `json:"timestamp"` }{ tx.ID, tx.Type, tx.From, tx.To, tx.Amount, tx.Fee, tx.Payload, tx.Timestamp, }) return data } // VerifyTx verifies a transaction's Ed25519 signature. func VerifyTx(tx *blockchain.Transaction) error { ok, err := Verify(tx.From, txSignBytes(tx), tx.Signature) if err != nil { return err } if !ok { return errors.New("transaction signature invalid") } return nil }