forked from decred/dcrwallet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
multisig.go
215 lines (188 loc) · 6.24 KB
/
multisig.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wallet
import (
"errors"
"fmt"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrwallet/apperrors"
"github.com/decred/dcrwallet/wallet/internal/txsizes"
"github.com/decred/dcrwallet/wallet/txrules"
"github.com/decred/dcrwallet/wallet/udb"
"github.com/decred/dcrwallet/walletdb"
)
// MakeSecp256k1MultiSigScript creates a multi-signature script that can be
// redeemed with nRequired signatures of the passed keys and addresses. If the
// address is a P2PKH address, the associated pubkey is looked up by the wallet
// if possible, otherwise an error is returned for a missing pubkey.
//
// This function only works with secp256k1 pubkeys and P2PKH addresses derived
// from them.
func (w *Wallet) MakeSecp256k1MultiSigScript(secp256k1Addrs []dcrutil.Address, nRequired int) ([]byte, error) {
secp256k1PubKeys := make([]*dcrutil.AddressSecpPubKey, len(secp256k1Addrs))
var dbtx walletdb.ReadTx
var addrmgrNs walletdb.ReadBucket
defer func() {
if dbtx != nil {
dbtx.Rollback()
}
}()
// The address list will made up either of addreseses (pubkey hash), for
// which we need to look up the keys in wallet, straight pubkeys, or a
// mixture of the two.
for i, addr := range secp256k1Addrs {
switch addr := addr.(type) {
default:
return nil, errors.New("cannot make multisig script for " +
"a non-secp256k1 public key or P2PKH address")
case *dcrutil.AddressSecpPubKey:
secp256k1PubKeys[i] = addr
case *dcrutil.AddressPubKeyHash:
if addr.DSA(w.chainParams) != chainec.ECTypeSecp256k1 {
return nil, errors.New("cannot make multisig " +
"script for a non-secp256k1 P2PKH address")
}
if dbtx == nil {
var err error
dbtx, err = w.db.BeginReadTx()
if err != nil {
return nil, err
}
addrmgrNs = dbtx.ReadBucket(waddrmgrNamespaceKey)
}
addrInfo, err := w.Manager.Address(addrmgrNs, addr)
if err != nil {
return nil, err
}
serializedPubKey := addrInfo.(udb.ManagedPubKeyAddress).
PubKey().Serialize()
pubKeyAddr, err := dcrutil.NewAddressSecpPubKey(
serializedPubKey, w.chainParams)
if err != nil {
return nil, err
}
secp256k1PubKeys[i] = pubKeyAddr
}
}
return txscript.MultiSigScript(secp256k1PubKeys, nRequired)
}
// ImportP2SHRedeemScript adds a P2SH redeem script to the wallet.
func (w *Wallet) ImportP2SHRedeemScript(script []byte) (*dcrutil.AddressScriptHash, error) {
var p2shAddr *dcrutil.AddressScriptHash
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
err := w.TxStore.InsertTxScript(txmgrNs, script)
if err != nil {
return err
}
addrInfo, err := w.Manager.ImportScript(addrmgrNs, script)
if err != nil {
// Don't care if it's already there, but still have to
// set the p2shAddr since the address manager didn't
// return anything useful.
if apperrors.IsError(err, apperrors.ErrDuplicateAddress) {
// This function will never error as it always
// hashes the script to the correct length.
p2shAddr, _ = dcrutil.NewAddressScriptHash(script,
w.chainParams)
return nil
}
return err
}
p2shAddr = addrInfo.Address().(*dcrutil.AddressScriptHash)
return nil
})
return p2shAddr, err
}
// FetchP2SHMultiSigOutput fetches information regarding a wallet's P2SH
// multi-signature output.
func (w *Wallet) FetchP2SHMultiSigOutput(outPoint *wire.OutPoint) (*P2SHMultiSigOutput, error) {
var (
mso *udb.MultisigOut
redeemScript []byte
)
err := walletdb.View(w.db, func(tx walletdb.ReadTx) error {
txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
var err error
mso, err = w.TxStore.GetMultisigOutput(txmgrNs, outPoint)
if err != nil {
return err
}
redeemScript, err = w.TxStore.GetTxScript(txmgrNs, mso.ScriptHash[:])
if err != nil {
return err
}
// returns nil, nil when it successfully found no script. That error is
// only used to return early when the database is closed.
if redeemScript == nil {
return errors.New("script not found")
}
return nil
})
if err != nil {
return nil, err
}
p2shAddr, err := dcrutil.NewAddressScriptHashFromHash(
mso.ScriptHash[:], w.chainParams)
if err != nil {
return nil, err
}
multiSigOutput := P2SHMultiSigOutput{
OutPoint: *mso.OutPoint,
OutputAmount: mso.Amount,
ContainingBlock: BlockIdentity{
Hash: mso.BlockHash,
Height: int32(mso.BlockHeight),
},
P2SHAddress: p2shAddr,
RedeemScript: redeemScript,
M: mso.M,
N: mso.N,
Redeemer: nil,
}
if mso.Spent {
multiSigOutput.Redeemer = &OutputRedeemer{
TxHash: mso.SpentBy,
InputIndex: mso.SpentByIndex,
}
}
return &multiSigOutput, nil
}
// FetchAllRedeemScripts returns all P2SH redeem scripts saved by the wallet.
func (w *Wallet) FetchAllRedeemScripts() ([][]byte, error) {
var redeemScripts [][]byte
err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error {
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
var err error
redeemScripts, err = w.TxStore.StoredTxScripts(txmgrNs)
return err
})
return redeemScripts, err
}
// PrepareRedeemMultiSigOutTxOutput estimates the tx value for a MultiSigOutTx
// output and adds it
func (w *Wallet) PrepareRedeemMultiSigOutTxOutput(msgTx *wire.MsgTx, p2shOutput *P2SHMultiSigOutput, pkScript *[]byte) error {
scriptSizers := []txsizes.ScriptSizer{}
// generate the script sizers for the inputs
for range msgTx.TxIn {
scriptSizers = append(scriptSizers, txsizes.P2SHScriptSize)
}
// estimate the output fee
txOut := wire.NewTxOut(0, *pkScript)
feeSize := txsizes.EstimateSerializeSize(scriptSizers, []*wire.TxOut{txOut}, false)
feeEst := txrules.FeeForSerializeSize(w.RelayFee(), feeSize)
if feeEst >= p2shOutput.OutputAmount {
return fmt.Errorf("multisig out amt is too small "+
"(have %v, %v fee suggested)", p2shOutput.OutputAmount, feeEst)
}
toReceive := p2shOutput.OutputAmount - feeEst
// set the output value and add to the tx
txOut.Value = int64(toReceive)
msgTx.AddTxOut(txOut)
return nil
}