243 lines
5.9 KiB
Go
243 lines
5.9 KiB
Go
package ledgerwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipfs/go-datastore"
|
|
"github.com/ipfs/go-datastore/query"
|
|
logging "github.com/ipfs/go-log/v2"
|
|
ledgerfil "github.com/whyrusleeping/ledger-filecoin-go"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/crypto"
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
|
)
|
|
|
|
var log = logging.Logger("wallet-ledger")
|
|
|
|
type LedgerWallet struct {
|
|
ds datastore.Datastore
|
|
}
|
|
|
|
func NewWallet(ds dtypes.MetadataDS) *LedgerWallet {
|
|
return &LedgerWallet{ds}
|
|
}
|
|
|
|
type LedgerKeyInfo struct {
|
|
Address address.Address
|
|
Path []uint32
|
|
}
|
|
|
|
var _ api.Wallet = (*LedgerWallet)(nil)
|
|
|
|
func (lw LedgerWallet) WalletSign(ctx context.Context, signer address.Address, toSign []byte, meta api.MsgMeta) (*crypto.Signature, error) {
|
|
ki, err := lw.getKeyInfo(ctx, signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fl, err := ledgerfil.FindLedgerFilecoinApp()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer fl.Close() // nolint:errcheck
|
|
if meta.Type != api.MTChainMsg {
|
|
return nil, fmt.Errorf("ledger can only sign chain messages")
|
|
}
|
|
|
|
{
|
|
var cmsg types.Message
|
|
if err := cmsg.UnmarshalCBOR(bytes.NewReader(meta.Extra)); err != nil {
|
|
return nil, xerrors.Errorf("unmarshalling message: %w", err)
|
|
}
|
|
|
|
_, bc, err := cid.CidFromBytes(toSign)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("getting cid from signing bytes: %w", err)
|
|
}
|
|
|
|
if !cmsg.Cid().Equals(bc) {
|
|
return nil, xerrors.Errorf("cid(meta.Extra).bytes() != toSign")
|
|
}
|
|
}
|
|
|
|
sig, err := fl.SignSECP256K1(ki.Path, meta.Extra)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &crypto.Signature{
|
|
Type: crypto.SigTypeSecp256k1,
|
|
Data: sig.SignatureBytes(),
|
|
}, nil
|
|
}
|
|
|
|
func (lw LedgerWallet) getKeyInfo(ctx context.Context, addr address.Address) (*LedgerKeyInfo, error) {
|
|
kib, err := lw.ds.Get(ctx, keyForAddr(addr))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var out LedgerKeyInfo
|
|
if err := json.Unmarshal(kib, &out); err != nil {
|
|
return nil, xerrors.Errorf("unmarshalling ledger key info: %w", err)
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func (lw LedgerWallet) WalletDelete(ctx context.Context, k address.Address) error {
|
|
return lw.ds.Delete(ctx, keyForAddr(k))
|
|
}
|
|
|
|
func (lw LedgerWallet) WalletExport(ctx context.Context, k address.Address) (*types.KeyInfo, error) {
|
|
return nil, fmt.Errorf("cannot export keys from ledger wallets")
|
|
}
|
|
|
|
func (lw LedgerWallet) WalletHas(ctx context.Context, k address.Address) (bool, error) {
|
|
_, err := lw.ds.Get(ctx, keyForAddr(k))
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if err == datastore.ErrNotFound {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func (lw LedgerWallet) WalletImport(ctx context.Context, kinfo *types.KeyInfo) (address.Address, error) {
|
|
var ki LedgerKeyInfo
|
|
if err := json.Unmarshal(kinfo.PrivateKey, &ki); err != nil {
|
|
return address.Undef, err
|
|
}
|
|
return lw.importKey(ctx, ki)
|
|
}
|
|
|
|
func (lw LedgerWallet) importKey(ctx context.Context, ki LedgerKeyInfo) (address.Address, error) {
|
|
if ki.Address == address.Undef {
|
|
return address.Undef, fmt.Errorf("no address given in imported key info")
|
|
}
|
|
if len(ki.Path) != filHdPathLen {
|
|
return address.Undef, fmt.Errorf("bad hd path len: %d, expected: %d", len(ki.Path), filHdPathLen)
|
|
}
|
|
bb, err := json.Marshal(ki)
|
|
if err != nil {
|
|
return address.Undef, xerrors.Errorf("marshaling key info: %w", err)
|
|
}
|
|
|
|
if err := lw.ds.Put(ctx, keyForAddr(ki.Address), bb); err != nil {
|
|
return address.Undef, err
|
|
}
|
|
|
|
return ki.Address, nil
|
|
}
|
|
|
|
func (lw LedgerWallet) WalletList(ctx context.Context) ([]address.Address, error) {
|
|
res, err := lw.ds.Query(ctx, query.Query{Prefix: dsLedgerPrefix})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Close() // nolint:errcheck
|
|
|
|
var out []address.Address
|
|
for {
|
|
res, ok := res.NextSync()
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
var ki LedgerKeyInfo
|
|
if err := json.Unmarshal(res.Value, &ki); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out = append(out, ki.Address)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
const hdHard = 0x80000000
|
|
|
|
var filHDBasePath = []uint32{hdHard | 44, hdHard | 461, hdHard, 0}
|
|
var filHdPathLen = 5
|
|
|
|
func (lw LedgerWallet) WalletNew(ctx context.Context, t types.KeyType) (address.Address, error) {
|
|
if t != types.KTSecp256k1Ledger {
|
|
return address.Undef, fmt.Errorf("unsupported key type: '%s', only '%s' supported",
|
|
t, types.KTSecp256k1Ledger)
|
|
}
|
|
|
|
res, err := lw.ds.Query(ctx, query.Query{Prefix: dsLedgerPrefix})
|
|
if err != nil {
|
|
return address.Undef, err
|
|
}
|
|
defer res.Close() // nolint:errcheck
|
|
|
|
var maxi int64 = -1
|
|
for {
|
|
res, ok := res.NextSync()
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
var ki LedgerKeyInfo
|
|
if err := json.Unmarshal(res.Value, &ki); err != nil {
|
|
return address.Undef, err
|
|
}
|
|
if i := ki.Path[filHdPathLen-1]; maxi == -1 || maxi < int64(i) {
|
|
maxi = int64(i)
|
|
}
|
|
}
|
|
|
|
fl, err := ledgerfil.FindLedgerFilecoinApp()
|
|
if err != nil {
|
|
return address.Undef, xerrors.Errorf("finding ledger: %w", err)
|
|
}
|
|
defer fl.Close() // nolint:errcheck
|
|
|
|
path := append(append([]uint32(nil), filHDBasePath...), uint32(maxi+1))
|
|
_, _, addr, err := fl.GetAddressPubKeySECP256K1(path)
|
|
if err != nil {
|
|
return address.Undef, xerrors.Errorf("getting public key from ledger: %w", err)
|
|
}
|
|
|
|
log.Warnf("creating key: %s, accept the key in ledger device", addr)
|
|
_, _, addr, err = fl.ShowAddressPubKeySECP256K1(path)
|
|
if err != nil {
|
|
return address.Undef, xerrors.Errorf("verifying public key with ledger: %w", err)
|
|
}
|
|
|
|
a, err := address.NewFromString(addr)
|
|
if err != nil {
|
|
return address.Undef, fmt.Errorf("parsing address: %w", err)
|
|
}
|
|
|
|
var lki LedgerKeyInfo
|
|
lki.Address = a
|
|
lki.Path = path
|
|
|
|
return lw.importKey(ctx, lki)
|
|
}
|
|
|
|
func (lw *LedgerWallet) Get() api.Wallet {
|
|
if lw == nil {
|
|
return nil
|
|
}
|
|
|
|
return lw
|
|
}
|
|
|
|
var dsLedgerPrefix = "/ledgerkey/"
|
|
|
|
func keyForAddr(addr address.Address) datastore.Key {
|
|
return datastore.NewKey(dsLedgerPrefix + addr.String())
|
|
}
|