OSDN Git Service

4bb63c8211aea7ab2753ffeb241b9170a901eae6
[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/equity/pegin_contract"
19         "github.com/vapor/errors"
20         "github.com/vapor/protocol/bc"
21         "github.com/vapor/protocol/bc/types"
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, ins.ClaimScript)
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 := *output.Source.Value.AssetId
65                 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
66                 builder.AddOutput(out)
67                 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
68
69         }
70
71         if !retire {
72                 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
73         }
74
75         if changeAmount > 0 {
76                 u := ins.Utxo
77                 out := bytomtypes.NewTxOutput(u.AssetID, changeAmount, ins.Utxo.ControlProgram)
78                 builder.AddOutput(out)
79         }
80
81         tmpl, tx, err := builder.Build()
82         if err != nil {
83                 return NewErrorResponse(err)
84         }
85
86         for i, out := range tmpl.Transaction.Outputs {
87                 if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
88                         //tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
89                         tx.Outputs[i].Amount = changeAmount - 100000000
90                 }
91         }
92         tmpl.Transaction = bytomtypes.NewTx(*tx)
93         return NewSuccessResponse(tmpl)
94 }
95
96 func (a *API) buildMainChainTx(ins struct {
97         Utxo           account.UTXO       `json:"utxo"`
98         Tx             types.Tx           `json:"raw_transaction"`
99         RootXPubs      []chainkd.XPub     `json:"root_xpubs"`
100         ControlProgram string             `json:"control_program"`
101         ClaimScript    chainjson.HexBytes `json:"claim_script"`
102 }) Response {
103
104         var xpubs []chainkd.XPub
105         for _, xpub := range ins.RootXPubs {
106                 // pub + scriptPubKey 生成一个随机数A
107                 var tmp [32]byte
108                 h := hmac.New(sha256.New, xpub[:])
109                 h.Write(ins.ClaimScript)
110                 tweak := h.Sum(tmp[:])
111                 // pub +  A 生成一个新的公钥pub_new
112                 chaildXPub := xpub.Child(tweak)
113                 xpubs = append(xpubs, chaildXPub)
114         }
115
116         txInput, sigInst, err := utxoToInputs(xpubs, &ins.Utxo)
117         if err != nil {
118                 return NewErrorResponse(err)
119         }
120
121         builder := mainchain.NewBuilder(time.Now())
122         builder.AddInput(txInput, sigInst)
123         changeAmount := uint64(0)
124         retire := false
125         for _, key := range ins.Tx.GetResultIds() {
126                 output, err := ins.Tx.Retire(*key)
127                 if err != nil {
128                         log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
129                         continue
130                 }
131                 retire = true
132                 var controlProgram []byte
133                 retBool := true
134                 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
135                         return NewErrorResponse(errors.New("The corresponding input cannot be found"))
136                 }
137
138                 assetID := *output.Source.Value.AssetId
139                 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
140                 builder.AddOutput(out)
141                 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
142
143         }
144
145         if !retire {
146                 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
147         }
148
149         if changeAmount > 0 {
150                 u := ins.Utxo
151                 out := bytomtypes.NewTxOutput(u.AssetID, changeAmount, ins.Utxo.ControlProgram)
152                 builder.AddOutput(out)
153         }
154
155         tmpl, tx, err := builder.Build()
156         if err != nil {
157                 return NewErrorResponse(err)
158         }
159         //交易费估算
160         txGasResp, err := EstimateTxGasForMainchain(*tmpl)
161         if err != nil {
162                 return NewErrorResponse(err)
163         }
164         for i, out := range tmpl.Transaction.Outputs {
165                 if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
166                         tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
167                 }
168         }
169         tmpl.Transaction = bytomtypes.NewTx(*tx)
170         return NewSuccessResponse(tmpl)
171 }
172
173 //
174 func getInput(entry map[bc.Hash]bc.Entry, outputID bc.Hash, controlProgram string) ([]byte, bool) {
175         output := entry[outputID].(*bc.Retirement)
176         mux := entry[*output.Source.Ref].(*bc.Mux)
177
178         for _, valueSource := range mux.GetSources() {
179                 spend := entry[*valueSource.Ref].(*bc.Spend)
180                 prevout := entry[*spend.SpentOutputId].(*bc.Output)
181
182                 var ctrlProgram chainjson.HexBytes
183                 ctrlProgram = prevout.ControlProgram.Code
184                 tmp, _ := ctrlProgram.MarshalText()
185                 if string(tmp) == controlProgram {
186                         return ctrlProgram, true
187                 }
188         }
189         return nil, false
190 }
191
192 // UtxoToInputs convert an utxo to the txinput
193 func utxoToInputs(xpubs []chainkd.XPub, u *account.UTXO) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
194         txInput := bytomtypes.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
195         sigInst := &mainchain.SigningInstruction{}
196         quorum := len(xpubs)
197         if u.Address == "" {
198                 sigInst.AddWitnessKeys(xpubs, quorum)
199                 return txInput, sigInst, nil
200         }
201
202         address, err := common.DecodeBytomAddress(u.Address, &consensus.ActiveNetParams)
203         if err != nil {
204                 return nil, nil, err
205         }
206
207         sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum)
208
209         switch address.(type) {
210         case *common.AddressWitnessPubKeyHash:
211                 derivedPK := xpubs[0].PublicKey()
212                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness([]byte(derivedPK)))
213
214         case *common.AddressWitnessScriptHash:
215                 derivedXPubs := xpubs
216                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
217                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum)
218                 if err != nil {
219                         return nil, nil, err
220                 }
221                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(script))
222
223         default:
224                 return nil, nil, errors.New("unsupport address type")
225         }
226
227         return txInput, sigInst, nil
228 }
229
230 func contractToInputs(a *API, u *account.UTXO, xpubs []chainkd.XPub, ClaimScript chainjson.HexBytes) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
231         txInput := bytomtypes.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
232         sigInst := &mainchain.SigningInstruction{}
233
234         // raw_tx_signature
235         for _, xpub := range xpubs {
236                 xpubsTmp := []chainkd.XPub{xpub}
237                 sigInst.AddRawWitnessKeysWithoutPath(xpubsTmp, 1)
238         }
239
240         // data
241         peginContractPrograms, err := pegin_contract.GetPeginContractPrograms(ClaimScript)
242         if err != nil {
243                 return nil, nil, err
244         }
245         sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(peginContractPrograms))
246
247         return txInput, sigInst, nil
248 }
249
250 type signRespForMainchain struct {
251         Tx           *mainchain.Template `json:"transaction"`
252         SignComplete bool                `json:"sign_complete"`
253 }
254
255 func (a *API) signWithKey(ins struct {
256         Xprv        string             `json:"xprv"`
257         XPub        chainkd.XPub       `json:"xpub"`
258         Txs         mainchain.Template `json:"transaction"`
259         ClaimScript chainjson.HexBytes `json:"claim_script"`
260 }) Response {
261         xprv := &chainkd.XPrv{}
262         if err := xprv.UnmarshalText([]byte(ins.Xprv)); err != nil {
263                 return NewErrorResponse(err)
264         }
265         // pub + scriptPubKey 生成一个随机数A
266         var tmp [32]byte
267         h := hmac.New(sha256.New, ins.XPub[:])
268         h.Write(ins.ClaimScript)
269         tweak := h.Sum(tmp[:])
270         // pub +  A 生成一个新的公钥pub_new
271         privateKey := xprv.Child(tweak, false)
272
273         if err := sign(&ins.Txs, privateKey); err != nil {
274                 return NewErrorResponse(err)
275         }
276         log.Info("Sign Transaction complete.")
277         log.Info(mainchain.SignProgress(&ins.Txs))
278         return NewSuccessResponse(&signRespForMainchain{Tx: &ins.Txs, SignComplete: mainchain.SignProgress(&ins.Txs)})
279 }
280
281 func sign(tmpl *mainchain.Template, xprv chainkd.XPrv) error {
282         for i, sigInst := range tmpl.SigningInstructions {
283                 for j, wc := range sigInst.WitnessComponents {
284                         switch sw := wc.(type) {
285                         case *mainchain.SignatureWitness:
286                                 err := sw.Sign(tmpl, uint32(i), xprv)
287                                 if err != nil {
288                                         return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
289                                 }
290                         case *mainchain.RawTxSigWitness:
291                                 err := sw.Sign(tmpl, uint32(i), xprv)
292                                 if err != nil {
293                                         return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
294                                 }
295                         }
296                 }
297         }
298         return materializeWitnesses(tmpl)
299 }
300
301 func materializeWitnesses(txTemplate *mainchain.Template) error {
302         msg := txTemplate.Transaction
303
304         if msg == nil {
305                 return errors.Wrap(txbuilder.ErrMissingRawTx)
306         }
307
308         if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
309                 return errors.Wrap(txbuilder.ErrBadInstructionCount)
310         }
311
312         for i, sigInst := range txTemplate.SigningInstructions {
313                 if msg.Inputs[sigInst.Position] == nil {
314                         return errors.WithDetailf(txbuilder.ErrBadTxInputIdx, "signing instruction %d references missing tx input %d", i, sigInst.Position)
315                 }
316
317                 var witness [][]byte
318                 for j, wc := range sigInst.WitnessComponents {
319                         err := wc.Materialize(&witness)
320                         if err != nil {
321                                 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
322                         }
323                 }
324                 msg.SetInputArguments(sigInst.Position, witness)
325         }
326
327         return nil
328 }