mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
273 lines
7.5 KiB
Go
273 lines
7.5 KiB
Go
package coins
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/go-bip39"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
)
|
|
|
|
// CoinType constants for BIP44 derivation
|
|
const (
|
|
CoinTypeCosmos uint32 = 118 // Cosmos Hub
|
|
CoinTypeEthereum uint32 = 60 // Ethereum
|
|
CoinTypeSonr uint32 = 60 // Sonr uses Ethereum coin type
|
|
)
|
|
|
|
// DerivationPath represents a BIP44 derivation path
|
|
type DerivationPath struct {
|
|
Purpose uint32 // 44 for BIP44
|
|
CoinType uint32 // 118 for Cosmos, 60 for Ethereum
|
|
Account uint32 // Account index
|
|
Change uint32 // 0 for external, 1 for internal
|
|
AddressIndex uint32 // Address index
|
|
}
|
|
|
|
// String returns the string representation of the derivation path
|
|
func (dp DerivationPath) String() string {
|
|
return fmt.Sprintf(
|
|
"m/%d'/%d'/%d'/%d/%d",
|
|
dp.Purpose,
|
|
dp.CoinType,
|
|
dp.Account,
|
|
dp.Change,
|
|
dp.AddressIndex,
|
|
)
|
|
}
|
|
|
|
// DefaultCosmosPath returns the default derivation path for Cosmos
|
|
func DefaultCosmosPath() DerivationPath {
|
|
return DerivationPath{
|
|
Purpose: 44,
|
|
CoinType: CoinTypeCosmos,
|
|
Account: 0,
|
|
Change: 0,
|
|
AddressIndex: 0,
|
|
}
|
|
}
|
|
|
|
// DefaultEthereumPath returns the default derivation path for Ethereum
|
|
func DefaultEthereumPath() DerivationPath {
|
|
return DerivationPath{
|
|
Purpose: 44,
|
|
CoinType: CoinTypeEthereum,
|
|
Account: 0,
|
|
Change: 0,
|
|
AddressIndex: 0,
|
|
}
|
|
}
|
|
|
|
// SeedFromMnemonic generates a seed from a mnemonic phrase
|
|
func SeedFromMnemonic(mnemonic, passphrase string) ([]byte, error) {
|
|
if !bip39.IsMnemonicValid(mnemonic) {
|
|
return nil, fmt.Errorf("invalid mnemonic")
|
|
}
|
|
return bip39.NewSeed(mnemonic, passphrase), nil
|
|
}
|
|
|
|
// SeedFromEntropy generates a seed from entropy (DID + salt)
|
|
func SeedFromEntropy(did, salt string) []byte {
|
|
entropy := fmt.Sprintf("%s:%s", did, salt)
|
|
hash := sha256.Sum256([]byte(entropy))
|
|
return hash[:]
|
|
}
|
|
|
|
// MasterKeyFromSeed generates a master key from seed
|
|
func MasterKeyFromSeed(seed []byte) (*hdkeychain.ExtendedKey, error) {
|
|
// Use Bitcoin mainnet params for key derivation
|
|
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate master key: %w", err)
|
|
}
|
|
return masterKey, nil
|
|
}
|
|
|
|
// DeriveKey derives a key at the given path from master key
|
|
func DeriveKey(
|
|
masterKey *hdkeychain.ExtendedKey,
|
|
path DerivationPath,
|
|
) (*hdkeychain.ExtendedKey, error) {
|
|
// Derive purpose
|
|
purpose, err := masterKey.Derive(hdkeychain.HardenedKeyStart + path.Purpose)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive purpose: %w", err)
|
|
}
|
|
|
|
// Derive coin type
|
|
coinType, err := purpose.Derive(hdkeychain.HardenedKeyStart + path.CoinType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive coin type: %w", err)
|
|
}
|
|
|
|
// Derive account
|
|
account, err := coinType.Derive(hdkeychain.HardenedKeyStart + path.Account)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive account: %w", err)
|
|
}
|
|
|
|
// Derive change
|
|
change, err := account.Derive(path.Change)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive change: %w", err)
|
|
}
|
|
|
|
// Derive address index
|
|
addressKey, err := change.Derive(path.AddressIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive address index: %w", err)
|
|
}
|
|
|
|
return addressKey, nil
|
|
}
|
|
|
|
// CosmosAddressFromKey generates a Cosmos address from an extended key
|
|
func CosmosAddressFromKey(key *hdkeychain.ExtendedKey, prefix string) (string, error) {
|
|
pubKeyBytes, err := key.ECPubKey()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get public key: %w", err)
|
|
}
|
|
|
|
// Convert to Cosmos SDK format
|
|
pubKey := pubKeyBytes.SerializeCompressed()
|
|
|
|
// Generate address using SHA256 hash of public key
|
|
hash := sha256.Sum256(pubKey)
|
|
addr := sdk.AccAddress(hash[:20])
|
|
|
|
// Convert to bech32 format with custom prefix
|
|
if prefix == "" {
|
|
prefix = "cosmos"
|
|
}
|
|
|
|
bech32Addr, err := sdk.Bech32ifyAddressBytes(prefix, addr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to convert to bech32: %w", err)
|
|
}
|
|
|
|
return bech32Addr, nil
|
|
}
|
|
|
|
// EthereumAddressFromKey generates an Ethereum address from an extended key
|
|
func EthereumAddressFromKey(key *hdkeychain.ExtendedKey) (string, error) {
|
|
pubKeyBytes, err := key.ECPubKey()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get public key: %w", err)
|
|
}
|
|
|
|
// Convert to Ethereum format
|
|
pubKey := pubKeyBytes.ToECDSA()
|
|
address := crypto.PubkeyToAddress(*pubKey)
|
|
|
|
return address.Hex(), nil
|
|
}
|
|
|
|
// PrivateKeyFromExtendedKey extracts the private key from an extended key
|
|
func PrivateKeyFromExtendedKey(key *hdkeychain.ExtendedKey) (*ecdsa.PrivateKey, error) {
|
|
privKeyBytes, err := key.ECPrivKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get private key: %w", err)
|
|
}
|
|
|
|
return privKeyBytes.ToECDSA(), nil
|
|
}
|
|
|
|
// DeriveAddressesFromEntropy derives both Cosmos and Ethereum addresses from DID and salt
|
|
func DeriveAddressesFromEntropy(
|
|
did, salt, cosmosPrefix string,
|
|
) (cosmosAddr, ethAddr, derivationPath string, err error) {
|
|
// Generate seed from DID and salt
|
|
seed := SeedFromEntropy(did, salt)
|
|
|
|
// Generate master key
|
|
masterKey, err := MasterKeyFromSeed(seed)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("failed to generate master key: %w", err)
|
|
}
|
|
|
|
// Derive Cosmos address
|
|
cosmosPath := DefaultCosmosPath()
|
|
cosmosKey, err := DeriveKey(masterKey, cosmosPath)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("failed to derive Cosmos key: %w", err)
|
|
}
|
|
|
|
cosmosAddr, err = CosmosAddressFromKey(cosmosKey, cosmosPrefix)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("failed to generate Cosmos address: %w", err)
|
|
}
|
|
|
|
// Derive Ethereum address
|
|
ethPath := DefaultEthereumPath()
|
|
ethKey, err := DeriveKey(masterKey, ethPath)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("failed to derive Ethereum key: %w", err)
|
|
}
|
|
|
|
ethAddr, err = EthereumAddressFromKey(ethKey)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("failed to generate Ethereum address: %w", err)
|
|
}
|
|
|
|
derivationPath = cosmosPath.String()
|
|
return cosmosAddr, ethAddr, derivationPath, nil
|
|
}
|
|
|
|
// WalletFromEntropy creates a wallet with both Cosmos and Ethereum keys from entropy
|
|
func WalletFromEntropy(did, salt, cosmosPrefix string) (*Wallet, error) {
|
|
seed := SeedFromEntropy(did, salt)
|
|
|
|
masterKey, err := MasterKeyFromSeed(seed)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate master key: %w", err)
|
|
}
|
|
|
|
// Derive Cosmos key
|
|
cosmosPath := DefaultCosmosPath()
|
|
cosmosKey, err := DeriveKey(masterKey, cosmosPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive Cosmos key: %w", err)
|
|
}
|
|
|
|
cosmosPrivKey, err := PrivateKeyFromExtendedKey(cosmosKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Cosmos private key: %w", err)
|
|
}
|
|
|
|
cosmosAddr, err := CosmosAddressFromKey(cosmosKey, cosmosPrefix)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate Cosmos address: %w", err)
|
|
}
|
|
|
|
// Derive Ethereum key
|
|
ethPath := DefaultEthereumPath()
|
|
ethKey, err := DeriveKey(masterKey, ethPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to derive Ethereum key: %w", err)
|
|
}
|
|
|
|
ethPrivKey, err := PrivateKeyFromExtendedKey(ethKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Ethereum private key: %w", err)
|
|
}
|
|
|
|
ethAddr, err := EthereumAddressFromKey(ethKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate Ethereum address: %w", err)
|
|
}
|
|
|
|
return &Wallet{
|
|
DID: did,
|
|
Salt: salt,
|
|
CosmosAddress: cosmosAddr,
|
|
EthereumAddress: ethAddr,
|
|
CosmosPrivKey: cosmosPrivKey,
|
|
EthereumPrivKey: ethPrivKey,
|
|
DerivationPath: cosmosPath.String(),
|
|
}, nil
|
|
}
|