14 log "github.com/sirupsen/logrus"
16 "github.com/vapor/account"
17 "github.com/vapor/blockchain/signers"
18 "github.com/vapor/blockchain/txbuilder"
19 "github.com/vapor/blockchain/txbuilder/mainchain"
20 "github.com/vapor/common"
21 "github.com/vapor/consensus"
22 "github.com/vapor/consensus/segwit"
23 "github.com/vapor/crypto/ed25519/chainkd"
24 "github.com/vapor/crypto/sha3pool"
25 chainjson "github.com/vapor/encoding/json"
26 "github.com/vapor/errors"
27 "github.com/vapor/math/checked"
28 "github.com/vapor/net/http/reqid"
29 "github.com/vapor/protocol/bc"
30 "github.com/vapor/protocol/bc/types"
31 "github.com/vapor/protocol/bc/types/bytom"
32 bytomtypes "github.com/vapor/protocol/bc/types/bytom/types"
33 "github.com/vapor/protocol/vm/vmutil"
34 "github.com/vapor/util"
38 defaultTxTTL = 5 * time.Minute
39 defaultBaseRate = float64(100000)
40 flexibleGas = int64(1800)
43 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
44 decoders := map[string]func([]byte) (txbuilder.Action, error){
45 "control_address": txbuilder.DecodeControlAddressAction,
46 "control_program": txbuilder.DecodeControlProgramAction,
47 "issue": a.wallet.AssetReg.DecodeIssueAction,
48 "retire": txbuilder.DecodeRetireAction,
49 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
50 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
52 decoder, ok := decoders[action]
56 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
58 for i, act := range req.Actions {
59 actionType, ok := act["type"].(string)
61 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
64 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
69 return count == len(req.Actions), nil
72 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
73 if err := a.checkRequestValidity(ctx, req); err != nil {
76 actions, err := a.mergeSpendActions(req)
81 maxTime := time.Now().Add(req.TTL.Duration)
82 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
83 if errors.Root(err) == txbuilder.ErrAction {
84 // append each of the inner errors contained in the data.
87 for i, innerErr := range errors.Data(err)["actions"].([]error) {
89 rootErr = errors.Root(innerErr)
91 Errs = Errs + innerErr.Error()
93 err = errors.WithDetail(rootErr, Errs)
99 // ensure null is never returned for signing instructions
100 if tpl.SigningInstructions == nil {
101 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
106 // POST /build-transaction
107 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
108 subctx := reqid.NewSubContext(ctx, reqid.New())
109 tmpl, err := a.buildSingle(subctx, buildReqs)
111 return NewErrorResponse(err)
114 return NewSuccessResponse(tmpl)
116 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
117 if err := a.completeMissingIDs(ctx, req); err != nil {
121 if req.TTL.Duration == 0 {
122 req.TTL.Duration = defaultTxTTL
125 if ok, err := onlyHaveInputActions(req); err != nil {
128 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
133 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
134 actions := make([]txbuilder.Action, 0, len(req.Actions))
135 for i, act := range req.Actions {
136 typ, ok := act["type"].(string)
138 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
140 decoder, ok := a.actionDecoder(typ)
142 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
145 // Remarshal to JSON, the action may have been modified when we
147 b, err := json.Marshal(act)
151 action, err := decoder(b)
153 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
155 actions = append(actions, action)
157 actions = account.MergeSpendAction(actions)
161 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
162 if err := a.checkRequestValidity(ctx, req); err != nil {
165 actions, err := a.mergeSpendActions(req)
170 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
171 tpls := []*txbuilder.Template{}
172 for _, action := range actions {
173 if action.ActionType() == "spend_account" {
174 tpls, err = account.SpendAccountChain(ctx, builder, action)
176 err = action.Build(ctx, builder)
185 tpl, _, err := builder.Build()
191 tpls = append(tpls, tpl)
195 // POST /build-chain-transactions
196 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
197 subctx := reqid.NewSubContext(ctx, reqid.New())
198 tmpls, err := a.buildTxs(subctx, buildReqs)
200 return NewErrorResponse(err)
202 return NewSuccessResponse(tmpls)
205 type submitTxResp struct {
206 TxID *bc.Hash `json:"tx_id"`
209 // POST /submit-transaction
210 func (a *API) submit(ctx context.Context, ins struct {
211 Tx types.Tx `json:"raw_transaction"`
213 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
214 return NewErrorResponse(err)
217 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
218 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
221 type submitTxsResp struct {
222 TxID []*bc.Hash `json:"tx_id"`
225 // POST /submit-transactions
226 func (a *API) submitTxs(ctx context.Context, ins struct {
227 Tx []types.Tx `json:"raw_transactions"`
229 txHashs := []*bc.Hash{}
230 for i := range ins.Tx {
231 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
232 return NewErrorResponse(err)
234 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
235 txHashs = append(txHashs, &ins.Tx[i].ID)
237 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
240 // EstimateTxGasResp estimate transaction consumed gas
241 type EstimateTxGasResp struct {
242 TotalNeu int64 `json:"total_neu"`
243 StorageNeu int64 `json:"storage_neu"`
244 VMNeu int64 `json:"vm_neu"`
247 // EstimateTxGas estimate consumed neu for transaction
248 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
249 // base tx size and not include sign
250 data, err := template.Transaction.TxData.MarshalText()
254 baseTxSize := int64(len(data))
256 // extra tx size for sign witness parts
257 signSize := estimateSignSize(template.SigningInstructions)
259 // total gas for tx storage
260 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
262 return nil, errors.New("calculate txsize gas got a math error")
265 // consume gas for run VM
266 totalP2WPKHGas := int64(0)
267 totalP2WSHGas := int64(0)
268 baseP2WPKHGas := int64(1419)
269 // flexible Gas is used for handle need extra utxo situation
271 for pos, inpID := range template.Transaction.Tx.InputIDs {
272 sp, err := template.Transaction.Spend(inpID)
277 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
282 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
283 totalP2WPKHGas += baseP2WPKHGas
284 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
285 sigInst := template.SigningInstructions[pos]
286 totalP2WSHGas += estimateP2WSHGas(sigInst)
290 // total estimate gas
291 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
293 // rounding totalNeu with base rate 100000
294 totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
295 roundingNeu := math.Ceil(totalNeu)
296 estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
300 return &EstimateTxGasResp{
301 TotalNeu: estimateNeu,
302 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
303 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
307 // estimate p2wsh gas.
308 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
309 // where a represent the num of public keys, and b represent the num of quorum.
310 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
312 baseP2WSHGas := int64(738)
314 for _, witness := range sigInst.WitnessComponents {
315 switch t := witness.(type) {
316 case *txbuilder.SignatureWitness:
317 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
318 case *txbuilder.RawTxSigWitness:
319 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
325 // estimate signature part size.
326 // if need multi-sign, calculate the size according to the length of keys.
327 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
329 baseWitnessSize := int64(300)
331 for _, sigInst := range signingInstructions {
332 for _, witness := range sigInst.WitnessComponents {
333 switch t := witness.(type) {
334 case *txbuilder.SignatureWitness:
335 signSize += int64(t.Quorum) * baseWitnessSize
336 case *txbuilder.RawTxSigWitness:
337 signSize += int64(t.Quorum) * baseWitnessSize
344 // POST /estimate-transaction-gas
345 func (a *API) estimateTxGas(ctx context.Context, in struct {
346 TxTemplate txbuilder.Template `json:"transaction_template"`
348 txGasResp, err := EstimateTxGas(in.TxTemplate)
350 return NewErrorResponse(err)
352 return NewSuccessResponse(txGasResp)
355 func getPeginTxnOutputIndex(rawTx bytomtypes.Tx, controlProg []byte) int {
356 for index, output := range rawTx.Outputs {
357 if bytes.Equal(output.ControlProgram, controlProg) {
364 func toHash(hexBytes []chainjson.HexBytes) (hashs []*bytom.Hash) {
365 for _, data := range hexBytes {
368 res := bytom.NewHash(b32)
369 hashs = append(hashs, &res)
374 func (a *API) claimPeginTx(ctx context.Context, ins struct {
375 Password string `json:"password"`
376 RawTx bytomtypes.Tx `json:"raw_transaction"`
377 BlockHeader bytomtypes.BlockHeader `json:"block_header"`
378 TxHashes []chainjson.HexBytes `json:"tx_hashes"`
379 StatusHashes []chainjson.HexBytes `json:"status_hashes"`
380 Flags []uint32 `json:"flags"`
381 MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
382 ClaimScript chainjson.HexBytes `json:"claim_script"`
384 tmpl, err := a.createrawpegin(ctx, ins)
386 log.WithField("build err", err).Error("fail on createrawpegin.")
387 return NewErrorResponse(err)
390 if err := txbuilder.Sign(ctx, tmpl, ins.Password, a.PseudohsmSignTemplate); err != nil {
391 log.WithField("build err", err).Error("fail on sign transaction.")
392 return NewErrorResponse(err)
396 if err := txbuilder.FinalizeTx(ctx, a.chain, tmpl.Transaction); err != nil {
397 return NewErrorResponse(err)
400 log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
401 return NewSuccessResponse(&submitTxResp{TxID: &tmpl.Transaction.ID})
404 // GetMerkleBlockResp is resp struct for GetTxOutProof API
405 type GetMerkleBlock struct {
406 BlockHeader bytomtypes.BlockHeader `json:"block_header"`
407 TxHashes []chainjson.HexBytes `json:"tx_hashes"`
408 StatusHashes []chainjson.HexBytes `json:"status_hashes"`
409 Flags []uint32 `json:"flags"`
410 MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
413 func (a *API) createrawpegin(ctx context.Context, ins struct {
414 Password string `json:"password"`
415 RawTx bytomtypes.Tx `json:"raw_transaction"`
416 BlockHeader bytomtypes.BlockHeader `json:"block_header"`
417 TxHashes []chainjson.HexBytes `json:"tx_hashes"`
418 StatusHashes []chainjson.HexBytes `json:"status_hashes"`
419 Flags []uint32 `json:"flags"`
420 MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
421 ClaimScript chainjson.HexBytes `json:"claim_script"`
422 }) (*txbuilder.Template, error) {
425 for flag := range ins.Flags {
426 flags = append(flags, uint8(flag))
428 txHashes := toHash(ins.TxHashes)
429 matchedTxIDs := toHash(ins.MatchedTxIDs)
430 if !bytomtypes.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, ins.BlockHeader.BlockCommitment.TransactionsMerkleRoot) {
431 return nil, errors.New("Merkleblock validation failed")
434 //difficulty.CheckBytomProofOfWork(ins.BlockHeader.Hash(), ins.BlockHeader)
435 // 增加spv验证以及连接主链api查询交易的确认数
436 if util.ValidatePegin {
437 if err := util.IsConfirmedBytomBlock(ins.BlockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil {
441 // 找出与claim script有关联的交易的输出
442 var claimScript []byte
443 nOut := len(ins.RawTx.Outputs)
444 if ins.ClaimScript == nil {
445 // 遍历寻找与交易输出有关的claim script
446 cps, err := a.wallet.AccountMgr.ListControlProgram()
451 for _, cp := range cps {
452 _, controlProg := a.wallet.AccountMgr.GetPeginControlPrograms(cp.ControlProgram)
453 if controlProg == nil {
457 nOut = getPeginTxnOutputIndex(ins.RawTx, controlProg)
458 if nOut != len(ins.RawTx.Outputs) {
459 claimScript = cp.ControlProgram
463 claimScript = ins.ClaimScript
464 _, controlProg := a.wallet.AccountMgr.GetPeginControlPrograms(claimScript)
466 nOut = getPeginTxnOutputIndex(ins.RawTx, controlProg)
468 if nOut == len(ins.RawTx.Outputs) {
469 return nil, errors.New("Failed to find output in bytom to the mainchain_address from getpeginaddress")
472 // 根据ClaimScript 获取account id
474 sha3pool.Sum256(hash[:], claimScript)
475 data := a.wallet.DB.Get(account.ContractKey(hash))
477 return nil, errors.New("Failed to find control program through claim script")
480 cp := &account.CtrlProgram{}
481 if err := json.Unmarshal(data, cp); err != nil {
482 return nil, errors.New("Failed on unmarshal control program")
487 builder := txbuilder.NewBuilder(time.Now())
488 // TODO 根据raw tx生成一个utxo
489 //txInput := types.NewClaimInputInput(nil, *ins.RawTx.Outputs[nOut].AssetId, ins.RawTx.Outputs[nOut].Amount, cp.ControlProgram)
490 assetId := bc.AssetID{}
491 assetId.V0 = ins.RawTx.Outputs[nOut].AssetId.GetV0()
492 assetId.V1 = ins.RawTx.Outputs[nOut].AssetId.GetV1()
493 assetId.V2 = ins.RawTx.Outputs[nOut].AssetId.GetV2()
494 assetId.V3 = ins.RawTx.Outputs[nOut].AssetId.GetV3()
496 sourceID := bc.Hash{}
497 sourceID.V0 = ins.RawTx.OutputID(nOut).GetV0()
498 sourceID.V1 = ins.RawTx.OutputID(nOut).GetV1()
499 sourceID.V2 = ins.RawTx.OutputID(nOut).GetV2()
500 sourceID.V3 = ins.RawTx.OutputID(nOut).GetV3()
501 outputAccount := ins.RawTx.Outputs[nOut].Amount
503 txInput := types.NewClaimInputInput(nil, sourceID, assetId, outputAccount, uint64(nOut), cp.ControlProgram)
504 if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
507 program, err := a.wallet.AccountMgr.CreateAddress(cp.AccountID, false)
512 if err = builder.AddOutput(types.NewTxOutput(assetId, outputAccount, program.ControlProgram)); err != nil {
516 tmpl, txData, err := builder.Build()
521 // todo把一些主链的信息加到交易的stack中
525 amount := strconv.FormatUint(ins.RawTx.Outputs[nOut].Amount, 10)
526 stack = append(stack, []byte(amount))
527 // 主链的gennesisBlockHash
528 stack = append(stack, []byte(consensus.ActiveNetParams.ParentGenesisBlockHash))
530 stack = append(stack, claimScript)
532 tx, _ := json.Marshal(ins.RawTx)
533 stack = append(stack, tx)
535 MerkleBLock := GetMerkleBlock{
536 BlockHeader: ins.BlockHeader,
537 TxHashes: ins.TxHashes,
538 StatusHashes: ins.StatusHashes,
540 MatchedTxIDs: ins.MatchedTxIDs,
542 txOutProof, _ := json.Marshal(MerkleBLock)
543 stack = append(stack, txOutProof)
545 // tmpl.Transaction.Inputs[0].Peginwitness = stack
546 txData.Inputs[0].Peginwitness = stack
549 txGasResp, err := EstimateTxGas(*tmpl)
553 txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
555 tmpl.Transaction = types.NewTx(*txData)
559 func (a *API) buildMainChainTx(ins struct {
560 Utxo account.UTXO `json:"utxo"`
561 Tx types.Tx `json:"raw_transaction"`
562 RootXPubs []chainkd.XPub `json:"root_xpubs"`
563 Alias string `json:"alias"`
564 ControlProgram string `json:"control_program"`
565 ClaimScript chainjson.HexBytes `json:"claim_script"`
568 var xpubs []chainkd.XPub
569 for _, xpub := range ins.RootXPubs {
570 // pub + scriptPubKey 生成一个随机数A
572 h := hmac.New(sha256.New, xpub[:])
573 h.Write(ins.ClaimScript)
574 tweak := h.Sum(tmp[:])
575 // pub + A 生成一个新的公钥pub_new
576 chaildXPub := xpub.Child(tweak)
577 xpubs = append(xpubs, chaildXPub)
579 acc := &account.Account{}
581 if acc, err = a.wallet.AccountMgr.FindByAlias(ins.Alias); err != nil {
582 acc, err = a.wallet.AccountMgr.Create(xpubs, len(xpubs), ins.Alias, signers.BIP0044)
584 return NewErrorResponse(err)
587 ins.Utxo.ControlProgramIndex = acc.Signer.KeyIndex
589 txInput, sigInst, err := utxoToInputs(acc.Signer, &ins.Utxo)
591 return NewErrorResponse(err)
594 builder := mainchain.NewBuilder(time.Now())
595 builder.AddInput(txInput, sigInst)
596 changeAmount := uint64(0)
598 for _, key := range ins.Tx.GetResultIds() {
599 output, err := ins.Tx.Retire(*key)
601 log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
605 var controlProgram []byte
607 if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
608 return NewErrorResponse(errors.New("The corresponding input cannot be found"))
611 assetID := bytom.AssetID{
612 V0: output.Source.Value.AssetId.GetV0(),
613 V1: output.Source.Value.AssetId.GetV1(),
614 V2: output.Source.Value.AssetId.GetV2(),
615 V3: output.Source.Value.AssetId.GetV3(),
617 out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
618 builder.AddOutput(out)
619 changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
624 return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
627 if changeAmount > 0 {
629 assetID := bytom.AssetID{
630 V0: u.AssetID.GetV0(),
631 V1: u.AssetID.GetV1(),
632 V2: u.AssetID.GetV2(),
633 V3: u.AssetID.GetV3(),
635 out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
636 builder.AddOutput(out)
639 tmpl, tx, err := builder.Build()
641 return NewErrorResponse(err)
644 txGasResp, err := EstimateTxGasForMainchain(*tmpl)
646 return NewErrorResponse(err)
648 for i, out := range tmpl.Transaction.Outputs {
649 if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
650 tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
653 tmpl.Transaction = bytomtypes.NewTx(*tx)
654 return NewSuccessResponse(tmpl)
658 func getInput(entry map[bc.Hash]bc.Entry, outputID bc.Hash, controlProgram string) ([]byte, bool) {
659 output := entry[outputID].(*bc.Retirement)
660 mux := entry[*output.Source.Ref].(*bc.Mux)
662 for _, valueSource := range mux.GetSources() {
663 spend := entry[*valueSource.Ref].(*bc.Spend)
664 prevout := entry[*spend.SpentOutputId].(*bc.Output)
666 var ctrlProgram chainjson.HexBytes
667 ctrlProgram = prevout.ControlProgram.Code
668 tmp, _ := ctrlProgram.MarshalText()
669 if string(tmp) == controlProgram {
670 return ctrlProgram, true
676 // UtxoToInputs convert an utxo to the txinput
677 func utxoToInputs(signer *signers.Signer, u *account.UTXO) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
678 sourceID := bytom.Hash{
679 V0: u.SourceID.GetV0(),
680 V1: u.SourceID.GetV1(),
681 V2: u.SourceID.GetV2(),
682 V3: u.SourceID.GetV3(),
685 assetID := bytom.AssetID{
686 V0: u.AssetID.GetV0(),
687 V1: u.AssetID.GetV1(),
688 V2: u.AssetID.GetV2(),
689 V3: u.AssetID.GetV3(),
692 txInput := bytomtypes.NewSpendInput(nil, sourceID, assetID, u.Amount, u.SourcePos, u.ControlProgram)
693 sigInst := &mainchain.SigningInstruction{}
695 return txInput, sigInst, nil
698 // TODO u.Change看怎么填写
699 path, _ := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
701 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
702 return txInput, sigInst, nil
705 address, err := common.DecodeBytomAddress(u.Address, &consensus.ActiveNetParams)
710 switch address.(type) {
711 case *common.AddressWitnessPubKeyHash:
712 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
713 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
714 derivedPK := derivedXPubs[0].PublicKey()
715 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness([]byte(derivedPK)))
717 case *common.AddressWitnessScriptHash:
718 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
719 //path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
720 //derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
721 derivedXPubs := signer.XPubs
722 derivedPKs := chainkd.XPubKeys(derivedXPubs)
723 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
727 sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(script))
730 return nil, nil, errors.New("unsupport address type")
733 return txInput, sigInst, nil
736 type signRespForMainchain struct {
737 Tx *mainchain.Template `json:"transaction"`
738 SignComplete bool `json:"sign_complete"`
741 func (a *API) signWithKey(ins struct {
742 Xprv string `json:"xprv"`
743 XPub chainkd.XPub `json:"xpub"`
744 Txs mainchain.Template `json:"transaction"`
745 ClaimScript chainjson.HexBytes `json:"claim_script"`
747 xprv := &chainkd.XPrv{}
748 if err := xprv.UnmarshalText([]byte(ins.Xprv)); err != nil {
749 return NewErrorResponse(err)
751 // pub + scriptPubKey 生成一个随机数A
753 h := hmac.New(sha256.New, ins.XPub[:])
754 h.Write(ins.ClaimScript)
755 tweak := h.Sum(tmp[:])
756 // pub + A 生成一个新的公钥pub_new
757 privateKey := xprv.Child(tweak, false)
759 if err := sign(&ins.Txs, privateKey); err != nil {
760 return NewErrorResponse(err)
762 log.Info("Sign Transaction complete.")
763 log.Info(mainchain.SignProgress(&ins.Txs))
764 return NewSuccessResponse(&signRespForMainchain{Tx: &ins.Txs, SignComplete: mainchain.SignProgress(&ins.Txs)})
767 func sign(tmpl *mainchain.Template, xprv chainkd.XPrv) error {
768 for i, sigInst := range tmpl.SigningInstructions {
769 for j, wc := range sigInst.WitnessComponents {
770 switch sw := wc.(type) {
771 case *mainchain.SignatureWitness:
772 err := sw.Sign(tmpl, uint32(i), xprv)
774 return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
776 case *mainchain.RawTxSigWitness:
777 err := sw.Sign(tmpl, uint32(i), xprv)
779 return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
784 return materializeWitnesses(tmpl)
787 func materializeWitnesses(txTemplate *mainchain.Template) error {
788 msg := txTemplate.Transaction
791 return errors.Wrap(txbuilder.ErrMissingRawTx)
794 if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
795 return errors.Wrap(txbuilder.ErrBadInstructionCount)
798 for i, sigInst := range txTemplate.SigningInstructions {
799 if msg.Inputs[sigInst.Position] == nil {
800 return errors.WithDetailf(txbuilder.ErrBadTxInputIdx, "signing instruction %d references missing tx input %d", i, sigInst.Position)
804 for j, wc := range sigInst.WitnessComponents {
805 err := wc.Materialize(&witness)
807 return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
810 msg.SetInputArguments(sigInst.Position, witness)