OSDN Git Service

Modify the dependency on path
[bytom/vapor.git] / api / main_transact.go
1 package api
2
3 import (
4         "bytes"
5         "crypto/hmac"
6         "crypto/sha256"
7         "time"
8
9         log "github.com/sirupsen/logrus"
10
11         "github.com/vapor/account"
12         "github.com/vapor/blockchain/txbuilder"
13         "github.com/vapor/blockchain/txbuilder/mainchain"
14         "github.com/vapor/common"
15         "github.com/vapor/consensus"
16         "github.com/vapor/crypto/ed25519/chainkd"
17         chainjson "github.com/vapor/encoding/json"
18         "github.com/vapor/errors"
19         "github.com/vapor/protocol/bc"
20         "github.com/vapor/protocol/bc/types"
21         "github.com/vapor/protocol/bc/types/bytom"
22         bytomtypes "github.com/vapor/protocol/bc/types/bytom/types"
23         "github.com/vapor/protocol/vm/vmutil"
24 )
25
26 func (a *API) buildMainChainTxForContract(ins struct {
27         Utxo           account.UTXO       `json:"utxo"`
28         Tx             types.Tx           `json:"raw_transaction"`
29         RootXPubs      []chainkd.XPub     `json:"root_xpubs"`
30         ControlProgram string             `json:"control_program"`
31         ClaimScript    chainjson.HexBytes `json:"claim_script"`
32 }) Response {
33
34         var xpubs []chainkd.XPub
35         for _, xpub := range ins.RootXPubs {
36                 // pub + scriptPubKey 生成一个随机数A
37                 var tmp [32]byte
38                 h := hmac.New(sha256.New, xpub[:])
39                 h.Write(ins.ClaimScript)
40                 tweak := h.Sum(tmp[:])
41                 // pub +  A 生成一个新的公钥pub_new
42                 chaildXPub := xpub.Child(tweak)
43                 xpubs = append(xpubs, chaildXPub)
44         }
45
46         txInput, sigInst, err := contractToInputs(a, &ins.Utxo, xpubs)
47         builder := mainchain.NewBuilder(time.Now())
48         builder.AddInput(txInput, sigInst)
49         changeAmount := uint64(0)
50         retire := false
51         for _, key := range ins.Tx.GetResultIds() {
52                 output, err := ins.Tx.Retire(*key)
53                 if err != nil {
54                         log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
55                         continue
56                 }
57                 retire = true
58                 var controlProgram []byte
59                 retBool := true
60                 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
61                         return NewErrorResponse(errors.New("The corresponding input cannot be found"))
62                 }
63
64                 assetID := bytom.AssetID{
65                         V0: output.Source.Value.AssetId.GetV0(),
66                         V1: output.Source.Value.AssetId.GetV1(),
67                         V2: output.Source.Value.AssetId.GetV2(),
68                         V3: output.Source.Value.AssetId.GetV3(),
69                 }
70                 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
71                 builder.AddOutput(out)
72                 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
73
74         }
75
76         if !retire {
77                 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
78         }
79
80         if changeAmount > 0 {
81                 u := ins.Utxo
82                 assetID := bytom.AssetID{
83                         V0: u.AssetID.GetV0(),
84                         V1: u.AssetID.GetV1(),
85                         V2: u.AssetID.GetV2(),
86                         V3: u.AssetID.GetV3(),
87                 }
88                 out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
89                 builder.AddOutput(out)
90         }
91
92         tmpl, tx, err := builder.Build()
93         if err != nil {
94                 return NewErrorResponse(err)
95         }
96
97         for i, out := range tmpl.Transaction.Outputs {
98                 if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
99                         //tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
100                         tx.Outputs[i].Amount = changeAmount - 100000000
101                 }
102         }
103         tmpl.Transaction = bytomtypes.NewTx(*tx)
104         return NewSuccessResponse(tmpl)
105 }
106
107 func (a *API) buildMainChainTx(ins struct {
108         Utxo           account.UTXO       `json:"utxo"`
109         Tx             types.Tx           `json:"raw_transaction"`
110         RootXPubs      []chainkd.XPub     `json:"root_xpubs"`
111         ControlProgram string             `json:"control_program"`
112         ClaimScript    chainjson.HexBytes `json:"claim_script"`
113 }) Response {
114
115         var xpubs []chainkd.XPub
116         for _, xpub := range ins.RootXPubs {
117                 // pub + scriptPubKey 生成一个随机数A
118                 var tmp [32]byte
119                 h := hmac.New(sha256.New, xpub[:])
120                 h.Write(ins.ClaimScript)
121                 tweak := h.Sum(tmp[:])
122                 // pub +  A 生成一个新的公钥pub_new
123                 chaildXPub := xpub.Child(tweak)
124                 xpubs = append(xpubs, chaildXPub)
125         }
126
127         txInput, sigInst, err := utxoToInputs(xpubs, &ins.Utxo)
128         if err != nil {
129                 return NewErrorResponse(err)
130         }
131
132         builder := mainchain.NewBuilder(time.Now())
133         builder.AddInput(txInput, sigInst)
134         changeAmount := uint64(0)
135         retire := false
136         for _, key := range ins.Tx.GetResultIds() {
137                 output, err := ins.Tx.Retire(*key)
138                 if err != nil {
139                         log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
140                         continue
141                 }
142                 retire = true
143                 var controlProgram []byte
144                 retBool := true
145                 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
146                         return NewErrorResponse(errors.New("The corresponding input cannot be found"))
147                 }
148
149                 assetID := bytom.AssetID{
150                         V0: output.Source.Value.AssetId.GetV0(),
151                         V1: output.Source.Value.AssetId.GetV1(),
152                         V2: output.Source.Value.AssetId.GetV2(),
153                         V3: output.Source.Value.AssetId.GetV3(),
154                 }
155                 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
156                 builder.AddOutput(out)
157                 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
158
159         }
160
161         if !retire {
162                 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
163         }
164
165         if changeAmount > 0 {
166                 u := ins.Utxo
167                 assetID := bytom.AssetID{
168                         V0: u.AssetID.GetV0(),
169                         V1: u.AssetID.GetV1(),
170                         V2: u.AssetID.GetV2(),
171                         V3: u.AssetID.GetV3(),
172                 }
173                 out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
174                 builder.AddOutput(out)
175         }
176
177         tmpl, tx, err := builder.Build()
178         if err != nil {
179                 return NewErrorResponse(err)
180         }
181         //交易费估算
182         txGasResp, err := EstimateTxGasForMainchain(*tmpl)
183         if err != nil {
184                 return NewErrorResponse(err)
185         }
186         for i, out := range tmpl.Transaction.Outputs {
187                 if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
188                         tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
189                 }
190         }
191         tmpl.Transaction = bytomtypes.NewTx(*tx)
192         return NewSuccessResponse(tmpl)
193 }
194
195 //
196 func getInput(entry map[bc.Hash]bc.Entry, outputID bc.Hash, controlProgram string) ([]byte, bool) {
197         output := entry[outputID].(*bc.Retirement)
198         mux := entry[*output.Source.Ref].(*bc.Mux)
199
200         for _, valueSource := range mux.GetSources() {
201                 spend := entry[*valueSource.Ref].(*bc.Spend)
202                 prevout := entry[*spend.SpentOutputId].(*bc.Output)
203
204                 var ctrlProgram chainjson.HexBytes
205                 ctrlProgram = prevout.ControlProgram.Code
206                 tmp, _ := ctrlProgram.MarshalText()
207                 if string(tmp) == controlProgram {
208                         return ctrlProgram, true
209                 }
210         }
211         return nil, false
212 }
213
214 // UtxoToInputs convert an utxo to the txinput
215 func utxoToInputs(xpubs []chainkd.XPub, u *account.UTXO) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
216         sourceID := bytom.Hash{
217                 V0: u.SourceID.GetV0(),
218                 V1: u.SourceID.GetV1(),
219                 V2: u.SourceID.GetV2(),
220                 V3: u.SourceID.GetV3(),
221         }
222
223         assetID := bytom.AssetID{
224                 V0: u.AssetID.GetV0(),
225                 V1: u.AssetID.GetV1(),
226                 V2: u.AssetID.GetV2(),
227                 V3: u.AssetID.GetV3(),
228         }
229
230         txInput := bytomtypes.NewSpendInput(nil, sourceID, assetID, u.Amount, u.SourcePos, u.ControlProgram)
231         sigInst := &mainchain.SigningInstruction{}
232         quorum := len(xpubs)
233         if u.Address == "" {
234                 sigInst.AddWitnessKeys(xpubs, quorum)
235                 return txInput, sigInst, nil
236         }
237
238         address, err := common.DecodeBytomAddress(u.Address, &consensus.ActiveNetParams)
239         if err != nil {
240                 return nil, nil, err
241         }
242
243         sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum)
244
245         switch address.(type) {
246         case *common.AddressWitnessPubKeyHash:
247                 derivedPK := xpubs[0].PublicKey()
248                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness([]byte(derivedPK)))
249
250         case *common.AddressWitnessScriptHash:
251                 derivedXPubs := xpubs
252                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
253                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum)
254                 if err != nil {
255                         return nil, nil, err
256                 }
257                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(script))
258
259         default:
260                 return nil, nil, errors.New("unsupport address type")
261         }
262
263         return txInput, sigInst, nil
264 }
265
266 func contractToInputs(a *API, u *account.UTXO, xpubs []chainkd.XPub) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
267         sourceID := bytom.Hash{
268                 V0: u.SourceID.GetV0(),
269                 V1: u.SourceID.GetV1(),
270                 V2: u.SourceID.GetV2(),
271                 V3: u.SourceID.GetV3(),
272         }
273
274         assetID := bytom.AssetID{
275                 V0: u.AssetID.GetV0(),
276                 V1: u.AssetID.GetV1(),
277                 V2: u.AssetID.GetV2(),
278                 V3: u.AssetID.GetV3(),
279         }
280
281         txInput := bytomtypes.NewSpendInput(nil, sourceID, assetID, u.Amount, u.SourcePos, u.ControlProgram)
282         sigInst := &mainchain.SigningInstruction{}
283         for _, xpub := range xpubs {
284                 xpubsTmp := []chainkd.XPub{xpub}
285                 sigInst.AddRawWitnessKeysWithoutPath(xpubsTmp, 1)
286         }
287         return txInput, sigInst, nil
288 }
289
290 type signRespForMainchain struct {
291         Tx           *mainchain.Template `json:"transaction"`
292         SignComplete bool                `json:"sign_complete"`
293 }
294
295 func (a *API) signWithKey(ins struct {
296         Xprv        string             `json:"xprv"`
297         XPub        chainkd.XPub       `json:"xpub"`
298         Txs         mainchain.Template `json:"transaction"`
299         ClaimScript chainjson.HexBytes `json:"claim_script"`
300 }) Response {
301         xprv := &chainkd.XPrv{}
302         if err := xprv.UnmarshalText([]byte(ins.Xprv)); err != nil {
303                 return NewErrorResponse(err)
304         }
305         // pub + scriptPubKey 生成一个随机数A
306         var tmp [32]byte
307         h := hmac.New(sha256.New, ins.XPub[:])
308         h.Write(ins.ClaimScript)
309         tweak := h.Sum(tmp[:])
310         // pub +  A 生成一个新的公钥pub_new
311         privateKey := xprv.Child(tweak, false)
312
313         if err := sign(&ins.Txs, privateKey); err != nil {
314                 return NewErrorResponse(err)
315         }
316         log.Info("Sign Transaction complete.")
317         log.Info(mainchain.SignProgress(&ins.Txs))
318         return NewSuccessResponse(&signRespForMainchain{Tx: &ins.Txs, SignComplete: mainchain.SignProgress(&ins.Txs)})
319 }
320
321 func sign(tmpl *mainchain.Template, xprv chainkd.XPrv) error {
322         for i, sigInst := range tmpl.SigningInstructions {
323                 for j, wc := range sigInst.WitnessComponents {
324                         switch sw := wc.(type) {
325                         case *mainchain.SignatureWitness:
326                                 err := sw.Sign(tmpl, uint32(i), xprv)
327                                 if err != nil {
328                                         return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
329                                 }
330                         case *mainchain.RawTxSigWitness:
331                                 err := sw.Sign(tmpl, uint32(i), xprv)
332                                 if err != nil {
333                                         return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
334                                 }
335                         }
336                 }
337         }
338         return materializeWitnesses(tmpl)
339 }
340
341 func materializeWitnesses(txTemplate *mainchain.Template) error {
342         msg := txTemplate.Transaction
343
344         if msg == nil {
345                 return errors.Wrap(txbuilder.ErrMissingRawTx)
346         }
347
348         if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
349                 return errors.Wrap(txbuilder.ErrBadInstructionCount)
350         }
351
352         for i, sigInst := range txTemplate.SigningInstructions {
353                 if msg.Inputs[sigInst.Position] == nil {
354                         return errors.WithDetailf(txbuilder.ErrBadTxInputIdx, "signing instruction %d references missing tx input %d", i, sigInst.Position)
355                 }
356
357                 var witness [][]byte
358                 for j, wc := range sigInst.WitnessComponents {
359                         err := wc.Materialize(&witness)
360                         if err != nil {
361                                 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
362                         }
363                 }
364                 msg.SetInputArguments(sigInst.Position, witness)
365         }
366
367         return nil
368 }