Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 0 additions & 138 deletions utils/crypto/keychain/keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,10 @@
package keychain

import (
"errors"
"fmt"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"
)

var (
_ Keychain = (*ledgerKeychain)(nil)
_ Signer = (*ledgerSigner)(nil)

ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0")
ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0")
ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses")
ErrInvalidNumSignatures = errors.New("incorrect number of signatures")
)

// Signer implements functions for a keychain to return its main address and
// to sign a hash
type Signer interface {
Expand All @@ -38,128 +25,3 @@ type Keychain interface {
// signer
Addresses() set.Set[ids.ShortID]
}

// ledgerKeychain is an abstraction of the underlying ledger hardware device,
// to be able to get a signer from a finite set of derived signers
type ledgerKeychain struct {
ledger Ledger
addrs set.Set[ids.ShortID]
addrToIdx map[ids.ShortID]uint32
}

// ledgerSigner is an abstraction of the underlying ledger hardware device,
// to be able sign for a specific address
type ledgerSigner struct {
ledger Ledger
idx uint32
addr ids.ShortID
}

// NewLedgerKeychain creates a new Ledger with [numToDerive] addresses.
func NewLedgerKeychain(l Ledger, numToDerive int) (Keychain, error) {
if numToDerive < 1 {
return nil, ErrInvalidNumAddrsToDerive
}

indices := make([]uint32, numToDerive)
for i := range indices {
indices[i] = uint32(i)
}

return NewLedgerKeychainFromIndices(l, indices)
}

// NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices].
func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (Keychain, error) {
if len(indices) == 0 {
return nil, ErrInvalidIndicesLength
}

addrs, err := l.Addresses(indices)
if err != nil {
return nil, err
}

if len(addrs) != len(indices) {
return nil, fmt.Errorf(
"%w. expected %d, got %d",
ErrInvalidNumAddrsDerived,
len(indices),
len(addrs),
)
}

addrsSet := set.Of(addrs...)

addrToIdx := map[ids.ShortID]uint32{}
for i := range indices {
addrToIdx[addrs[i]] = indices[i]
}

return &ledgerKeychain{
ledger: l,
addrs: addrsSet,
addrToIdx: addrToIdx,
}, nil
}

func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] {
return l.addrs
}

func (l *ledgerKeychain) Get(addr ids.ShortID) (Signer, bool) {
idx, ok := l.addrToIdx[addr]
if !ok {
return nil, false
}

return &ledgerSigner{
ledger: l.ledger,
idx: idx,
addr: addr,
}, true
}

// expects to receive a hash of the unsigned tx bytes
func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) {
// Sign using the address with index l.idx on the ledger device. The number
// of returned signatures should be the same length as the provided indices.
sigs, err := l.ledger.SignHash(b, []uint32{l.idx})
if err != nil {
return nil, err
}

if sigsLen := len(sigs); sigsLen != 1 {
return nil, fmt.Errorf(
"%w. expected 1, got %d",
ErrInvalidNumSignatures,
sigsLen,
)
}

return sigs[0], nil
}

// expects to receive the unsigned tx bytes
func (l *ledgerSigner) Sign(b []byte) ([]byte, error) {
// Sign using the address with index l.idx on the ledger device. The number
// of returned signatures should be the same length as the provided indices.
sigs, err := l.ledger.Sign(b, []uint32{l.idx})
if err != nil {
return nil, err
}

if sigsLen := len(sigs); sigsLen != 1 {
return nil, fmt.Errorf(
"%w. expected 1, got %d",
ErrInvalidNumSignatures,
sigsLen,
)
}

return sigs[0], nil
}

func (l *ledgerSigner) Address() ids.ShortID {
return l.addr
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package keychain
package ledger

import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/crypto/ledger"
"github.com/ava-labs/avalanchego/version"
)

Expand All @@ -17,3 +18,6 @@ type Ledger interface {
Sign(unsignedTxBytes []byte, addressIndices []uint32) ([][]byte, error)
Disconnect() error
}

// Verify that the ledger implementation satisfies the interface
var _ Ledger = (*ledger.Ledger)(nil)
153 changes: 153 additions & 0 deletions utils/crypto/keychain/ledger/ledger_keychain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package ledger

import (
"errors"
"fmt"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/crypto/keychain"
"github.com/ava-labs/avalanchego/utils/set"
)

var (
_ keychain.Keychain = (*KeyChain)(nil)
_ keychain.Signer = (*ledgerSigner)(nil)

ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0")
ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0")
ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses")
ErrInvalidNumSignatures = errors.New("incorrect number of signatures")
)

// KeyChain is an abstraction of the underlying ledger hardware device,
// to be able to get a signer from a finite set of derived signers
type KeyChain struct {
ledger Ledger
addrs set.Set[ids.ShortID]
addrToIdx map[ids.ShortID]uint32
}

// NewKeychain creates a new Ledger with [numToDerive] addresses.
func NewKeychain(l Ledger, numToDerive int) (*KeyChain, error) {
if numToDerive < 1 {
return nil, ErrInvalidNumAddrsToDerive
}

indices := make([]uint32, numToDerive)
for i := range indices {
indices[i] = uint32(i)
}

return NewKeychainFromIndices(l, indices)
}

// NewKeychainFromIndices creates a new Ledger with addresses taken from the given [indices].
func NewKeychainFromIndices(l Ledger, indices []uint32) (*KeyChain, error) {
if len(indices) == 0 {
return nil, ErrInvalidIndicesLength
}

addrs, err := l.Addresses(indices)
if err != nil {
return nil, err
}

if len(addrs) != len(indices) {
return nil, fmt.Errorf(
"%w. expected %d, got %d",
ErrInvalidNumAddrsDerived,
len(indices),
len(addrs),
)
}

addrsSet := set.Of(addrs...)

addrToIdx := map[ids.ShortID]uint32{}
for i := range indices {
addrToIdx[addrs[i]] = indices[i]
}

return &KeyChain{
ledger: l,
addrs: addrsSet,
addrToIdx: addrToIdx,
}, nil
}

// Addresses returns the set of addresses that this keychain can sign for.
func (l *KeyChain) Addresses() set.Set[ids.ShortID] {
return l.addrs
}

// Get returns a signer for the given address, if it exists in this keychain.
func (l *KeyChain) Get(addr ids.ShortID) (keychain.Signer, bool) {
idx, ok := l.addrToIdx[addr]
if !ok {
return nil, false
}

return &ledgerSigner{
ledger: l.ledger,
idx: idx,
addr: addr,
}, true
}

// ledgerSigner is an abstraction of the underlying ledger hardware device,
// to be able sign for a specific address
type ledgerSigner struct {
ledger Ledger
idx uint32
addr ids.ShortID
}

// SignHash signs the provided hash using the Ledger device.
// It expects to receive a hash of the unsigned transaction bytes.
func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) {
// Sign using the address with index l.idx on the ledger device. The number
// of returned signatures should be the same length as the provided indices.
sigs, err := l.ledger.SignHash(b, []uint32{l.idx})
if err != nil {
return nil, err
}

if sigsLen := len(sigs); sigsLen != 1 {
return nil, fmt.Errorf(
"%w. expected 1, got %d",
ErrInvalidNumSignatures,
sigsLen,
)
}

return sigs[0], nil
}

// Sign signs the provided unsigned transaction bytes using the Ledger device.
// It expects to receive the unsigned transaction bytes and returns the signature.
func (l *ledgerSigner) Sign(b []byte) ([]byte, error) {
// Sign using the address with index l.idx on the ledger device. The number
// of returned signatures should be the same length as the provided indices.
sigs, err := l.ledger.Sign(b, []uint32{l.idx})
if err != nil {
return nil, err
}

if sigsLen := len(sigs); sigsLen != 1 {
return nil, fmt.Errorf(
"%w. expected 1, got %d",
ErrInvalidNumSignatures,
sigsLen,
)
}

return sigs[0], nil
}

// Address returns the address associated with this signer.
func (l *ledgerSigner) Address() ids.ShortID {
return l.addr
}
Loading