Files
sonr/types/coins/derivation.go
T
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

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
}