mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 18:31:41 +00:00
@@ -0,0 +1,272 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user