1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-08 05:57:03 +09:00
any-sync/util/crypto/mnemonic.go
2024-05-24 17:04:34 +01:00

215 lines
4.7 KiB
Go

package crypto
import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"
"github.com/anyproto/go-slip10"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/tyler-smith/go-bip39"
"github.com/anyproto/any-sync/util/ethereum/accounts"
)
var (
ErrInvalidWordCount = errors.New("error invalid word count for mnemonic")
ErrInvalidMnemonic = errors.New("error invalid mnemonic")
)
const (
anytypeAccountOldPrefix = "m/44'/607'"
// https://github.com/satoshilabs/slips/blob/master/slip-0044.md
anytypeAccountNewPrefix = "m/44'/2046'"
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// standard Ethereum path (MetaMask, MyEtherWallet, etc) is
// "m/44'/60'/0'/0/0" for first account
// "m/44'/60'/0'/0/1" for second account, etc
defaultEthereumDerivation = "m/44'/60'/0'/0"
)
type DerivationResult struct {
// m/44'/code'/index'
MasterKey PrivKey
// m/44'/code'/index'/0'
Identity PrivKey
OldAccountKey PrivKey
MasterNode slip10.Node
// Anytype uses ED25519
// Ethereum and Bitcoin use ECDSA secp256k1 elliptic curves
//
// this key is used to sign ethereum transactions to use Any Naming Service
// same mnemonic/seed phrase is used as for AnyType identity
// m/44'/60'/0'/0/index
EthereumIdentity ecdsa.PrivateKey
}
type MnemonicGenerator struct {
mnemonic string
}
func NewMnemonicGenerator() MnemonicGenerator {
return MnemonicGenerator{}
}
type Mnemonic string
func (g MnemonicGenerator) WithWordCount(wc int) (Mnemonic, error) {
size := 0
switch wc {
case 12:
size = 128
case 15:
size = 160
case 18:
size = 192
case 21:
size = 224
case 24:
size = 256
default:
return "", ErrInvalidWordCount
}
return g.WithRandomEntropy(size)
}
func (g MnemonicGenerator) WithRandomEntropy(size int) (Mnemonic, error) {
entropy, err := bip39.NewEntropy(size)
if err != nil {
return "", err
}
mnemonic, err := bip39.NewMnemonic(entropy)
if err != nil {
return "", err
}
return Mnemonic(mnemonic), nil
}
func (g MnemonicGenerator) WithEntropy(b []byte) (Mnemonic, error) {
mnemonic, err := bip39.NewMnemonic(b)
if err != nil {
return "", err
}
return Mnemonic(mnemonic), nil
}
func (m Mnemonic) deriveForPath(onlyMaster bool, index uint32, path string) (res DerivationResult, err error) {
seed, err := m.Seed()
if err != nil {
return
}
prefixNode, err := slip10.DeriveForPath(path, seed)
if err != nil {
return
}
// m/44'/code'/index'
res.MasterNode, err = prefixNode.Derive(slip10.FirstHardenedIndex + index)
if err != nil {
return
}
res.MasterKey, err = genKey(res.MasterNode)
if err != nil || onlyMaster {
return
}
// m/44'/code'/index'/0'
identityNode, err := res.MasterNode.Derive(slip10.FirstHardenedIndex)
if err != nil {
return
}
res.Identity, err = genKey(identityNode)
return
}
func (m Mnemonic) DeriveKeys(index uint32) (res DerivationResult, err error) {
oldRes, err := m.deriveForPath(true, index, anytypeAccountOldPrefix)
if err != nil {
return
}
res, err = m.deriveForPath(false, index, anytypeAccountNewPrefix)
if err != nil {
return
}
res.OldAccountKey = oldRes.MasterKey
// now derive ethereum key
pk, err := m.ethereumKeyFromMnemonic(index, defaultEthereumDerivation)
if err != nil {
return
}
res.EthereumIdentity = *pk
return
}
func (m Mnemonic) Seed() ([]byte, error) {
seed, err := bip39.NewSeedWithErrorChecking(string(m), "")
if err != nil {
if err == bip39.ErrInvalidMnemonic {
return nil, ErrInvalidMnemonic
}
return nil, err
}
return seed, nil
}
func (m Mnemonic) Bytes() ([]byte, error) {
return bip39.MnemonicToByteArray(string(m), true)
}
func genKey(node slip10.Node) (key PrivKey, err error) {
reader := bytes.NewReader(node.RawSeed())
key, _, err = GenerateEd25519Key(reader)
return
}
func derivePrivateKey(masterKey *hdkeychain.ExtendedKey, path accounts.DerivationPath) (*ecdsa.PrivateKey, error) {
var err error
key := masterKey
for _, n := range path {
key, err = key.DeriveNonStandard(n)
if err != nil {
return nil, err
}
}
privateKey, err := key.ECPrivKey()
privateKeyECDSA := privateKey.ToECDSA()
if err != nil {
return nil, err
}
return privateKeyECDSA, nil
}
func (m Mnemonic) ethereumKeyFromMnemonic(index uint32, path string) (pk *ecdsa.PrivateKey, err error) {
seed, err := m.Seed()
if err != nil {
return
}
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
if err != nil {
return nil, err
}
// m/44'/60'/0'/0/0 for first account
// m/44'/60'/0'/0/1 for second account, etc
fullPath := fmt.Sprintf("%s/%d", path, index)
p, err := accounts.ParseDerivationPath(fullPath)
if err != nil {
panic(err)
}
pk, err = derivePrivateKey(masterKey, p)
if err != nil {
return nil, err
}
return pk, nil
}