github.com/MetalBlockchain/metalgo@v1.11.9/utils/crypto/keychain/keychain.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package keychain
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/metalgo/ids"
    11  	"github.com/MetalBlockchain/metalgo/utils/set"
    12  )
    13  
    14  var (
    15  	_ Keychain = (*ledgerKeychain)(nil)
    16  	_ Signer   = (*ledgerSigner)(nil)
    17  
    18  	ErrInvalidIndicesLength    = errors.New("number of indices should be greater than 0")
    19  	ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0")
    20  	ErrInvalidNumAddrsDerived  = errors.New("incorrect number of ledger derived addresses")
    21  	ErrInvalidNumSignatures    = errors.New("incorrect number of signatures")
    22  )
    23  
    24  // Signer implements functions for a keychain to return its main address and
    25  // to sign a hash
    26  type Signer interface {
    27  	SignHash([]byte) ([]byte, error)
    28  	Sign([]byte) ([]byte, error)
    29  	Address() ids.ShortID
    30  }
    31  
    32  // Keychain maintains a set of addresses together with their corresponding
    33  // signers
    34  type Keychain interface {
    35  	// The returned Signer can provide a signature for [addr]
    36  	Get(addr ids.ShortID) (Signer, bool)
    37  	// Returns the set of addresses for which the accessor keeps an associated
    38  	// signer
    39  	Addresses() set.Set[ids.ShortID]
    40  }
    41  
    42  // ledgerKeychain is an abstraction of the underlying ledger hardware device,
    43  // to be able to get a signer from a finite set of derived signers
    44  type ledgerKeychain struct {
    45  	ledger    Ledger
    46  	addrs     set.Set[ids.ShortID]
    47  	addrToIdx map[ids.ShortID]uint32
    48  }
    49  
    50  // ledgerSigner is an abstraction of the underlying ledger hardware device,
    51  // to be able sign for a specific address
    52  type ledgerSigner struct {
    53  	ledger Ledger
    54  	idx    uint32
    55  	addr   ids.ShortID
    56  }
    57  
    58  // NewLedgerKeychain creates a new Ledger with [numToDerive] addresses.
    59  func NewLedgerKeychain(l Ledger, numToDerive int) (Keychain, error) {
    60  	if numToDerive < 1 {
    61  		return nil, ErrInvalidNumAddrsToDerive
    62  	}
    63  
    64  	indices := make([]uint32, numToDerive)
    65  	for i := range indices {
    66  		indices[i] = uint32(i)
    67  	}
    68  
    69  	return NewLedgerKeychainFromIndices(l, indices)
    70  }
    71  
    72  // NewLedgerKeychainFromIndices creates a new Ledger with addresses taken from the given [indices].
    73  func NewLedgerKeychainFromIndices(l Ledger, indices []uint32) (Keychain, error) {
    74  	if len(indices) == 0 {
    75  		return nil, ErrInvalidIndicesLength
    76  	}
    77  
    78  	addrs, err := l.Addresses(indices)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	if len(addrs) != len(indices) {
    84  		return nil, fmt.Errorf(
    85  			"%w. expected %d, got %d",
    86  			ErrInvalidNumAddrsDerived,
    87  			len(indices),
    88  			len(addrs),
    89  		)
    90  	}
    91  
    92  	addrsSet := set.Of(addrs...)
    93  
    94  	addrToIdx := map[ids.ShortID]uint32{}
    95  	for i := range indices {
    96  		addrToIdx[addrs[i]] = indices[i]
    97  	}
    98  
    99  	return &ledgerKeychain{
   100  		ledger:    l,
   101  		addrs:     addrsSet,
   102  		addrToIdx: addrToIdx,
   103  	}, nil
   104  }
   105  
   106  func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] {
   107  	return l.addrs
   108  }
   109  
   110  func (l *ledgerKeychain) Get(addr ids.ShortID) (Signer, bool) {
   111  	idx, ok := l.addrToIdx[addr]
   112  	if !ok {
   113  		return nil, false
   114  	}
   115  
   116  	return &ledgerSigner{
   117  		ledger: l.ledger,
   118  		idx:    idx,
   119  		addr:   addr,
   120  	}, true
   121  }
   122  
   123  // expects to receive a hash of the unsigned tx bytes
   124  func (l *ledgerSigner) SignHash(b []byte) ([]byte, error) {
   125  	// Sign using the address with index l.idx on the ledger device. The number
   126  	// of returned signatures should be the same length as the provided indices.
   127  	sigs, err := l.ledger.SignHash(b, []uint32{l.idx})
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	if sigsLen := len(sigs); sigsLen != 1 {
   133  		return nil, fmt.Errorf(
   134  			"%w. expected 1, got %d",
   135  			ErrInvalidNumSignatures,
   136  			sigsLen,
   137  		)
   138  	}
   139  
   140  	return sigs[0], err
   141  }
   142  
   143  // expects to receive the unsigned tx bytes
   144  func (l *ledgerSigner) Sign(b []byte) ([]byte, error) {
   145  	// Sign using the address with index l.idx on the ledger device. The number
   146  	// of returned signatures should be the same length as the provided indices.
   147  	sigs, err := l.ledger.Sign(b, []uint32{l.idx})
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if sigsLen := len(sigs); sigsLen != 1 {
   153  		return nil, fmt.Errorf(
   154  			"%w. expected 1, got %d",
   155  			ErrInvalidNumSignatures,
   156  			sigsLen,
   157  		)
   158  	}
   159  
   160  	return sigs[0], err
   161  }
   162  
   163  func (l *ledgerSigner) Address() ids.ShortID {
   164  	return l.addr
   165  }