// Copyright (c) 2020 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package webapi import ( "errors" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec" "github.com/decred/dcrd/dcrutil/v3" "github.com/decred/dcrd/hdkeychain/v3" ) type addressGenerator struct { external *hdkeychain.ExtendedKey netParams *chaincfg.Params lastUsedIndex uint32 } func newAddressGenerator(xPub string, netParams *chaincfg.Params, lastUsedIdx uint32) (*addressGenerator, error) { xPubKey, err := hdkeychain.NewKeyFromString(xPub, netParams) if err != nil { return nil, err } if xPubKey.IsPrivate() { return nil, errors.New("not a public key") } // Derive the extended key for the external chain. external, err := xPubKey.Child(0) if err != nil { return nil, err } return &addressGenerator{ external: external, netParams: netParams, lastUsedIndex: lastUsedIdx, }, nil } // NextAddress increments the last used address counter and returns a new // address. It will skip any address index which causes an ErrInvalidChild. // Not safe for concurrent access. func (m *addressGenerator) NextAddress() (string, uint32, error) { var key *hdkeychain.ExtendedKey var err error // There is a small chance that generating addresses for a given index can // fail with ErrInvalidChild, so loop until we find an index which works. // See the hdkeychain.ExtendedKey.Child docs for more info. invalidChildren := 0 for { m.lastUsedIndex++ key, err = m.external.Child(m.lastUsedIndex) if err != nil { if errors.Is(err, hdkeychain.ErrInvalidChild) { invalidChildren++ log.Warnf("Generating address for index %d failed: %v", m.lastUsedIndex, err) // If this happens 3 times, something is seriously wrong, so // return an error. if invalidChildren > 2 { return "", 0, errors.New("multiple invalid children generated for key") } continue } return "", 0, err } break } // Convert to a standard pay-to-pubkey-hash address. pkHash := dcrutil.Hash160(key.SerializedPubKey()) addr, err := dcrutil.NewAddressPubKeyHash(pkHash, m.netParams, dcrec.STEcdsaSecp256k1) if err != nil { return "", 0, err } return addr.String(), m.lastUsedIndex, nil }