github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/cryptocurrency.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/keybase/client/go/libkb"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  )
    13  
    14  type CryptocurrencyEngine struct {
    15  	libkb.Contextified
    16  	arg keybase1.RegisterAddressArg
    17  	res keybase1.RegisterAddressRes
    18  }
    19  
    20  func NewCryptocurrencyEngine(g *libkb.GlobalContext, arg keybase1.RegisterAddressArg) *CryptocurrencyEngine {
    21  	if arg.SigVersion == nil || libkb.SigVersion(*arg.SigVersion) == libkb.KeybaseNullSigVersion {
    22  		tmp := keybase1.SigVersion(libkb.GetDefaultSigVersion(g))
    23  		arg.SigVersion = &tmp
    24  	}
    25  	return &CryptocurrencyEngine{
    26  		Contextified: libkb.NewContextified(g),
    27  		arg:          arg,
    28  	}
    29  }
    30  
    31  func (e *CryptocurrencyEngine) Name() string {
    32  	return "Cryptocurrency"
    33  }
    34  
    35  func (e *CryptocurrencyEngine) Prereqs() Prereqs {
    36  	return Prereqs{
    37  		Device: true,
    38  	}
    39  }
    40  
    41  func (e *CryptocurrencyEngine) RequiredUIs() []libkb.UIKind {
    42  	return []libkb.UIKind{
    43  		libkb.LogUIKind,
    44  		libkb.SecretUIKind,
    45  	}
    46  }
    47  
    48  func (e *CryptocurrencyEngine) SubConsumers() []libkb.UIConsumer {
    49  	return []libkb.UIConsumer{}
    50  }
    51  
    52  func normalizeAddress(address string) string {
    53  	if len(address) <= 3 {
    54  		return address
    55  	}
    56  	switch strings.ToLower(address)[0:3] {
    57  	case "bc1", "zs1":
    58  		// bech32 addresses require that cases not be mixed
    59  		// (an error which will be caught downstream in validation),
    60  		// but the spec is otherwise case-insensitive. so if it's
    61  		// passed in as uppercase, then we downcase it right away to
    62  		// ensure that everything we do with the address (e.g. sign it into
    63  		// a sigchain link) will be consistent and checksum correctly.
    64  		if strings.ToUpper(address) == address {
    65  			return strings.ToLower(address)
    66  		}
    67  	}
    68  	return address
    69  }
    70  
    71  func (e *CryptocurrencyEngine) Run(m libkb.MetaContext) (err error) {
    72  	m.G().LocalSigchainGuard().Set(m.Ctx(), "CryptocurrencyEngine")
    73  	defer m.G().LocalSigchainGuard().Clear(m.Ctx(), "CryptocurrencyEngine")
    74  
    75  	defer m.Trace("CryptocurrencyEngine", &err)()
    76  
    77  	var typ libkb.CryptocurrencyType
    78  	e.arg.Address = normalizeAddress(e.arg.Address)
    79  	typ, _, err = libkb.CryptocurrencyParseAndCheck(e.arg.Address)
    80  	if err != nil {
    81  		return libkb.InvalidAddressError{Msg: err.Error()}
    82  	}
    83  
    84  	family := typ.ToCryptocurrencyFamily()
    85  	if len(e.arg.WantedFamily) > 0 && e.arg.WantedFamily != string(family) {
    86  		return libkb.InvalidAddressError{Msg: fmt.Sprintf("wanted coin type %q, but got %q", e.arg.WantedFamily, family)}
    87  	}
    88  
    89  	me, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(m))
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	cryptocurrencyLink := me.IDTable().ActiveCryptocurrency(typ.ToCryptocurrencyFamily())
    95  	if cryptocurrencyLink != nil && !e.arg.Force {
    96  		return libkb.ExistsError{Msg: string(family)}
    97  	}
    98  	var sigIDToRevoke keybase1.SigID
    99  	var lease *libkb.Lease
   100  	var merkleRoot *libkb.MerkleRoot
   101  	if cryptocurrencyLink != nil {
   102  		sigIDToRevoke = cryptocurrencyLink.GetSigID()
   103  		lease, merkleRoot, err = libkb.RequestDowngradeLeaseBySigIDs(m.Ctx(), m.G(), []keybase1.SigID{sigIDToRevoke})
   104  		if err != nil {
   105  			return err
   106  		}
   107  	}
   108  
   109  	ska := libkb.SecretKeyArg{
   110  		Me:      me,
   111  		KeyType: libkb.DeviceSigningKeyType,
   112  	}
   113  	sigKey, err := m.G().Keyrings.GetSecretKeyWithPrompt(m, m.SecretKeyPromptArg(ska, "to register a cryptocurrency address"))
   114  	if err != nil {
   115  		return err
   116  	}
   117  	if err = sigKey.CheckSecretKey(); err != nil {
   118  		return err
   119  	}
   120  	sigVersion := libkb.SigVersion(*e.arg.SigVersion)
   121  	claim, err := me.CryptocurrencySig(m, sigKey, e.arg.Address, typ, sigIDToRevoke, merkleRoot, sigVersion)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	sigInner, err := claim.J.Marshal()
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	sig, _, linkID, err := libkb.MakeSig(
   132  		m,
   133  		sigKey,
   134  		libkb.LinkTypeCryptocurrency,
   135  		sigInner,
   136  		libkb.SigHasRevokes(len(sigIDToRevoke) > 0),
   137  		keybase1.SeqType_PUBLIC,
   138  		libkb.SigIgnoreIfUnsupported(false),
   139  		me,
   140  		sigVersion,
   141  	)
   142  
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	kid := sigKey.GetKID()
   148  	args := libkb.HTTPArgs{
   149  		"sig":             libkb.S{Val: sig},
   150  		"signing_kid":     libkb.S{Val: kid.String()},
   151  		"is_remote_proof": libkb.B{Val: false},
   152  		"type":            libkb.S{Val: "cryptocurrency"},
   153  	}
   154  	if lease != nil {
   155  		args["downgrade_lease_id"] = libkb.S{Val: string(lease.LeaseID)}
   156  	}
   157  
   158  	if sigVersion == libkb.KeybaseSignatureV2 {
   159  		args["sig_inner"] = libkb.S{Val: string(sigInner)}
   160  	}
   161  
   162  	_, err = m.G().API.Post(m, libkb.APIArg{
   163  		Endpoint:    "sig/post",
   164  		SessionType: libkb.APISessionTypeREQUIRED,
   165  		Args:        args,
   166  	})
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	err = libkb.MerkleCheckPostedUserSig(m, me.GetUID(), claim.Seqno, linkID)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	e.res.Family = string(family)
   177  	e.res.Type = typ.String()
   178  
   179  	return nil
   180  }
   181  
   182  func (e *CryptocurrencyEngine) Result() keybase1.RegisterAddressRes {
   183  	return e.res
   184  }