Skip to content

Commit

Permalink
Merge pull request #2339 from oasislabs/david-yan/wallet-integration
Browse files Browse the repository at this point in the history
Ledger device support for CLI
  • Loading branch information
david-yan authored Dec 13, 2019
2 parents 82d8c42 + 78f59da commit 1117444
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 35 deletions.
2 changes: 1 addition & 1 deletion go/common/crypto/signature/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const (
)

// SignerFactoryCtor is an SignerFactory constructor.
type SignerFactoryCtor func(string, ...SignerRole) SignerFactory
type SignerFactoryCtor func(interface{}, ...SignerRole) SignerFactory

// SignerFactory is the opaque factory interface for Signers.
type SignerFactory interface {
Expand Down
10 changes: 9 additions & 1 deletion go/common/crypto/signature/signers/file/file_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const (
privateKeyPemType = "ED25519 PRIVATE KEY"

filePerm = 0600

// SignerName is the name used to identify the file backed signer.
SignerName = "file"
)

var (
Expand All @@ -44,7 +47,12 @@ var (

// NewFactory creates a new factory with the specified roles, with the
// specified dataDir.
func NewFactory(dataDir string, roles ...signature.SignerRole) signature.SignerFactory {
func NewFactory(config interface{}, roles ...signature.SignerRole) signature.SignerFactory {
dataDir, ok := config.(string)
if !ok {
panic("invalid file signer configuration provided")
}

return &Factory{
roles: append([]signature.SignerRole{}, roles...),
dataDir: dataDir,
Expand Down
140 changes: 140 additions & 0 deletions go/common/crypto/signature/signers/ledger/ledger_signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Package ledger provides a Ledger backed signer.
package ledger

import (
"fmt"
"io"

"github.com/oasislabs/oasis-core/go/common/crypto/signature"
ledgerCommon "github.com/oasislabs/oasis-core/go/common/ledger"
)

const (
// SignerName is the name used to identify the Ledger backed signer.
SignerName = "ledger"

// SignerPathCoinType is set to 118, the number owned by Cosmos via SLIP-0044 registration.
// TODO: Update this number after SLIP-0044 registration is complete.
SignerPathCoinType uint32 = 118
// SignerPathAccount is the account index used to sign transactions.
SignerPathAccount uint32 = 0
// SignerPathChange indicates an external chain.
SignerPathChange uint32 = 0
)

var (
_ signature.SignerFactoryCtor = NewFactory
_ signature.SignerFactory = (*Factory)(nil)
_ signature.Signer = (*Signer)(nil)

// SignerDerivationRootPath is the derivation path prefix used for
// generating the signature key on the Ledger device.
SignerDerivationRootPath = []uint32{ledgerCommon.PathPurpose, SignerPathCoinType, SignerPathAccount, SignerPathChange}

roleDerivationRootPaths = map[signature.SignerRole][]uint32{
signature.SignerEntity: SignerDerivationRootPath,
}
)

// Factory is a Ledger backed SignerFactory.
type Factory struct {
roles []signature.SignerRole
address string
index uint32
}

// FactoryConfig is the config necessary to create a Factory for Ledger Signers
type FactoryConfig struct {
Address string
Index uint32
}

// NewFactory creates a new factory with the specified roles.
func NewFactory(config interface{}, roles ...signature.SignerRole) signature.SignerFactory {
ledgerConfig, ok := config.(*FactoryConfig)
if !ok {
panic("invalid Ledger signer configuration provided")
}
return &Factory{
roles: append([]signature.SignerRole{}, roles...),
address: ledgerConfig.Address,
index: ledgerConfig.Index,
}
}

// EnsureRole ensures that the SignatureFactory is configured for the given
// role.
func (fac *Factory) EnsureRole(role signature.SignerRole) error {
for _, v := range fac.roles {
if v == role {
return nil
}
}
return signature.ErrRoleMismatch
}

// Generate has the same functionality as Load, since all keys are generated
// on the Ledger device.
func (fac *Factory) Generate(role signature.SignerRole, _rng io.Reader) (signature.Signer, error) {
return fac.Load(role)
}

// Load will create a Signer backed by a Ledger device by searching for
// a device with the expected address. If no address is provided, this will
// created a Signer with the first Ledger device it finds.
// The only role allowed is SignerEntity.
func (fac *Factory) Load(role signature.SignerRole) (signature.Signer, error) {
pathPrefix, ok := roleDerivationRootPaths[role]
if !ok {
return nil, fmt.Errorf("role %d is not supported when using the Ledger backed signer", role)
}
device, err := ledgerCommon.ConnectToDevice(fac.address)
if err != nil {
return nil, err
}

return &Signer{device, append(pathPrefix, fac.index), nil}, nil
}

// Signer is a Ledger backed Signer.
type Signer struct {
device *ledgerCommon.Device
path []uint32
publicKey *signature.PublicKey
}

// Public retrieves the public key from the Ledger device.
func (s *Signer) Public() signature.PublicKey {
if s.publicKey != nil {
return *s.publicKey
}

var pubKey signature.PublicKey
retrieved, err := s.device.GetPublicKeyEd25519(s.path)
if err != nil {
panic(fmt.Errorf("failed to retrieve public key from device: %w", err))
}
copy(pubKey[:], retrieved)
s.publicKey = &pubKey
return pubKey
}

// ContextSign generates a signature with the private key over the context and
// message.
func (s *Signer) ContextSign(context signature.Context, message []byte) ([]byte, error) {
preparedContext, err := signature.PrepareSignerContext(context)
if err != nil {
return nil, err
}
return s.device.SignEd25519(s.path, preparedContext, message)
}

// String returns the address of the account on the Ledger device.
func (s *Signer) String() string {
return fmt.Sprintf("[ledger signer: %s]", s.Public())
}

// Reset tears down the Signer.
func (s *Signer) Reset() {
s.device.Close()
}
39 changes: 39 additions & 0 deletions go/common/ledger/ledger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Packaged ledger contains the common constants and functions related to Ledger devices
package ledger

import (
ledger "github.com/zondax/ledger-oasis-go"
)

const (
// PathPurpose is set to 44 to indicate use of the BIP-0044 specification.
PathPurpose uint32 = 44

// ListingPathCoinType is set to 118, the number owned by Cosmos via SLIP-0044 registration.
// TODO: Update this number after SLIP-0044 registration is complete.
ListingPathCoinType uint32 = 118
// ListingPathAccount is the account index used to list and connect to Ledger devices by address.
ListingPathAccount uint32 = 0
// ListingPathChange indicates an external chain.
ListingPathChange uint32 = 0
// ListingPathIndex is the address index used to list and connect to Ledger devices by address.
ListingPathIndex uint32 = 0
)

var (
// ListingDerivationPath is the path used to list and connect to devices by address.
ListingDerivationPath = []uint32{PathPurpose, ListingPathCoinType, ListingPathAccount, ListingPathChange, ListingPathIndex}
)

// Device is a Ledger device.
type Device = ledger.LedgerOasis

// ListDevices will list Ledger devices by address, derived from ListingDerivationPath.
func ListDevices() {
ledger.ListOasisDevices(ListingDerivationPath)
}

// ConnectToDevice attempts to connect to a Ledger device by address, which is derived by ListingDerivationPath.
func ConnectToDevice(address string) (*Device, error) {
return ledger.ConnectLedgerOasisApp(address, ListingDerivationPath)
}
1 change: 1 addition & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ require (
github.com/uber/jaeger-client-go v2.16.0+incompatible
github.com/uber/jaeger-lib v2.0.0+incompatible // indirect
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc
github.com/zondax/ledger-oasis-go v0.2.0
gitlab.com/yawning/dynlib.git v0.0.0-20190911075527-1e6ab3739fd8
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
Expand Down
6 changes: 6 additions & 0 deletions go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,12 @@ github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
github.com/zondax/ledger-go v0.11.0 h1:EEqUh6eaZucWAaGo87G7sJiqRNJpzBZr+I9PpGgjjPg=
github.com/zondax/ledger-go v0.11.0/go.mod h1:NI6JDs8VWwgh+9Bf1vPZMm9Xufp2Q7Iwm2IzxJWzmus=
github.com/zondax/ledger-oasis-go v0.2.0 h1:t75E0Z9N0SuVn1ARkhGA3LHvesXZ1XgniR4mAfeHlTs=
github.com/zondax/ledger-oasis-go v0.2.0/go.mod h1:czCs1jEu/4KYz3mYgALCedjgKxaFygAQxJ1di+3hHoI=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
gitlab.com/yawning/dynlib.git v0.0.0-20190911075527-1e6ab3739fd8 h1:wrAqF2qnhhJpaRy6v+aJBLuKiUxqwHN4Pa+/d9Epe7c=
Expand Down
29 changes: 25 additions & 4 deletions go/oasis-node/cmd/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/oasislabs/oasis-core/go/common"
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
fileSigner "github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/file"
ledgerSigner "github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/ledger"

"github.com/oasislabs/oasis-core/go/common/entity"
"github.com/oasislabs/oasis-core/go/common/logging"
"github.com/oasislabs/oasis-core/go/common/sgx/ias"
Expand Down Expand Up @@ -57,6 +59,22 @@ func DataDirOrPwd() (string, error) {
return dataDir, nil
}

// SignerFactory returns the appropriate SignerFactory based on flags.
func SignerFactory(signerBackend string, signerDir string) (signature.SignerFactory, error) {
switch signerBackend {
case ledgerSigner.SignerName:
config := ledgerSigner.FactoryConfig{
Address: flags.SignerLedgerAddress(),
Index: flags.SignerLedgerIndex(),
}
return ledgerSigner.NewFactory(&config, signature.SignerEntity), nil
case fileSigner.SignerName:
return fileSigner.NewFactory(signerDir, signature.SignerEntity), nil
default:
return nil, fmt.Errorf("unsupported signer backend: %s", signerBackend)
}
}

// EarlyLogAndExit logs the error and exits.
//
// Note: This routine should only be used prior to the logging system
Expand Down Expand Up @@ -191,12 +209,15 @@ func GetInputReader(cmd *cobra.Command, cfg string) (io.ReadCloser, bool, error)
}

// LoadEntity loads the entity and it's signer.
func LoadEntity(dataDir string) (*entity.Entity, signature.Signer, error) {
func LoadEntity(signerBackend string, entityDir string) (*entity.Entity, signature.Signer, error) {
if flags.DebugTestEntity() {
return entity.TestEntity()
}

// TODO/hsm: Configure factory dynamically.
entitySignerFactory := fileSigner.NewFactory(dataDir, signature.SignerEntity)
return entity.Load(dataDir, entitySignerFactory)
factory, err := SignerFactory(signerBackend, entityDir)
if err != nil {
return nil, nil, err
}

return entity.Load(entityDir, factory)
}
11 changes: 9 additions & 2 deletions go/oasis-node/cmd/common/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ func GetTxNonceAndFee() (uint64, *transaction.Fee) {
}

func SignAndSaveTx(tx *transaction.Transaction) {
_, signer, err := cmdCommon.LoadEntity(cmdFlags.Entity())
entityDir, err := cmdFlags.SignerDirOrPwd()
if err != nil {
logger.Error("failed to retrieve signer dir",
"err", err,
)
os.Exit(1)
}
_, signer, err := cmdCommon.LoadEntity(cmdFlags.Signer(), entityDir)
if err != nil {
logger.Error("failed to load account entity",
"err", err,
Expand Down Expand Up @@ -124,6 +131,6 @@ func init() {
_ = viper.BindPFlags(TxFlags)
TxFlags.AddFlagSet(TxFileFlags)
TxFlags.AddFlagSet(cmdFlags.DebugTestEntityFlags)
TxFlags.AddFlagSet(cmdFlags.EntityFlags)
TxFlags.AddFlagSet(cmdFlags.SignerFlags)
TxFlags.AddFlagSet(cmdFlags.GenesisFileFlags)
}
Loading

0 comments on commit 1117444

Please sign in to comment.