You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
4.4 KiB

  1. package crypto
  2. import (
  3. "fmt"
  4. secp256k1 "github.com/btcsuite/btcd/btcec"
  5. ledger "github.com/zondax/ledger-goclient"
  6. )
  7. func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) {
  8. key, err := device.GetPublicKeySECP256K1(path)
  9. if err != nil {
  10. return nil, fmt.Errorf("error fetching public key: %v", err)
  11. }
  12. var p PubKeySecp256k1
  13. // Reserialize in the 33-byte compressed format
  14. cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
  15. copy(p[:], cmp.SerializeCompressed())
  16. pub = p
  17. return
  18. }
  19. func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) {
  20. bsig, err := device.SignSECP256K1(path, msg)
  21. if err != nil {
  22. return sig, err
  23. }
  24. sig = SignatureSecp256k1FromBytes(bsig)
  25. return
  26. }
  27. // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano
  28. // we cache the PubKey from the first call to use it later
  29. type PrivKeyLedgerSecp256k1 struct {
  30. // PubKey should be private, but we want to encode it via go-amino
  31. // so we can view the address later, even without having the ledger
  32. // attached
  33. CachedPubKey PubKey
  34. Path DerivationPath
  35. }
  36. // NewPrivKeyLedgerSecp256k1 will generate a new key and store the
  37. // public key for later use.
  38. func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) {
  39. var pk PrivKeyLedgerSecp256k1
  40. pk.Path = path
  41. // getPubKey will cache the pubkey for later use,
  42. // this allows us to return an error early if the ledger
  43. // is not plugged in
  44. _, err := pk.getPubKey()
  45. return &pk, err
  46. }
  47. // ValidateKey allows us to verify the sanity of a key
  48. // after loading it from disk
  49. func (pk PrivKeyLedgerSecp256k1) ValidateKey() error {
  50. // getPubKey will return an error if the ledger is not
  51. // properly set up...
  52. pub, err := pk.forceGetPubKey()
  53. if err != nil {
  54. return err
  55. }
  56. // verify this matches cached address
  57. if !pub.Equals(pk.CachedPubKey) {
  58. return fmt.Errorf("cached key does not match retrieved key")
  59. }
  60. return nil
  61. }
  62. // AssertIsPrivKeyInner fulfils PrivKey Interface
  63. func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
  64. // Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify
  65. // the same key when we reconnect to a ledger
  66. func (pk PrivKeyLedgerSecp256k1) Bytes() []byte {
  67. bin, err := cdc.MarshalBinaryBare(pk)
  68. if err != nil {
  69. panic(err)
  70. }
  71. return bin
  72. }
  73. // Sign calls the ledger and stores the PubKey for future use
  74. //
  75. // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes,
  76. // returning an error, so this should only trigger if the privkey is held
  77. // in memory for a while before use.
  78. func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) Signature {
  79. // oh, I wish there was better error handling
  80. dev, err := getLedger()
  81. if err != nil {
  82. panic(err)
  83. }
  84. sig, err := signLedgerSecp256k1(dev, pk.Path, msg)
  85. if err != nil {
  86. panic(err)
  87. }
  88. pub, err := pubkeyLedgerSecp256k1(dev, pk.Path)
  89. if err != nil {
  90. panic(err)
  91. }
  92. // if we have no pubkey yet, store it for future queries
  93. if pk.CachedPubKey == nil {
  94. pk.CachedPubKey = pub
  95. } else if !pk.CachedPubKey.Equals(pub) {
  96. panic("stored key does not match signing key")
  97. }
  98. return sig
  99. }
  100. // PubKey returns the stored PubKey
  101. func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey {
  102. key, err := pk.getPubKey()
  103. if err != nil {
  104. panic(err)
  105. }
  106. return key
  107. }
  108. // getPubKey reads the pubkey from cache or from the ledger itself
  109. // since this involves IO, it may return an error, which is not exposed
  110. // in the PubKey interface, so this function allows better error handling
  111. func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) {
  112. // if we have no pubkey, set it
  113. if pk.CachedPubKey == nil {
  114. pk.CachedPubKey, err = pk.forceGetPubKey()
  115. }
  116. return pk.CachedPubKey, err
  117. }
  118. // forceGetPubKey is like getPubKey but ignores any cached key
  119. // and ensures we get it from the ledger itself.
  120. func (pk PrivKeyLedgerSecp256k1) forceGetPubKey() (key PubKey, err error) {
  121. dev, err := getLedger()
  122. if err != nil {
  123. return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err)
  124. }
  125. key, err = pubkeyLedgerSecp256k1(dev, pk.Path)
  126. if err != nil {
  127. return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
  128. }
  129. return key, err
  130. }
  131. // Equals fulfils PrivKey Interface - makes sure both keys refer to the
  132. // same
  133. func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool {
  134. if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
  135. return pk.CachedPubKey.Equals(ledger.CachedPubKey)
  136. }
  137. return false
  138. }