multisig.go raw

   1  package wallet
   2  
   3  import (
   4  	"errors"
   5  	"github.com/p9c/p9/pkg/btcaddr"
   6  	
   7  	"github.com/p9c/p9/pkg/txscript"
   8  	"github.com/p9c/p9/pkg/waddrmgr"
   9  	"github.com/p9c/p9/pkg/walletdb"
  10  )
  11  
  12  // MakeMultiSigScript creates a multi-signature script that can be redeemed with nRequired signatures of the passed keys
  13  // and addresses. If the address is a P2PKH address, the associated pubkey is looked up by the wallet if possible,
  14  // otherwise an error is returned for a missing pubkey.
  15  //
  16  // This function only works with pubkeys and P2PKH addresses derived from them.
  17  func (w *Wallet) MakeMultiSigScript(addrs []btcaddr.Address, nRequired int) ([]byte, error) {
  18  	pubKeys := make([]*btcaddr.PubKey, len(addrs))
  19  	var dbtx walletdb.ReadTx
  20  	var addrmgrNs walletdb.ReadBucket
  21  	defer func() {
  22  		if dbtx != nil {
  23  			e := dbtx.Rollback()
  24  			if e != nil {
  25  			}
  26  		}
  27  	}()
  28  	// The address list will made up either of addreseses (pubkey hash), for which we need to look up the keys in
  29  	// wallet, straight pubkeys, or a mixture of the two.
  30  	for i, addr := range addrs {
  31  		switch addr := addr.(type) {
  32  		default:
  33  			return nil, errors.New(
  34  				"cannot make multisig script for " +
  35  					"a non-secp256k1 public key or P2PKH address",
  36  			)
  37  		case *btcaddr.PubKey:
  38  			pubKeys[i] = addr
  39  		case *btcaddr.PubKeyHash:
  40  			if dbtx == nil {
  41  				var e error
  42  				dbtx, e = w.db.BeginReadTx()
  43  				if e != nil {
  44  					return nil, e
  45  				}
  46  				addrmgrNs = dbtx.ReadBucket(waddrmgrNamespaceKey)
  47  			}
  48  			addrInfo, e := w.Manager.Address(addrmgrNs, addr)
  49  			if e != nil {
  50  				return nil, e
  51  			}
  52  			serializedPubKey := addrInfo.(waddrmgr.ManagedPubKeyAddress).
  53  				PubKey().SerializeCompressed()
  54  			pubKeyAddr, e := btcaddr.NewPubKey(
  55  				serializedPubKey, w.chainParams,
  56  			)
  57  			if e != nil {
  58  				return nil, e
  59  			}
  60  			pubKeys[i] = pubKeyAddr
  61  		}
  62  	}
  63  	return txscript.MultiSigScript(pubKeys, nRequired)
  64  }
  65  
  66  // ImportP2SHRedeemScript adds a P2SH redeem script to the wallet.
  67  func (w *Wallet) ImportP2SHRedeemScript(script []byte) (*btcaddr.ScriptHash, error) {
  68  	var p2shAddr *btcaddr.ScriptHash
  69  	e := walletdb.Update(
  70  		w.db, func(tx walletdb.ReadWriteTx) (e error) {
  71  			addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
  72  			// TODO(oga) blockstamp current block?
  73  			bs := &waddrmgr.BlockStamp{
  74  				Hash:   *w.ChainParams().GenesisHash,
  75  				Height: 0,
  76  			}
  77  			// As this is a regular P2SH script, we'll import this into the BIP0044 scope.
  78  			var bip44Mgr *waddrmgr.ScopedKeyManager
  79  			bip44Mgr, e = w.Manager.FetchScopedKeyManager(
  80  				waddrmgr.KeyScopeBIP0044,
  81  			)
  82  			if e != nil {
  83  				return e
  84  			}
  85  			addrInfo, e := bip44Mgr.ImportScript(addrmgrNs, script, bs)
  86  			if e != nil {
  87  				// Don't care if it's already there, but still have to set the p2shAddr since the address manager didn't
  88  				// return anything useful.
  89  				if waddrmgr.IsError(e, waddrmgr.ErrDuplicateAddress) {
  90  					// This function will never error as it always hashes the script to the correct length.
  91  					p2shAddr, _ = btcaddr.NewScriptHash(
  92  						script,
  93  						w.chainParams,
  94  					)
  95  					return nil
  96  				}
  97  				return e
  98  			}
  99  			p2shAddr = addrInfo.Address().(*btcaddr.ScriptHash)
 100  			return nil
 101  		},
 102  	)
 103  	return p2shAddr, e
 104  }
 105