9 log "github.com/sirupsen/logrus"
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"
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"`
34 var xpubs []chainkd.XPub
35 for _, xpub := range ins.RootXPubs {
36 // pub + scriptPubKey 生成一个随机数A
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)
46 txInput, sigInst, err := contractToInputs(a, &ins.Utxo, xpubs)
47 builder := mainchain.NewBuilder(time.Now())
48 builder.AddInput(txInput, sigInst)
49 changeAmount := uint64(0)
51 for _, key := range ins.Tx.GetResultIds() {
52 output, err := ins.Tx.Retire(*key)
54 log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
58 var controlProgram []byte
60 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
61 return NewErrorResponse(errors.New("The corresponding input cannot be found"))
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(),
70 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
71 builder.AddOutput(out)
72 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
77 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
82 assetID := bytom.AssetID{
83 V0: u.AssetID.GetV0(),
84 V1: u.AssetID.GetV1(),
85 V2: u.AssetID.GetV2(),
86 V3: u.AssetID.GetV3(),
88 out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
89 builder.AddOutput(out)
92 tmpl, tx, err := builder.Build()
94 return NewErrorResponse(err)
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
103 tmpl.Transaction = bytomtypes.NewTx(*tx)
104 return NewSuccessResponse(tmpl)
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"`
115 var xpubs []chainkd.XPub
116 for _, xpub := range ins.RootXPubs {
117 // pub + scriptPubKey 生成一个随机数A
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)
127 txInput, sigInst, err := utxoToInputs(xpubs, &ins.Utxo)
129 return NewErrorResponse(err)
132 builder := mainchain.NewBuilder(time.Now())
133 builder.AddInput(txInput, sigInst)
134 changeAmount := uint64(0)
136 for _, key := range ins.Tx.GetResultIds() {
137 output, err := ins.Tx.Retire(*key)
139 log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
143 var controlProgram []byte
145 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
146 return NewErrorResponse(errors.New("The corresponding input cannot be found"))
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(),
155 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
156 builder.AddOutput(out)
157 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
162 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
165 if changeAmount > 0 {
167 assetID := bytom.AssetID{
168 V0: u.AssetID.GetV0(),
169 V1: u.AssetID.GetV1(),
170 V2: u.AssetID.GetV2(),
171 V3: u.AssetID.GetV3(),
173 out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
174 builder.AddOutput(out)
177 tmpl, tx, err := builder.Build()
179 return NewErrorResponse(err)
182 txGasResp, err := EstimateTxGasForMainchain(*tmpl)
184 return NewErrorResponse(err)
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)
191 tmpl.Transaction = bytomtypes.NewTx(*tx)
192 return NewSuccessResponse(tmpl)
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)
200 for _, valueSource := range mux.GetSources() {
201 spend := entry[*valueSource.Ref].(*bc.Spend)
202 prevout := entry[*spend.SpentOutputId].(*bc.Output)
204 var ctrlProgram chainjson.HexBytes
205 ctrlProgram = prevout.ControlProgram.Code
206 tmp, _ := ctrlProgram.MarshalText()
207 if string(tmp) == controlProgram {
208 return ctrlProgram, true
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(),
223 assetID := bytom.AssetID{
224 V0: u.AssetID.GetV0(),
225 V1: u.AssetID.GetV1(),
226 V2: u.AssetID.GetV2(),
227 V3: u.AssetID.GetV3(),
230 txInput := bytomtypes.NewSpendInput(nil, sourceID, assetID, u.Amount, u.SourcePos, u.ControlProgram)
231 sigInst := &mainchain.SigningInstruction{}
234 sigInst.AddWitnessKeys(xpubs, quorum)
235 return txInput, sigInst, nil
238 address, err := common.DecodeBytomAddress(u.Address, &consensus.ActiveNetParams)
243 sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum)
245 switch address.(type) {
246 case *common.AddressWitnessPubKeyHash:
247 derivedPK := xpubs[0].PublicKey()
248 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness([]byte(derivedPK)))
250 case *common.AddressWitnessScriptHash:
251 derivedXPubs := xpubs
252 derivedPKs := chainkd.XPubKeys(derivedXPubs)
253 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum)
257 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(script))
260 return nil, nil, errors.New("unsupport address type")
263 return txInput, sigInst, nil
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(),
274 assetID := bytom.AssetID{
275 V0: u.AssetID.GetV0(),
276 V1: u.AssetID.GetV1(),
277 V2: u.AssetID.GetV2(),
278 V3: u.AssetID.GetV3(),
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)
287 return txInput, sigInst, nil
290 type signRespForMainchain struct {
291 Tx *mainchain.Template `json:"transaction"`
292 SignComplete bool `json:"sign_complete"`
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"`
301 xprv := &chainkd.XPrv{}
302 if err := xprv.UnmarshalText([]byte(ins.Xprv)); err != nil {
303 return NewErrorResponse(err)
305 // pub + scriptPubKey 生成一个随机数A
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)
313 if err := sign(&ins.Txs, privateKey); err != nil {
314 return NewErrorResponse(err)
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)})
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)
328 return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
330 case *mainchain.RawTxSigWitness:
331 err := sw.Sign(tmpl, uint32(i), xprv)
333 return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
338 return materializeWitnesses(tmpl)
341 func materializeWitnesses(txTemplate *mainchain.Template) error {
342 msg := txTemplate.Transaction
345 return errors.Wrap(txbuilder.ErrMissingRawTx)
348 if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
349 return errors.Wrap(txbuilder.ErrBadInstructionCount)
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)
358 for j, wc := range sigInst.WitnessComponents {
359 err := wc.Materialize(&witness)
361 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
364 msg.SetInputArguments(sigInst.Position, witness)