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