OSDN Git Service

Address (#213)
authorPaladz <yzhu101@uottawa.ca>
Tue, 26 Dec 2017 09:10:35 +0000 (17:10 +0800)
committerGitHub <noreply@github.com>
Tue, 26 Dec 2017 09:10:35 +0000 (17:10 +0800)
* tmp save for developing

* tmp save for check other available method

* update txbuilder pass the unit test

* add integration test

* address to pubkeyHash

* edit txbuild for support pay to address ==> pubkeyHash

* black, white, elegant

37 files changed:
blockchain/account/accounts.go
blockchain/account/builder.go
blockchain/account/indexer.go
blockchain/account/receivers.go
blockchain/account/reserve.go
blockchain/receivers.go
blockchain/rpc_reactor.go
blockchain/transact.go
blockchain/txbuilder/actions.go
blockchain/txbuilder/builder.go [changed mode: 0644->0755]
blockchain/txbuilder/data_witness.go [new file with mode: 0755]
blockchain/txbuilder/finalize.go [changed mode: 0644->0755]
blockchain/txbuilder/rawtxsig_witness.go [new file with mode: 0755]
blockchain/txbuilder/signature_program.go [new file with mode: 0755]
blockchain/txbuilder/signature_witness.go [new file with mode: 0755]
blockchain/txbuilder/signing_instruction.go [new file with mode: 0755]
blockchain/txbuilder/txbuilder.go [changed mode: 0644->0755]
blockchain/txbuilder/txbuilder_test.go [new file with mode: 0755]
blockchain/txbuilder/types.go [changed mode: 0644->0755]
blockchain/txbuilder/witness.go [changed mode: 0644->0755]
blockchain/txbuilder/witness_test.go [changed mode: 0644->0755]
blockchain/wallet/indexer.go
cmd/bytomcli/example/issue.go
cmd/bytomcli/example/spend.go
cmd/bytomcli/example/wallet.go
cmd/bytomcli/main.go
config/genesis.go
consensus/difficulty/difficulty.go [moved from consensus/difficulty.go with 91% similarity]
consensus/difficulty/difficulty_test.go [moved from consensus/difficulty_test.go with 59% similarity]
consensus/general.go
integration_test/standard_transaction_test.go [new file with mode: 0644]
mining/cpuminer/cpuminer.go
mining/mining.go
protocol/validation/validation.go
protocol/vm/crypto.go
protocol/vm/ops.go
protocol/vm/vmutil/script.go

index 57489dc..18af001 100755 (executable)
@@ -15,6 +15,8 @@ import (
        "github.com/bytom/blockchain/signers"
        "github.com/bytom/blockchain/txbuilder"
        "github.com/bytom/common"
+       "github.com/bytom/consensus"
+       "github.com/bytom/crypto"
        "github.com/bytom/crypto/ed25519/chainkd"
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/errors"
@@ -219,6 +221,57 @@ func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, err
        return account.Signer, nil
 }
 
+func (m *Manager) CreateP2PKH(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
+       cp, err := m.createP2PKH(ctx, accountID, change, expiresAt)
+       if err != nil {
+               return nil, err
+       }
+
+       if err = m.insertAccountControlProgram(ctx, cp); err != nil {
+               return nil, err
+       }
+       return cp, nil
+}
+
+func (m *Manager) createP2PKH(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
+       account, err := m.findByID(ctx, accountID)
+       if err != nil {
+               return nil, err
+       }
+       if account.Quorum != 1 {
+               return nil, errors.New("need single key pair account to create standard transaction")
+       }
+
+       idx, err := m.nextIndex(ctx)
+       if err != nil {
+               return nil, err
+       }
+       path := signers.Path(account, signers.AccountKeySpace, idx)
+       derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
+       derivedPK := derivedXPubs[0].PublicKey()
+       pubHash := crypto.Ripemd160(derivedPK)
+
+       // TODO: pass different params due to config
+       address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
+       if err != nil {
+               return nil, err
+       }
+
+       control, err := vmutil.P2PKHSigProgram([]byte(pubHash))
+       if err != nil {
+               return nil, err
+       }
+
+       return &CtrlProgram{
+               AccountID:      account.ID,
+               Address:        address.EncodeAddress(),
+               KeyIndex:       idx,
+               ControlProgram: control,
+               Change:         change,
+               ExpiresAt:      expiresAt,
+       }, nil
+}
+
 func (m *Manager) createControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
        account, err := m.findByID(ctx, accountID)
        if err != nil {
@@ -264,6 +317,7 @@ func (m *Manager) CreateControlProgram(ctx context.Context, accountID string, ch
 //CtrlProgram is structure of account control program
 type CtrlProgram struct {
        AccountID      string
+       Address        string
        KeyIndex       uint64
        ControlProgram []byte
        Change         bool
index eb3e4da..07bb392 100644 (file)
@@ -8,6 +8,7 @@ import (
 
        "github.com/bytom/blockchain/signers"
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/crypto/ed25519/chainkd"
        chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
@@ -59,7 +60,7 @@ func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) e
        b.OnRollback(canceler(ctx, a.accounts, res.ID))
 
        for _, r := range res.UTXOs {
-               txInput, sigInst, err := utxoToInputs(acct, r, a.ReferenceData)
+               txInput, sigInst, err := UtxoToInputs(acct, r, a.ReferenceData)
                if err != nil {
                        return errors.Wrap(err, "creating inputs")
                }
@@ -123,7 +124,7 @@ func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilde
                }
        }
 
-       txInput, sigInst, err := utxoToInputs(acct, res.UTXOs[0], a.ReferenceData)
+       txInput, sigInst, err := UtxoToInputs(acct, res.UTXOs[0], a.ReferenceData)
        if err != nil {
                return err
        }
@@ -140,18 +141,21 @@ func canceler(ctx context.Context, m *Manager, rid uint64) func() {
        }
 }
 
-func utxoToInputs(account *signers.Signer, u *utxo, refData []byte) (
-       *legacy.TxInput,
-       *txbuilder.SigningInstruction,
-       error,
-) {
+func UtxoToInputs(account *signers.Signer, u *utxo, refData []byte) (*legacy.TxInput, *txbuilder.SigningInstruction, error) {
        txInput := legacy.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.RefDataHash, refData)
 
-       sigInst := &txbuilder.SigningInstruction{}
-
        path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
-       sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+       sigInst := &txbuilder.SigningInstruction{}
 
+       //TODO: handle pay to script hash address
+       if u.Address != "" {
+               sigInst.AddRawWitnessKeys(account.XPubs, path, account.Quorum)
+               derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
+               derivedPK := derivedXPubs[0].PublicKey()
+               sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
+       } else {
+               sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+       }
        return txInput, sigInst, nil
 }
 
index 8eb7ac5..03fd2fb 100755 (executable)
@@ -26,6 +26,7 @@ type UTXO struct {
        AssetID      []byte
        Amount       uint64
        AccountID    string
+       Address      string
        ProgramIndex uint64
        Program      []byte
        SourceID     []byte
index bf43c04..b091550 100755 (executable)
@@ -34,3 +34,15 @@ func (m *Manager) CreateReceiver(ctx context.Context, accInfo string, expiresAt
                ExpiresAt:      expiresAt,
        }, nil
 }
+
+func (m *Manager) CreateAddress(ctx context.Context, accInfo string, expiresAt time.Time) (*txbuilder.Receiver, error) {
+       program, err := m.CreateP2PKH(ctx, accInfo, false, expiresAt)
+       if err != nil {
+               return nil, err
+       }
+
+       return &txbuilder.Receiver{
+               Address:   program.Address,
+               ExpiresAt: expiresAt,
+       }, nil
+}
index 4d30088..a0e7bd1 100755 (executable)
@@ -50,9 +50,14 @@ type utxo struct {
        RefDataHash    bc.Hash
 
        AccountID           string
+       Address             string
        ControlProgramIndex uint64
 }
 
+func NewUtxo() *utxo {
+       return &utxo{}
+}
+
 func (u *utxo) source() source {
        return source{AssetID: u.AssetID, AccountID: u.AccountID}
 }
@@ -407,6 +412,7 @@ func findMatchingUTXOs(db dbm.DB, src source) ([]*utxo, error) {
                                ControlProgram:      accountUTXO.Program,
                                RefDataHash:         bc.NewHash(rawRefData),
                                AccountID:           src.AccountID,
+                               Address:             accountUTXO.Address,
                                ControlProgramIndex: accountUTXO.ProgramIndex,
                        })
 
@@ -452,6 +458,7 @@ func findSpecificUTXO(db dbm.DB, outHash bc.Hash) (*utxo, error) {
 
        u.OutputID = bc.NewHash(*rawOutputID)
        u.AccountID = accountUTXO.AccountID
+       u.Address = accountUTXO.Address
        u.AssetID = bc.NewAssetID(*rawAssetID)
        u.Amount = accountUTXO.Amount
        u.ControlProgramIndex = accountUTXO.ProgramIndex
index af14e29..dbaa2f7 100755 (executable)
@@ -21,3 +21,14 @@ func (a *BlockchainReactor) createAccountReceiver(ctx context.Context, ins struc
 
        return response
 }
+
+func (a *BlockchainReactor) createAccountAddress(ctx context.Context, ins struct {
+       AccountInfo string    `json:"account_info"`
+       ExpiresAt   time.Time `json:"expires_at"`
+}) interface{} {
+       receiver, err := a.accounts.CreateAddress(ctx, ins.AccountInfo, ins.ExpiresAt)
+       if err != nil {
+               return err
+       }
+       return receiver
+}
index 9e64f6e..1e32993 100644 (file)
@@ -1,8 +1,8 @@
 package blockchain
 
 import (
-       "time"
        "net/http"
+       "time"
 
        log "github.com/sirupsen/logrus"
 
@@ -36,6 +36,7 @@ func (bcr *BlockchainReactor) BuildHander() {
                m.Handle("/create-account", jsonHandler(bcr.createAccount))
                m.Handle("/update-account-tags", jsonHandler(bcr.updateAccountTags))
                m.Handle("/create-account-receiver", jsonHandler(bcr.createAccountReceiver))
+               m.Handle("/create-account-address", jsonHandler(bcr.createAccountAddress))
                m.Handle("/list-accounts", jsonHandler(bcr.listAccounts))
                m.Handle("/create-asset", jsonHandler(bcr.createAsset))
                m.Handle("/update-asset-tags", jsonHandler(bcr.updateAssetTags))
index 45aae11..7ae5665 100644 (file)
@@ -23,6 +23,8 @@ func (a *BlockchainReactor) actionDecoder(action string) (func([]byte) (txbuilde
        switch action {
        case "control_account":
                decoder = a.accounts.DecodeControlAction
+       case "control_address":
+               decoder = txbuilder.DecodeControlAddressAction
        case "control_program":
                decoder = txbuilder.DecodeControlProgramAction
        case "control_receiver":
index e289761..5813b06 100644 (file)
@@ -4,10 +4,13 @@ import (
        "context"
        stdjson "encoding/json"
 
+       "github.com/bytom/common"
+       "github.com/bytom/consensus"
        "github.com/bytom/encoding/json"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
        "github.com/bytom/protocol/vm"
+       "github.com/bytom/protocol/vm/vmutil"
 )
 
 var retirementProgram = []byte{byte(vm.OP_FAIL)}
@@ -48,6 +51,42 @@ func (a *controlReceiverAction) Build(ctx context.Context, b *TemplateBuilder) e
        return b.AddOutput(out)
 }
 
+func DecodeControlAddressAction(data []byte) (Action, error) {
+       a := new(controlAddressAction)
+       err := stdjson.Unmarshal(data, a)
+       return a, err
+}
+
+type controlAddressAction struct {
+       bc.AssetAmount
+       Address       string   `json:"address"`
+       ReferenceData json.Map `json:"reference_data"`
+}
+
+func (a *controlAddressAction) Build(ctx context.Context, b *TemplateBuilder) error {
+       var missing []string
+       if a.Address == "" {
+               missing = append(missing, "address")
+       }
+       if a.AssetId.IsZero() {
+               missing = append(missing, "asset_id")
+       }
+       if len(missing) > 0 {
+               return MissingFieldsError(missing...)
+       }
+
+       // TODO: call different stand script generate due to address start with 1 or 3
+       address, err := common.DecodeAddress(a.Address, &consensus.MainNetParams)
+       pubkeyHash := address.ScriptAddress()
+       program, err := vmutil.P2PKHSigProgram(pubkeyHash)
+       if err != nil {
+               return err
+       }
+
+       out := legacy.NewTxOutput(*a.AssetId, a.Amount, program, a.ReferenceData)
+       return b.AddOutput(out)
+}
+
 func DecodeControlProgramAction(data []byte) (Action, error) {
        a := new(controlProgramAction)
        err := stdjson.Unmarshal(data, a)
old mode 100644 (file)
new mode 100755 (executable)
index 1b4a254..5cdb8dc
@@ -124,8 +124,8 @@ func (b *TemplateBuilder) Build() (*Template, *legacy.TxData, error) {
                instruction.Position = uint32(len(tx.Inputs))
 
                // Empty signature arrays should be serialized as empty arrays, not null.
-               if instruction.SignatureWitnesses == nil {
-                       instruction.SignatureWitnesses = []*signatureWitness{}
+               if instruction.WitnessComponents == nil {
+                       instruction.WitnessComponents = []witnessComponent{}
                }
                tpl.SigningInstructions = append(tpl.SigningInstructions, instruction)
                tx.Inputs = append(tx.Inputs, in)
diff --git a/blockchain/txbuilder/data_witness.go b/blockchain/txbuilder/data_witness.go
new file mode 100755 (executable)
index 0000000..4120fe9
--- /dev/null
@@ -0,0 +1,25 @@
+package txbuilder
+
+import (
+       "encoding/json"
+
+       chainjson "github.com/bytom/encoding/json"
+)
+
+type DataWitness chainjson.HexBytes
+
+func (dw DataWitness) materialize(args *[][]byte) error {
+       *args = append(*args, dw)
+       return nil
+}
+
+func (dw DataWitness) MarshalJSON() ([]byte, error) {
+       x := struct {
+               Type  string             `json:"type"`
+               Value chainjson.HexBytes `json:"value"`
+       }{
+               Type:  "data",
+               Value: chainjson.HexBytes(dw),
+       }
+       return json.Marshal(x)
+}
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/blockchain/txbuilder/rawtxsig_witness.go b/blockchain/txbuilder/rawtxsig_witness.go
new file mode 100755 (executable)
index 0000000..7cf1573
--- /dev/null
@@ -0,0 +1,84 @@
+package txbuilder
+
+import (
+       "context"
+       "encoding/json"
+
+       "github.com/bytom/crypto/ed25519/chainkd"
+       chainjson "github.com/bytom/encoding/json"
+       "github.com/bytom/errors"
+)
+
+// TODO(bobg): most of the code here is duplicated from
+// signature_witness.go and needs refactoring.
+
+// RawTxSigWitness is like SignatureWitness but doesn't involve
+// signature programs.
+type RawTxSigWitness struct {
+       Quorum int                  `json:"quorum"`
+       Keys   []keyID              `json:"keys"`
+       Sigs   []chainjson.HexBytes `json:"signatures"`
+}
+
+func (sw *RawTxSigWitness) sign(ctx context.Context, tpl *Template, index uint32, xpubs []chainkd.XPub, auth string, signFn SignFunc) error {
+       if len(sw.Sigs) < len(sw.Keys) {
+               // Each key in sw.Keys may produce a signature in sw.Sigs. Make
+               // sure there are enough slots in sw.Sigs and that we preserve any
+               // sigs already present.
+               newSigs := make([]chainjson.HexBytes, len(sw.Keys))
+               copy(newSigs, sw.Sigs)
+               sw.Sigs = newSigs
+       }
+       for i, keyID := range sw.Keys {
+               if len(sw.Sigs[i]) > 0 {
+                       // Already have a signature for this key
+                       continue
+               }
+               var found bool
+               for _, xpub := range xpubs {
+                       if keyID.XPub == xpub {
+                               found = true
+                               break
+                       }
+               }
+               if xpubs != nil && !found {
+                       continue
+               }
+               path := make([]([]byte), len(keyID.DerivationPath))
+               for i, p := range keyID.DerivationPath {
+                       path[i] = p
+               }
+               sigBytes, err := signFn(ctx, keyID.XPub, path, tpl.Hash(index).Byte32(), auth)
+               if err != nil {
+                       return errors.WithDetailf(err, "computing signature %d", i)
+               }
+               sw.Sigs[i] = sigBytes
+       }
+       return nil
+}
+
+func (sw RawTxSigWitness) materialize(args *[][]byte) error {
+       var nsigs int
+       for i := 0; i < len(sw.Sigs) && nsigs < sw.Quorum; i++ {
+               if len(sw.Sigs[i]) > 0 {
+                       *args = append(*args, sw.Sigs[i])
+                       nsigs++
+               }
+       }
+       return nil
+}
+
+func (sw RawTxSigWitness) MarshalJSON() ([]byte, error) {
+       obj := struct {
+               Type   string               `json:"type"`
+               Quorum int                  `json:"quorum"`
+               Keys   []keyID              `json:"keys"`
+               Sigs   []chainjson.HexBytes `json:"signatures"`
+       }{
+               Type:   "raw_tx_signature",
+               Quorum: sw.Quorum,
+               Keys:   sw.Keys,
+               Sigs:   sw.Sigs,
+       }
+       return json.Marshal(obj)
+}
diff --git a/blockchain/txbuilder/signature_program.go b/blockchain/txbuilder/signature_program.go
new file mode 100755 (executable)
index 0000000..5111f9c
--- /dev/null
@@ -0,0 +1,85 @@
+package txbuilder
+
+import (
+       "github.com/bytom/crypto/sha3pool"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/protocol/vm"
+       "github.com/bytom/protocol/vm/vmutil"
+)
+
+// Signature programs constrain how the signed inputs of a transaction
+// in a template may be used, especially if the transaction is not yet
+// complete.
+//
+// For example, suppose Alice wants to send Bob 80 EUR but only if Bob
+// pays her 100 USD, and only if payment is made before next
+// Tuesday. Alice constructs a partial transaction that includes her
+// 80 EUR as one input, a payment to Bob as one output, _and_ a
+// payment to Alice (of 100 USD) as one more output. She then
+// constructs a program testing that the transaction includes all
+// those components (and that the maxtime of the transaction is before
+// next Tuesday) and signs a hash of that in order to unlock her 80
+// EUR. She then passes the partial transaction template to Bob, who
+// supplies his 100 USD input. Because of the signature program, Bob
+// (or an eavesdropper) cannot use the signed 80-EUR input in any
+// transaction other than one that pays 100 USD to Alice before
+// Tuesday.
+//
+// This works because of Chain's convention for formatting of account
+// control programs. The 80 EUR prevout being spent by Alice was paid
+// to the program:
+//   DUP TOALTSTACK SHA3 <pubkey1> <pubkey2> ... <pubkeyN> <quorum> <N> CHECKMULTISIG VERIFY FROMALTSTACK 0 CHECKPREDICATE
+// which means that any attempt to spend it must be accompanied by a
+// signed program that evaluates to true. The default program (for a
+// complete transaction to which no other entries may be added) is
+//   <txsighash> TXSIGHASH EQUAL
+// which commits to the transaction as-is.
+
+func buildSigProgram(tpl *Template, index uint32) ([]byte, error) {
+       if !tpl.AllowAdditional {
+               h := tpl.Hash(index)
+               builder := vmutil.NewBuilder()
+               builder.AddData(h.Bytes())
+               builder.AddOp(vm.OP_TXSIGHASH).AddOp(vm.OP_EQUAL)
+
+               return builder.Build()
+       }
+       constraints := make([]constraint, 0, 3+len(tpl.Transaction.Outputs))
+       id := tpl.Transaction.Tx.InputIDs[index]
+       if sp, err := tpl.Transaction.Tx.Spend(id); err == nil {
+               constraints = append(constraints, outputIDConstraint(*sp.SpentOutputId))
+       }
+
+       // Commitment to the tx-level refdata is conditional on it being
+       // non-empty. Commitment to the input-level refdata is
+       // unconditional. Rationale: no one should be able to change "my"
+       // reference data; anyone should be able to set tx refdata but, once
+       // set, it should be immutable.
+       if len(tpl.Transaction.ReferenceData) > 0 {
+               constraints = append(constraints, refdataConstraint{tpl.Transaction.ReferenceData, true})
+       }
+       constraints = append(constraints, refdataConstraint{tpl.Transaction.Inputs[index].ReferenceData, false})
+
+       for i, out := range tpl.Transaction.Outputs {
+               c := &payConstraint{
+                       Index:       i,
+                       AssetAmount: out.AssetAmount,
+                       Program:     out.ControlProgram,
+               }
+               if len(out.ReferenceData) > 0 {
+                       var b32 [32]byte
+                       sha3pool.Sum256(b32[:], out.ReferenceData)
+                       h := bc.NewHash(b32)
+                       c.RefDataHash = &h
+               }
+               constraints = append(constraints, c)
+       }
+       var program []byte
+       for i, c := range constraints {
+               program = append(program, c.code()...)
+               if i < len(constraints)-1 { // leave the final bool on top of the stack
+                       program = append(program, byte(vm.OP_VERIFY))
+               }
+       }
+       return program, nil
+}
diff --git a/blockchain/txbuilder/signature_witness.go b/blockchain/txbuilder/signature_witness.go
new file mode 100755 (executable)
index 0000000..0f28216
--- /dev/null
@@ -0,0 +1,134 @@
+package txbuilder
+
+import (
+       "context"
+       "encoding/json"
+
+       "github.com/bytom/crypto/ed25519/chainkd"
+       "github.com/bytom/crypto/sha3pool"
+       chainjson "github.com/bytom/encoding/json"
+       "github.com/bytom/errors"
+       "github.com/bytom/protocol/vm"
+)
+
+type (
+       SignatureWitness struct {
+               // Quorum is the number of signatures required.
+               Quorum int `json:"quorum"`
+
+               // Keys are the identities of the keys to sign with.
+               Keys []keyID `json:"keys"`
+
+               // Program is the predicate part of the signature program, whose hash is what gets
+               // signed. If empty, it is computed during Sign from the outputs
+               // and the current input of the transaction.
+               Program chainjson.HexBytes `json:"program"`
+
+               // Sigs are signatures of Program made from each of the Keys
+               // during Sign.
+               Sigs []chainjson.HexBytes `json:"signatures"`
+       }
+
+       keyID struct {
+               XPub           chainkd.XPub         `json:"xpub"`
+               DerivationPath []chainjson.HexBytes `json:"derivation_path"`
+       }
+)
+
+var ErrEmptyProgram = errors.New("empty signature program")
+
+// Sign populates sw.Sigs with as many signatures of the predicate in
+// sw.Program as it can from the overlapping set of keys in sw.Keys
+// and xpubs.
+//
+// If sw.Program is empty, it is populated with an _inferred_ predicate:
+// a program committing to aspects of the current
+// transaction. Specifically, the program commits to:
+//  - the mintime and maxtime of the transaction (if non-zero)
+//  - the outputID and (if non-empty) reference data of the current input
+//  - the assetID, amount, control program, and (if non-empty) reference data of each output.
+func (sw *SignatureWitness) sign(ctx context.Context, tpl *Template, index uint32, xpubs []chainkd.XPub, auth string, signFn SignFunc) error {
+       // Compute the predicate to sign. This is either a
+       // txsighash program if tpl.AllowAdditional is false (i.e., the tx is complete
+       // and no further changes are allowed) or a program enforcing
+       // constraints derived from the existing outputs and current input.
+       if len(sw.Program) == 0 {
+               var err error
+               sw.Program, err = buildSigProgram(tpl, tpl.SigningInstructions[index].Position)
+               if err != nil {
+                       return err
+               }
+               if len(sw.Program) == 0 {
+                       return ErrEmptyProgram
+               }
+       }
+       if len(sw.Sigs) < len(sw.Keys) {
+               // Each key in sw.Keys may produce a signature in sw.Sigs. Make
+               // sure there are enough slots in sw.Sigs and that we preserve any
+               // sigs already present.
+               newSigs := make([]chainjson.HexBytes, len(sw.Keys))
+               copy(newSigs, sw.Sigs)
+               sw.Sigs = newSigs
+       }
+       var h [32]byte
+       sha3pool.Sum256(h[:], sw.Program)
+       for i, keyID := range sw.Keys {
+               if len(sw.Sigs[i]) > 0 {
+                       // Already have a signature for this key
+                       continue
+               }
+               var found bool
+               for _, xpub := range xpubs {
+                       if keyID.XPub == xpub {
+                               found = true
+                               break
+                       }
+               }
+               if xpubs != nil && !found {
+                       continue
+               }
+               path := make([]([]byte), len(keyID.DerivationPath))
+               for i, p := range keyID.DerivationPath {
+                       path[i] = p
+               }
+               sigBytes, err := signFn(ctx, keyID.XPub, path, h, auth)
+               if err != nil {
+                       return errors.WithDetailf(err, "computing signature %d", i)
+               }
+               sw.Sigs[i] = sigBytes
+       }
+       return nil
+}
+
+func (sw SignatureWitness) materialize(args *[][]byte) error {
+       // This is the value of N for the CHECKPREDICATE call. The code
+       // assumes that everything already in the arg list before this call
+       // to Materialize is input to the signature program, so N is
+       // len(*args).
+       *args = append(*args, vm.Int64Bytes(int64(len(*args))))
+
+       var nsigs int
+       for i := 0; i < len(sw.Sigs) && nsigs < sw.Quorum; i++ {
+               if len(sw.Sigs[i]) > 0 {
+                       *args = append(*args, sw.Sigs[i])
+                       nsigs++
+               }
+       }
+       *args = append(*args, sw.Program)
+       return nil
+}
+
+func (sw SignatureWitness) MarshalJSON() ([]byte, error) {
+       obj := struct {
+               Type   string               `json:"type"`
+               Quorum int                  `json:"quorum"`
+               Keys   []keyID              `json:"keys"`
+               Sigs   []chainjson.HexBytes `json:"signatures"`
+       }{
+               Type:   "signature",
+               Quorum: sw.Quorum,
+               Keys:   sw.Keys,
+               Sigs:   sw.Sigs,
+       }
+       return json.Marshal(obj)
+}
diff --git a/blockchain/txbuilder/signing_instruction.go b/blockchain/txbuilder/signing_instruction.go
new file mode 100755 (executable)
index 0000000..a2c1d60
--- /dev/null
@@ -0,0 +1,118 @@
+package txbuilder
+
+import (
+       "encoding/json"
+
+       "github.com/bytom/crypto/ed25519/chainkd"
+       chainjson "github.com/bytom/encoding/json"
+       "github.com/bytom/errors"
+)
+
+// AddWitnessKeys adds a SignatureWitness with the given quorum and
+// list of keys derived by applying the derivation path to each of the
+// xpubs.
+func (si *SigningInstruction) AddWitnessKeys(xpubs []chainkd.XPub, path [][]byte, quorum int) {
+       hexPath := make([]chainjson.HexBytes, 0, len(path))
+       for _, p := range path {
+               hexPath = append(hexPath, p)
+       }
+
+       keyIDs := make([]keyID, 0, len(xpubs))
+       for _, xpub := range xpubs {
+               keyIDs = append(keyIDs, keyID{xpub, hexPath})
+       }
+
+       sw := &SignatureWitness{
+               Quorum: quorum,
+               Keys:   keyIDs,
+       }
+       si.WitnessComponents = append(si.WitnessComponents, sw)
+}
+
+// AddWitnessKeys adds a SignatureWitness with the given quorum and
+// list of keys derived by applying the derivation path to each of the
+// xpubs.
+func (si *SigningInstruction) AddRawWitnessKeys(xpubs []chainkd.XPub, path [][]byte, quorum int) {
+       hexPath := make([]chainjson.HexBytes, 0, len(path))
+       for _, p := range path {
+               hexPath = append(hexPath, p)
+       }
+
+       keyIDs := make([]keyID, 0, len(xpubs))
+       for _, xpub := range xpubs {
+               keyIDs = append(keyIDs, keyID{xpub, hexPath})
+       }
+
+       sw := &RawTxSigWitness{
+               Quorum: quorum,
+               Keys:   keyIDs,
+       }
+       si.WitnessComponents = append(si.WitnessComponents, sw)
+}
+
+// SigningInstruction gives directions for signing inputs in a TxTemplate.
+type SigningInstruction struct {
+       Position          uint32             `json:"position"`
+       WitnessComponents []witnessComponent `json:"witness_components,omitempty"`
+}
+
+// witnessComponent is the abstract type for the parts of a
+// SigningInstruction.  Each witnessComponent produces one or more
+// arguments for a VM program via its materialize method. Concrete
+// witnessComponent types include SignatureWitness and dataWitness.
+type witnessComponent interface {
+       materialize(*[][]byte) error
+}
+
+func (si *SigningInstruction) UnmarshalJSON(b []byte) error {
+       var pre struct {
+               Position          uint32            `json:"position"`
+               WitnessComponents []json.RawMessage `json:"witness_components"`
+       }
+       err := json.Unmarshal(b, &pre)
+       if err != nil {
+               return err
+       }
+
+       si.Position = pre.Position
+       for i, wc := range pre.WitnessComponents {
+               var t struct {
+                       Type string
+               }
+               err = json.Unmarshal(wc, &t)
+               if err != nil {
+                       return errors.Wrapf(err, "unmarshaling error on witness component %d, input %s", i, wc)
+               }
+               switch t.Type {
+               case "data":
+                       var d struct {
+                               Value chainjson.HexBytes
+                       }
+                       err = json.Unmarshal(wc, &d)
+                       if err != nil {
+                               return errors.Wrapf(err, "unmarshaling error on witness component %d, type data, input %s", i, wc)
+                       }
+                       si.WitnessComponents = append(si.WitnessComponents, DataWitness(d.Value))
+
+               case "signature":
+                       var s SignatureWitness
+                       err = json.Unmarshal(wc, &s)
+                       if err != nil {
+                               return errors.Wrapf(err, "unmarshaling error on witness component %d, type signature, input %s", i, wc)
+                       }
+                       si.WitnessComponents = append(si.WitnessComponents, &s)
+
+               case "raw_tx_signature":
+                       var s RawTxSigWitness
+                       err = json.Unmarshal(wc, &s)
+                       if err != nil {
+                               return errors.Wrapf(err, "unmarshaling error on witness component %d, type raw_signature, input %s", i, wc)
+                       }
+                       si.WitnessComponents = append(si.WitnessComponents, &s)
+
+               default:
+                       return errors.WithDetailf(ErrBadWitnessComponent, "witness component %d has unknown type '%s'", i, t.Type)
+               }
+       }
+       return nil
+}
old mode 100644 (file)
new mode 100755 (executable)
index d3ace5f..00a02fc
@@ -11,8 +11,6 @@ import (
        "github.com/bytom/math/checked"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
-
-       log "github.com/sirupsen/logrus"
 )
 
 var (
@@ -41,7 +39,6 @@ func Build(ctx context.Context, tx *legacy.TxData, actions []Action, maxTime tim
        for i, action := range actions {
                err := action.Build(ctx, &builder)
 
-               log.WithFields(log.Fields{"action": action, "error": err}).Info("Loop tx's action")
                if err != nil {
                        err = errors.WithData(err, "index", i)
                        errs = append(errs, err)
@@ -72,13 +69,20 @@ func Build(ctx context.Context, tx *legacy.TxData, actions []Action, maxTime tim
        return tpl, nil
 }
 
-
 func Sign(ctx context.Context, tpl *Template, xpubs []chainkd.XPub, auth string, signFn SignFunc) error {
        for i, sigInst := range tpl.SigningInstructions {
-               for j, sw := range sigInst.SignatureWitnesses {
-                       err := sw.sign(ctx, tpl, uint32(i), xpubs, auth, signFn)
-                       if err != nil {
-                               return errors.WithDetailf(err, "adding signature(s) to witness component %d of input %d", j, i)
+               for j, wc := range sigInst.WitnessComponents {
+                       switch sw := wc.(type) {
+                       case *SignatureWitness:
+                               err := sw.sign(ctx, tpl, uint32(i), xpubs, auth, signFn)
+                               if err != nil {
+                                       return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
+                               }
+                       case *RawTxSigWitness:
+                               err := sw.sign(ctx, tpl, uint32(i), xpubs, auth, signFn)
+                               if err != nil {
+                                       return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
+                               }
                        }
                }
        }
diff --git a/blockchain/txbuilder/txbuilder_test.go b/blockchain/txbuilder/txbuilder_test.go
new file mode 100755 (executable)
index 0000000..1731a5c
--- /dev/null
@@ -0,0 +1,268 @@
+package txbuilder
+
+import (
+       "context"
+       "encoding/hex"
+       "math"
+       "testing"
+       "time"
+
+       "github.com/davecgh/go-spew/spew"
+
+       "golang.org/x/crypto/sha3"
+
+       "github.com/bytom/crypto/ed25519"
+       "github.com/bytom/crypto/ed25519/chainkd"
+       "github.com/bytom/encoding/json"
+       "github.com/bytom/errors"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/protocol/bc/legacy"
+       "github.com/bytom/protocol/vm"
+       "github.com/bytom/protocol/vm/vmutil"
+       "github.com/bytom/testutil"
+)
+
+type testAction bc.AssetAmount
+
+func (t testAction) Build(ctx context.Context, b *TemplateBuilder) error {
+       in := legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *t.AssetId, t.Amount, 0, nil, bc.Hash{}, nil)
+       tplIn := &SigningInstruction{}
+
+       err := b.AddInput(in, tplIn)
+       if err != nil {
+               return err
+       }
+       return b.AddOutput(legacy.NewTxOutput(*t.AssetId, t.Amount, []byte("change"), nil))
+}
+
+func newControlProgramAction(assetAmt bc.AssetAmount, script []byte) *controlProgramAction {
+       return &controlProgramAction{
+               AssetAmount: assetAmt,
+               Program:     script,
+       }
+}
+
+func TestBuild(t *testing.T) {
+       ctx := context.Background()
+
+       assetID1 := bc.NewAssetID([32]byte{1})
+       assetID2 := bc.NewAssetID([32]byte{2})
+
+       actions := []Action{
+               newControlProgramAction(bc.AssetAmount{AssetId: &assetID2, Amount: 6}, []byte("dest")),
+               testAction(bc.AssetAmount{AssetId: &assetID1, Amount: 5}),
+               &setTxRefDataAction{Data: []byte("xyz")},
+       }
+       expiryTime := time.Now().Add(time.Minute)
+       got, err := Build(ctx, nil, actions, expiryTime)
+       if err != nil {
+               testutil.FatalErr(t, err)
+       }
+
+       want := &Template{
+               Transaction: legacy.NewTx(legacy.TxData{
+                       Version: 1,
+                       Inputs: []*legacy.TxInput{
+                               legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), assetID1, 5, 0, nil, bc.Hash{}, nil),
+                       },
+                       Outputs: []*legacy.TxOutput{
+                               legacy.NewTxOutput(assetID2, 6, []byte("dest"), nil),
+                               legacy.NewTxOutput(assetID1, 5, []byte("change"), nil),
+                       },
+                       ReferenceData: []byte("xyz"),
+               }),
+               SigningInstructions: []*SigningInstruction{{
+                       WitnessComponents: []witnessComponent{},
+               }},
+       }
+
+       if !testutil.DeepEqual(got.Transaction.TxData, want.Transaction.TxData) {
+               t.Errorf("got tx:\n%s\nwant tx:\n%s", spew.Sdump(got.Transaction.TxData), spew.Sdump(want.Transaction.TxData))
+       }
+
+       if !testutil.DeepEqual(got.SigningInstructions, want.SigningInstructions) {
+               t.Errorf("got signing instructions:\n\t%#v\nwant signing instructions:\n\t%#v", got.SigningInstructions, want.SigningInstructions)
+       }
+
+       // setting tx refdata twice should fail
+       actions = append(actions, &setTxRefDataAction{Data: []byte("lmnop")})
+       _, err = Build(ctx, nil, actions, expiryTime)
+       if errors.Root(err) != ErrAction {
+               t.Errorf("got error %#v, want ErrAction", err)
+       }
+       errs := errors.Data(err)["actions"].([]error)
+       if len(errs) != 1 {
+               t.Errorf("got error %v action errors, want 1", len(errs))
+       }
+       if errors.Root(errs[0]) != ErrBadRefData {
+               t.Errorf("got error %v in action error, want ErrBadRefData", errs[0])
+       }
+}
+
+func TestSignatureWitnessMaterialize(t *testing.T) {
+       var initialBlockHash bc.Hash
+       privkey1, pubkey1, err := chainkd.NewXKeys(nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       privkey2, pubkey2, err := chainkd.NewXKeys(nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       privkey3, pubkey3, err := chainkd.NewXKeys(nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+       issuanceProg, _ := vmutil.P2SPMultiSigProgram([]ed25519.PublicKey{pubkey1.PublicKey(), pubkey2.PublicKey(), pubkey3.PublicKey()}, 2)
+       assetID := bc.ComputeAssetID(issuanceProg, &initialBlockHash, 1, &bc.EmptyStringHash)
+       outscript := mustDecodeHex("76a914c5d128911c28776f56baaac550963f7b88501dc388c0")
+       unsigned := legacy.NewTx(legacy.TxData{
+               Version: 1,
+               Inputs: []*legacy.TxInput{
+                       legacy.NewIssuanceInput([]byte{1}, 100, nil, initialBlockHash, issuanceProg, nil, nil),
+               },
+               Outputs: []*legacy.TxOutput{
+                       legacy.NewTxOutput(assetID, 100, outscript, nil),
+               },
+       })
+
+       tpl := &Template{
+               Transaction: unsigned,
+       }
+       h := tpl.Hash(0)
+       builder := vmutil.NewBuilder()
+       builder.AddData(h.Bytes())
+       builder.AddOp(vm.OP_TXSIGHASH).AddOp(vm.OP_EQUAL)
+       prog, _ := builder.Build()
+       msg := sha3.Sum256(prog)
+       sig1 := privkey1.Sign(msg[:])
+       sig2 := privkey2.Sign(msg[:])
+       sig3 := privkey3.Sign(msg[:])
+       want := [][]byte{
+               vm.Int64Bytes(0),
+               sig1,
+               sig2,
+               prog,
+       }
+
+       // Test with more signatures than required, in correct order
+       tpl.SigningInstructions = []*SigningInstruction{{
+               WitnessComponents: []witnessComponent{
+                       &SignatureWitness{
+                               Quorum: 2,
+                               Keys: []keyID{
+                                       {
+                                               XPub:           pubkey1,
+                                               DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
+                                       },
+                                       {
+                                               XPub:           pubkey2,
+                                               DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
+                                       },
+                                       {
+                                               XPub:           pubkey3,
+                                               DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
+                                       },
+                               },
+                               Program: prog,
+                               Sigs:    []json.HexBytes{sig1, sig2, sig3},
+                       },
+               },
+       }}
+       err = materializeWitnesses(tpl)
+       if err != nil {
+               testutil.FatalErr(t, err)
+       }
+       got := tpl.Transaction.Inputs[0].Arguments()
+       if !testutil.DeepEqual(got, want) {
+               t.Errorf("got input witness %v, want input witness %v", got, want)
+       }
+
+       // Test with exact amount of signatures required, in correct order
+       component := tpl.SigningInstructions[0].WitnessComponents[0].(*SignatureWitness)
+       component.Sigs = []json.HexBytes{sig1, sig2}
+       err = materializeWitnesses(tpl)
+       if err != nil {
+               testutil.FatalErr(t, err)
+       }
+       got = tpl.Transaction.Inputs[0].Arguments()
+       if !testutil.DeepEqual(got, want) {
+               t.Errorf("got input witness %v, want input witness %v", got, want)
+       }
+}
+
+func mustDecodeHex(str string) []byte {
+       data, err := hex.DecodeString(str)
+       if err != nil {
+               panic(err)
+       }
+       return data
+}
+
+func TestCheckBlankCheck(t *testing.T) {
+       cases := []struct {
+               tx   *legacy.TxData
+               want error
+       }{{
+               tx: &legacy.TxData{
+                       Inputs: []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
+               },
+               want: ErrBlankCheck,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
+                       Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 3, nil, nil)},
+               },
+               want: ErrBlankCheck,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs: []*legacy.TxInput{
+                               legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil),
+                               legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.NewAssetID([32]byte{1}), 5, 0, nil, bc.Hash{}, nil),
+                       },
+                       Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
+               },
+               want: ErrBlankCheck,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs: []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
+                       Outputs: []*legacy.TxOutput{
+                               legacy.NewTxOutput(bc.AssetID{}, math.MaxInt64, nil, nil),
+                               legacy.NewTxOutput(bc.AssetID{}, 7, nil, nil),
+                       },
+               },
+               want: ErrBadAmount,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs: []*legacy.TxInput{
+                               legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil),
+                               legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, math.MaxInt64, 0, nil, bc.Hash{}, nil),
+                       },
+               },
+               want: ErrBadAmount,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
+                       Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
+               },
+               want: nil,
+       }, {
+               tx: &legacy.TxData{
+                       Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
+               },
+               want: nil,
+       }, {
+               tx: &legacy.TxData{
+                       Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
+                       Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.NewAssetID([32]byte{1}), 5, nil, nil)},
+               },
+               want: nil,
+       }}
+
+       for _, c := range cases {
+               got := checkBlankCheck(c.tx)
+               if errors.Root(got) != c.want {
+                       t.Errorf("checkUnsafe(%+v) err = %v want %v", c.tx, errors.Root(got), c.want)
+               }
+       }
+}
old mode 100644 (file)
new mode 100755 (executable)
index 3fa6948..43cfd3c
@@ -2,11 +2,9 @@ package txbuilder
 
 import (
        "context"
-       "encoding/json"
        "time"
 
        chainjson "github.com/bytom/encoding/json"
-       "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
 )
@@ -34,36 +32,6 @@ func (t *Template) Hash(idx uint32) bc.Hash {
        return t.Transaction.SigHash(idx)
 }
 
-// SigningInstruction gives directions for signing inputs in a TxTemplate.
-type SigningInstruction struct {
-       Position           uint32              `json:"position"`
-       SignatureWitnesses []*signatureWitness `json:"witness_components,omitempty"`
-}
-
-func (si *SigningInstruction) UnmarshalJSON(b []byte) error {
-       var pre struct {
-               Position           uint32 `json:"position"`
-               SignatureWitnesses []struct {
-                       Type string
-                       signatureWitness
-               } `json:"witness_components"`
-       }
-       err := json.Unmarshal(b, &pre)
-       if err != nil {
-               return err
-       }
-
-       si.Position = pre.Position
-       si.SignatureWitnesses = make([]*signatureWitness, 0, len(pre.SignatureWitnesses))
-       for i, w := range pre.SignatureWitnesses {
-               if w.Type != "signature" {
-                       return errors.WithDetailf(ErrBadWitnessComponent, "witness component %d has unknown type '%s'", i, w.Type)
-               }
-               si.SignatureWitnesses = append(si.SignatureWitnesses, &w.signatureWitness)
-       }
-       return nil
-}
-
 type Action interface {
        Build(context.Context, *TemplateBuilder) error
 }
@@ -71,5 +39,6 @@ type Action interface {
 // Receiver encapsulates information about where to send assets.
 type Receiver struct {
        ControlProgram chainjson.HexBytes `json:"control_program"`
+       Address        string             `json:"address"`
        ExpiresAt      time.Time          `json:"expires_at"`
 }
old mode 100644 (file)
new mode 100755 (executable)
index 87b241a..8daee37
@@ -1,17 +1,10 @@
 package txbuilder
 
 import (
-       "bytes"
        "context"
-       "encoding/json"
 
        "github.com/bytom/crypto/ed25519/chainkd"
-       "github.com/bytom/crypto/sha3pool"
-       chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/vm"
-       "github.com/bytom/protocol/vm/vmutil"
 )
 
 // SignFunc is the function passed into Sign that produces
@@ -38,206 +31,14 @@ func materializeWitnesses(txTemplate *Template) error {
                }
 
                var witness [][]byte
-               for j, sw := range sigInst.SignatureWitnesses {
-                       err := sw.materialize(txTemplate, sigInst.Position, &witness)
+               for j, wc := range sigInst.WitnessComponents {
+                       err := wc.materialize(&witness)
                        if err != nil {
                                return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
                        }
                }
-
                msg.SetInputArguments(sigInst.Position, witness)
        }
 
        return nil
 }
-
-type (
-       signatureWitness struct {
-               // Quorum is the number of signatures required.
-               Quorum int `json:"quorum"`
-
-               // Keys are the identities of the keys to sign with.
-               Keys []keyID `json:"keys"`
-
-               // Program is the predicate part of the signature program, whose hash is what gets
-               // signed. If empty, it is computed during Sign from the outputs
-               // and the current input of the transaction.
-               Program chainjson.HexBytes `json:"program"`
-
-               // Sigs are signatures of Program made from each of the Keys
-               // during Sign.
-               Sigs []chainjson.HexBytes `json:"signatures"`
-       }
-
-       keyID struct {
-               XPub           chainkd.XPub         `json:"xpub"`
-               DerivationPath []chainjson.HexBytes `json:"derivation_path"`
-       }
-)
-
-var ErrEmptyProgram = errors.New("empty signature program")
-
-// Sign populates sw.Sigs with as many signatures of the predicate in
-// sw.Program as it can from the overlapping set of keys in sw.Keys
-// and xpubs.
-//
-// If sw.Program is empty, it is populated with an _inferred_ predicate:
-// a program committing to aspects of the current
-// transaction. Specifically, the program commits to:
-//  - the mintime and maxtime of the transaction (if non-zero)
-//  - the outputID and (if non-empty) reference data of the current input
-//  - the assetID, amount, control program, and (if non-empty) reference data of each output.
-func (sw *signatureWitness) sign(ctx context.Context, tpl *Template, index uint32, xpubs []chainkd.XPub, auth string, signFn SignFunc) error {
-       // Compute the predicate to sign. This is either a
-       // txsighash program if tpl.AllowAdditional is false (i.e., the tx is complete
-       // and no further changes are allowed) or a program enforcing
-       // constraints derived from the existing outputs and current input.
-       if len(sw.Program) == 0 {
-               sw.Program = buildSigProgram(tpl, tpl.SigningInstructions[index].Position)
-               if len(sw.Program) == 0 {
-                       return ErrEmptyProgram
-               }
-       }
-       if len(sw.Sigs) < len(sw.Keys) {
-               // Each key in sw.Keys may produce a signature in sw.Sigs. Make
-               // sure there are enough slots in sw.Sigs and that we preserve any
-               // sigs already present.
-               newSigs := make([]chainjson.HexBytes, len(sw.Keys))
-               copy(newSigs, sw.Sigs)
-               sw.Sigs = newSigs
-       }
-       var h [32]byte
-       sha3pool.Sum256(h[:], sw.Program)
-       for i, keyID := range sw.Keys {
-               if len(sw.Sigs[i]) > 0 {
-                       // Already have a signature for this key
-                       continue
-               }
-               if xpubs != nil && !contains(xpubs, keyID.XPub) {
-                       continue
-               }
-               path := make([]([]byte), len(keyID.DerivationPath))
-               for i, p := range keyID.DerivationPath {
-                       path[i] = p
-               }
-               sigBytes, err := signFn(ctx, keyID.XPub, path, h, auth)
-               if err != nil {
-                       return errors.WithDetailf(err, "computing signature %d", i)
-               }
-               sw.Sigs[i] = sigBytes
-       }
-       return nil
-}
-
-func contains(list []chainkd.XPub, key chainkd.XPub) bool {
-       for _, k := range list {
-               if bytes.Equal(k[:], key[:]) {
-                       return true
-               }
-       }
-       return false
-}
-
-func buildSigProgram(tpl *Template, index uint32) []byte {
-       if !tpl.AllowAdditional {
-               h := tpl.Hash(index)
-               builder := vmutil.NewBuilder()
-               builder.AddData(h.Bytes())
-               builder.AddOp(vm.OP_TXSIGHASH).AddOp(vm.OP_EQUAL)
-               prog, _ := builder.Build() // error is impossible
-               return prog
-       }
-       constraints := make([]constraint, 0, 3+len(tpl.Transaction.Outputs))
-       id := tpl.Transaction.Tx.InputIDs[index]
-       if sp, err := tpl.Transaction.Tx.Spend(id); err == nil {
-               constraints = append(constraints, outputIDConstraint(*sp.SpentOutputId))
-       }
-
-       // Commitment to the tx-level refdata is conditional on it being
-       // non-empty. Commitment to the input-level refdata is
-       // unconditional. Rationale: no one should be able to change "my"
-       // reference data; anyone should be able to set tx refdata but, once
-       // set, it should be immutable.
-       if len(tpl.Transaction.ReferenceData) > 0 {
-               constraints = append(constraints, refdataConstraint{tpl.Transaction.ReferenceData, true})
-       }
-       constraints = append(constraints, refdataConstraint{tpl.Transaction.Inputs[index].ReferenceData, false})
-
-       for i, out := range tpl.Transaction.Outputs {
-               c := &payConstraint{
-                       Index:       i,
-                       AssetAmount: out.AssetAmount,
-                       Program:     out.ControlProgram,
-               }
-               if len(out.ReferenceData) > 0 {
-                       var b32 [32]byte
-                       sha3pool.Sum256(b32[:], out.ReferenceData)
-                       h := bc.NewHash(b32)
-                       c.RefDataHash = &h
-               }
-               constraints = append(constraints, c)
-       }
-       var program []byte
-       for i, c := range constraints {
-               program = append(program, c.code()...)
-               if i < len(constraints)-1 { // leave the final bool on top of the stack
-                       program = append(program, byte(vm.OP_VERIFY))
-               }
-       }
-       return program
-}
-
-func (sw signatureWitness) materialize(tpl *Template, index uint32, args *[][]byte) error {
-       // This is the value of N for the CHECKPREDICATE call. The code
-       // assumes that everything already in the arg list before this call
-       // to Materialize is input to the signature program, so N is
-       // len(*args).
-       // 0 sig1 sig2 ... program  100 sig1 sig2 ... program
-       *args = append(*args, vm.Int64Bytes(int64(len(*args))))
-
-       var nsigs int
-       for i := 0; i < len(sw.Sigs) && nsigs < sw.Quorum; i++ {
-               if len(sw.Sigs[i]) > 0 {
-                       *args = append(*args, sw.Sigs[i])
-                       nsigs++
-               }
-       }
-       *args = append(*args, sw.Program)
-       return nil
-}
-
-func (sw signatureWitness) MarshalJSON() ([]byte, error) {
-       obj := struct {
-               Type   string               `json:"type"`
-               Quorum int                  `json:"quorum"`
-               Keys   []keyID              `json:"keys"`
-               Sigs   []chainjson.HexBytes `json:"signatures"`
-       }{
-               Type:   "signature",
-               Quorum: sw.Quorum,
-               Keys:   sw.Keys,
-               Sigs:   sw.Sigs,
-       }
-       return json.Marshal(obj)
-}
-
-// AddWitnessKeys adds a signatureWitness with the given quorum and
-// list of keys derived by applying the derivation path to each of the
-// xpubs.
-func (si *SigningInstruction) AddWitnessKeys(xpubs []chainkd.XPub, path [][]byte, quorum int) {
-       hexPath := make([]chainjson.HexBytes, 0, len(path))
-       for _, p := range path {
-               hexPath = append(hexPath, p)
-       }
-
-       keyIDs := make([]keyID, 0, len(xpubs))
-       for _, xpub := range xpubs {
-               keyIDs = append(keyIDs, keyID{xpub, hexPath})
-       }
-
-       sw := &signatureWitness{
-               Quorum: quorum,
-               Keys:   keyIDs,
-       }
-       si.SignatureWitnesses = append(si.SignatureWitnesses, sw)
-}
old mode 100644 (file)
new mode 100755 (executable)
index 153d1ac..192f3ab
@@ -1,54 +1,21 @@
 package txbuilder
 
 import (
-       "bytes"
        "encoding/json"
-       "fmt"
        "testing"
 
        "github.com/davecgh/go-spew/spew"
 
        chainjson "github.com/bytom/encoding/json"
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/bc/legacy"
-       "github.com/bytom/protocol/vm"
        "github.com/bytom/testutil"
 )
 
-func TestInferConstraints(t *testing.T) {
-       tpl := &Template{
-               Transaction: legacy.NewTx(legacy.TxData{
-                       Inputs: []*legacy.TxInput{
-                               legacy.NewSpendInput(nil, bc.Hash{}, bc.AssetID{}, 123, 0, nil, bc.Hash{}, []byte{1}),
-                       },
-                       Outputs: []*legacy.TxOutput{
-                               legacy.NewTxOutput(bc.AssetID{}, 123, []byte{10, 11, 12}, nil),
-                       },
-               }),
-               AllowAdditional: true,
-       }
-       prog := buildSigProgram(tpl, 0)
-       spID := tpl.Transaction.Tx.InputIDs[0]
-       spend, err := tpl.Transaction.Tx.Spend(spID)
-       if err != nil {
-               t.Fatal(err)
-       }
-       wantSrc := fmt.Sprintf("0x%x OUTPUTID EQUAL VERIFY 0x2767f15c8af2f2c7225d5273fdd683edc714110a987d1054697c348aed4e6cc7 ENTRYDATA EQUAL VERIFY 0 0 123 0x0000000000000000000000000000000000000000000000000000000000000000 1 0x0a0b0c CHECKOUTPUT", spend.SpentOutputId.Bytes())
-       want, err := vm.Assemble(wantSrc)
-       if err != nil {
-               t.Fatal(err)
-       }
-       if !bytes.Equal(want, prog) {
-               progSrc, _ := vm.Disassemble(prog)
-               t.Errorf("expected sig witness program %x [%s], got %x [%s]", want, wantSrc, prog, progSrc)
-       }
-}
-
 func TestWitnessJSON(t *testing.T) {
        si := &SigningInstruction{
                Position: 17,
-               SignatureWitnesses: []*signatureWitness{
-                       &signatureWitness{
+               WitnessComponents: []witnessComponent{
+                       DataWitness{1, 2, 3},
+                       &SignatureWitness{
                                Quorum: 4,
                                Keys: []keyID{{
                                        XPub:           testutil.TestXPub,
@@ -56,6 +23,14 @@ func TestWitnessJSON(t *testing.T) {
                                }},
                                Sigs: []chainjson.HexBytes{{8, 9, 10}},
                        },
+                       &RawTxSigWitness{
+                               Quorum: 20,
+                               Keys: []keyID{{
+                                       XPub:           testutil.TestXPub,
+                                       DerivationPath: []chainjson.HexBytes{{21, 22}},
+                               }},
+                               Sigs: []chainjson.HexBytes{{23, 24, 25}},
+                       },
                },
        }
 
@@ -67,7 +42,7 @@ func TestWitnessJSON(t *testing.T) {
        var got SigningInstruction
        err = json.Unmarshal(b, &got)
        if err != nil {
-               t.Fatal(err)
+               t.Fatalf("error on input %s: %s", b, err)
        }
 
        if !testutil.DeepEqual(si, &got) {
index 150f741..3f6de8e 100644 (file)
@@ -29,6 +29,7 @@ type rawOutput struct {
 type accountOutput struct {
        rawOutput
        AccountID string
+       Address   string
        keyIndex  uint64
        change    bool
 }
@@ -214,6 +215,7 @@ func loadAccountInfo(outs []*rawOutput, w *Wallet) []*accountOutput {
                        newOut := &accountOutput{
                                rawOutput: *out,
                                AccountID: cp.AccountID,
+                               Address:   cp.Address,
                                keyIndex:  cp.KeyIndex,
                                change:    cp.Change,
                        }
@@ -231,16 +233,19 @@ func upsertConfirmedAccountOutputs(outs []*accountOutput, block *legacy.Block, b
        var u *account.UTXO
 
        for _, out := range outs {
-               u = &account.UTXO{OutputID: out.OutputID.Bytes(),
+               u = &account.UTXO{
+                       OutputID:     out.OutputID.Bytes(),
                        AssetID:      out.AssetId.Bytes(),
                        Amount:       out.Amount,
                        AccountID:    out.AccountID,
+                       Address:      out.Address,
                        ProgramIndex: out.keyIndex,
                        Program:      out.ControlProgram,
                        SourceID:     out.sourceID.Bytes(),
                        SourcePos:    out.sourcePos,
                        RefData:      out.refData.Bytes(),
-                       Change:       out.change}
+                       Change:       out.change,
+               }
 
                rawUTXO, err := json.Marshal(u)
                if err != nil {
index 9328f45..48beca9 100644 (file)
@@ -9,11 +9,12 @@ import (
        "github.com/bytom/blockchain/query"
        "github.com/bytom/blockchain/rpc"
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/config"
        "github.com/bytom/crypto/ed25519/chainkd"
        "github.com/bytom/encoding/json"
-       "github.com/bytom/config"
 
        stdjson "encoding/json"
+
        bchain "github.com/bytom/blockchain"
 )
 
@@ -118,7 +119,6 @@ func IssueTest(client *rpc.Client, args []string) {
 
        fmt.Printf("sign tpl:%v\n", tpl[0])
        fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0])
-       fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0])
 
        // submit-transaction
        var submitResponse interface{}
index 1f8116f..5a37b0a 100644 (file)
@@ -11,9 +11,9 @@ import (
        bchain "github.com/bytom/blockchain"
        "github.com/bytom/blockchain/rpc"
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/config"
        "github.com/bytom/crypto/ed25519/chainkd"
        "github.com/bytom/encoding/json"
-       "github.com/bytom/config"
 )
 
 type accUTXOShort struct {
@@ -121,7 +121,6 @@ func SpendTest(client *rpc.Client, args []string) {
 
        fmt.Printf("sign tpl:%v\n", tpl[0])
        fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0])
-       fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0])
 
        // submit-transaction1-Issue
        var submitResponse interface{}
@@ -168,7 +167,6 @@ func SpendTest(client *rpc.Client, args []string) {
 
        fmt.Printf("sign tpl2:%v\n", tpl2[0])
        fmt.Printf("sign tpl2's SigningInstructions:%v\n", tpl2[0].SigningInstructions[0])
-       fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl2[0].SigningInstructions[0].SignatureWitnesses[0])
 
        // submit-transaction2-Spend_account
        var submitResponse2 interface{}
@@ -249,7 +247,6 @@ func SpendTest(client *rpc.Client, args []string) {
 
        fmt.Printf("sign tpl3:%v\n", tpl2[0])
        fmt.Printf("sign tpl3's SigningInstructions:%v\n", tpl3[0].SigningInstructions[0])
-       fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl3[0].SigningInstructions[0].SignatureWitnesses[0])
 
        // submit-transaction3-Spend_account_utxo
        var submitResponse3 interface{}
index f600681..0e2fa47 100644 (file)
@@ -119,7 +119,6 @@ func WalletTest(client *rpc.Client, args []string) {
        }
        fmt.Printf("sign tpl:%v\n", tpl[0])
        fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0])
-       fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0])
 
        // submit-transaction
        /*
index f17db0d..f7b00c6 100755 (executable)
@@ -63,6 +63,7 @@ var commands = map[string]*command{
        "build-transaction":        {buildTransaction},
        "create-control-program":   {createControlProgram},
        "create-account-receiver":  {createAccountReceiver},
+       "create-account-address":   {createAccountAddress},
        "create-transaction-feed":  {createTxFeed},
        "get-transaction-feed":     {getTxFeed},
        "update-transaction-feed":  {updateTxFeed},
@@ -87,6 +88,7 @@ var commands = map[string]*command{
        "sign-transactions":        {signTransactions},
        "sub-create-issue-tx":      {submitCreateIssueTransaction},
        "sub-spend-account-tx":     {submitSpendTransaction},
+       "sub-control-address-tx":   {submitAddressTransaction},
        "sub-control-receiver-tx":  {submitReceiverTransaction},
        "reset-password":           {resetPassword},
        "update-alias":             {updateAlias},
@@ -553,6 +555,42 @@ func submitCreateIssueTransaction(client *rpc.Client, args []string) {
        fmt.Printf("submit transaction:%v\n", submitResponse)
 }
 
+func submitAddressTransaction(client *rpc.Client, args []string) {
+       if len(args) != 5 {
+               fmt.Println("error: need args: [password] [account id] [asset id] [spend amount] [address]")
+               return
+       }
+
+       fmt.Printf("To build transaction:\n")
+       buildReqFmt := `
+               {"actions": [
+                   {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":20000000, "account_id": "%s"},
+                       {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
+                       {"type": "control_address", "asset_id": "%s", "amount": %s, "address": "%s"}
+       ]}`
+       buildReqStr := fmt.Sprintf(buildReqFmt, args[1], args[2], args[3], args[1], args[2], args[3], args[4])
+
+       var buildReq blockchain.BuildRequest
+       if err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq); err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+
+       tpl := make([]txbuilder.Template, 1)
+       client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl)
+       fmt.Printf("tpl:%v\n", tpl)
+
+       signResp := sign(client, tpl, args[0])
+
+       fmt.Printf("sign tpl:%v\n", tpl[0])
+
+       // submit-transaction-Spend_account
+       var submitResponse interface{}
+       submitArg := blockchain.SubmitArg{Transactions: signResp, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"}
+       client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse)
+       fmt.Printf("submit transaction:%v\n", submitResponse)
+}
+
 func submitReceiverTransaction(client *rpc.Client, args []string) {
        if len(args) != 5 {
                fmt.Println("error: need args: [password] [account id] [asset id] [spend amount] [control_program]")
@@ -664,6 +702,26 @@ func createAccountReceiver(client *rpc.Client, args []string) {
        fmt.Printf("responses:%v\n", response)
 }
 
+func createAccountAddress(client *rpc.Client, args []string) {
+       if len(args) != 1 {
+               fmt.Println("error: need args: [account id] or [account alias]")
+               return
+       }
+       type Ins struct {
+               AccountInfo string    `json:"account_info"`
+               ExpiresAt   time.Time `json:"expires_at"`
+       }
+       var ins Ins
+       var response interface{}
+
+       //TODO:undefined argument to ExpiresAt
+
+       ins.AccountInfo = args[0]
+
+       client.Call(context.Background(), "/create-account-address", &ins, &response)
+       fmt.Printf("responses:%v\n", response)
+}
+
 func createTxFeed(client *rpc.Client, args []string) {
        if len(args) != 2 {
                fatalln("error:createTxFeed need arguments")
index cce2848..a63d351 100644 (file)
@@ -4,6 +4,7 @@ import (
        log "github.com/sirupsen/logrus"
 
        "github.com/bytom/consensus"
+       "github.com/bytom/consensus/difficulty"
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
@@ -62,7 +63,7 @@ func GenerateGenesisBlock() *legacy.Block {
                genesisBlock.Nonce = i
                hash := genesisBlock.Hash()
 
-               if consensus.CheckProofOfWork(&hash, genesisBlock.Bits) {
+               if difficulty.CheckProofOfWork(&hash, genesisBlock.Bits) {
                        break
                }
        }
similarity index 91%
rename from consensus/difficulty.go
rename to consensus/difficulty/difficulty.go
index 0703afd..703080e 100755 (executable)
@@ -1,9 +1,10 @@
-package consensus
+package difficulty
 
 // HashToBig converts a *bc.Hash into a big.Int that can be used to
 import (
        "math/big"
 
+       "github.com/bytom/consensus"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
 )
@@ -87,12 +88,12 @@ func CheckProofOfWork(hash *bc.Hash, bits uint64) bool {
 
 func CalcNextRequiredDifficulty(lastBH, compareBH *legacy.BlockHeader) uint64 {
        if lastBH == nil {
-               return powMinBits
-       } else if (lastBH.Height)%BlocksPerRetarget != 0 {
+               return consensus.PowMinBits
+       } else if (lastBH.Height)%consensus.BlocksPerRetarget != 0 {
                return lastBH.Bits
        }
 
-       targetTimeSpan := int64(BlocksPerRetarget * targetSecondsPerBlock)
+       targetTimeSpan := int64(consensus.BlocksPerRetarget * consensus.TargetSecondsPerBlock)
        actualTimeSpan := int64(lastBH.Time().Sub(compareBH.Time()).Seconds())
 
        oldTarget := CompactToBig(lastBH.Bits)
similarity index 59%
rename from consensus/difficulty_test.go
rename to consensus/difficulty/difficulty_test.go
index b53e7ab..46d3a2a 100644 (file)
@@ -1,14 +1,15 @@
-package consensus
+package difficulty
 
 import (
        "math/big"
        "testing"
 
+       "github.com/bytom/consensus"
        "github.com/bytom/protocol/bc/legacy"
 )
 
 func TestCalcNextRequiredDifficulty(t *testing.T) {
-       targetTimeSpan := uint64(BlocksPerRetarget * targetSecondsPerBlock * 1000)
+       targetTimeSpan := uint64(consensus.BlocksPerRetarget * consensus.TargetSecondsPerBlock * 1000)
        cases := []struct {
                lastBH    *legacy.BlockHeader
                compareBH *legacy.BlockHeader
@@ -17,17 +18,18 @@ func TestCalcNextRequiredDifficulty(t *testing.T) {
                //{nil, nil, powMinBits},
                //{&legacy.BlockHeader{Height: BlocksPerRetarget, Bits: 87654321}, nil, 87654321},
                {
-                       &legacy.BlockHeader{Height: BlocksPerRetarget, TimestampMS: targetTimeSpan, Bits: BigToCompact(big.NewInt(1000))},
+                       &legacy.BlockHeader{Height: consensus.BlocksPerRetarget, TimestampMS: targetTimeSpan, Bits: BigToCompact(big.NewInt(1000))},
                        &legacy.BlockHeader{Height: 0, TimestampMS: 0},
                        BigToCompact(big.NewInt(1000)),
                },
                {
-                       &legacy.BlockHeader{Height: BlocksPerRetarget, TimestampMS: targetTimeSpan * 2, Bits: BigToCompact(big.NewInt(1000))},
+                       &legacy.BlockHeader{Height: consensus.BlocksPerRetarget, TimestampMS: targetTimeSpan * 2, Bits: BigToCompact(big.NewInt(1000))},
                        &legacy.BlockHeader{Height: 0, TimestampMS: 0},
                        BigToCompact(big.NewInt(2000)),
                },
                {
-                       &legacy.BlockHeader{Height: BlocksPerRetarget, TimestampMS: targetTimeSpan / 2, Bits: BigToCompact(big.NewInt(1000))},
+                       &legacy.BlockHeader{Height: consensus.
+                               BlocksPerRetarget, TimestampMS: targetTimeSpan / 2, Bits: BigToCompact(big.NewInt(1000))},
                        &legacy.BlockHeader{Height: 0, TimestampMS: 0},
                        BigToCompact(big.NewInt(500)),
                },
index 5c83f60..92eb3ca 100644 (file)
@@ -19,9 +19,9 @@ const (
        initialBlockSubsidy        = uint64(1470000000000000000)
 
        // config for pow mining
-       powMinBits            = uint64(2161727821138738707)
+       PowMinBits            = uint64(2161727821138738707)
        BlocksPerRetarget     = uint64(1024)
-       targetSecondsPerBlock = uint64(60)
+       TargetSecondsPerBlock = uint64(60)
 )
 
 // BTMAssetID is BTM's asset id, the soul asset of Bytom
diff --git a/integration_test/standard_transaction_test.go b/integration_test/standard_transaction_test.go
new file mode 100644 (file)
index 0000000..5a5febe
--- /dev/null
@@ -0,0 +1,112 @@
+package integrationTest
+
+import (
+       "context"
+       "io/ioutil"
+       "os"
+       "testing"
+       "time"
+
+       dbm "github.com/tendermint/tmlibs/db"
+
+       "github.com/bytom/blockchain/account"
+       "github.com/bytom/blockchain/pseudohsm"
+       "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/blockchain/txdb"
+       "github.com/bytom/consensus"
+       "github.com/bytom/crypto/ed25519/chainkd"
+       "github.com/bytom/protocol"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/protocol/bc/legacy"
+       "github.com/bytom/protocol/validation"
+       "github.com/bytom/protocol/vm"
+)
+
+func TestP2PKH(t *testing.T) {
+       dirPath, err := ioutil.TempDir(".", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.RemoveAll(dirPath)
+
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       chain, err := mockChain(testDB)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       accountManager := account.NewManager(testDB, chain)
+       hsm, err := pseudohsm.New(dirPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub, err := hsm.XCreate("test_pub", "password")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       testAccount, err := accountManager.Create(nil, []chainkd.XPub{xpub.XPub}, 1, "testAccount", nil, "")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       controlProg, err := accountManager.CreateP2PKH(nil, testAccount.Signer.ID, false, time.Now())
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       utxo := account.NewUtxo()
+       utxo.OutputID = bc.Hash{V0: 1}
+       utxo.SourceID = bc.Hash{V0: 2}
+       utxo.AssetID = *consensus.BTMAssetID
+       utxo.Amount = 1000000000
+       utxo.SourcePos = 0
+       utxo.ControlProgram = controlProg.ControlProgram
+       utxo.AccountID = controlProg.AccountID
+       utxo.Address = controlProg.Address
+       utxo.ControlProgramIndex = controlProg.KeyIndex
+       txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo, nil)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       b := txbuilder.NewBuilder(time.Now())
+       b.AddInput(txInput, sigInst)
+       out := legacy.NewTxOutput(*consensus.BTMAssetID, 100, []byte{byte(vm.OP_FAIL)}, nil)
+       b.AddOutput(out)
+       tpl, tx, err := b.Build()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       err = txbuilder.Sign(nil, tpl, nil, "password", func(_ context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
+               sigBytes, err := hsm.XSign(xpub, path, data[:], password)
+               if err != nil {
+                       return nil, nil
+               }
+               return sigBytes, err
+       })
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       bcBlock := &bc.Block{
+               BlockHeader: &bc.BlockHeader{Height: 1},
+       }
+       if _, err = validation.ValidateTx(legacy.MapTx(tx), bcBlock); err != nil {
+               t.Fatal(err)
+       }
+}
+
+func mockChain(testDB dbm.DB) (*protocol.Chain, error) {
+       store := txdb.NewStore(testDB)
+       txPool := protocol.NewTxPool()
+       chain, err := protocol.NewChain(bc.Hash{}, store, txPool)
+       if err != nil {
+               return nil, err
+       }
+       return chain, nil
+}
index 862b49c..ab349b1 100644 (file)
@@ -11,8 +11,8 @@ import (
        log "github.com/sirupsen/logrus"
 
        "github.com/bytom/blockchain/account"
-       "github.com/bytom/consensus"
        "github.com/bytom/consensus/algorithm"
+       "github.com/bytom/consensus/difficulty"
        "github.com/bytom/mining"
        "github.com/bytom/protocol"
        "github.com/bytom/protocol/bc/legacy"
@@ -74,7 +74,7 @@ func (m *CPUMiner) solveBlock(block *legacy.Block, ticker *time.Ticker, quit cha
                        return false
                }
 
-               if consensus.CheckProofOfWork(proofHash, header.Bits) {
+               if difficulty.CheckProofOfWork(proofHash, header.Bits) {
                        return true
                }
        }
index 256d2ab..ab2c934 100644 (file)
@@ -13,6 +13,7 @@ import (
        "github.com/bytom/blockchain/txbuilder"
        "github.com/bytom/consensus"
        "github.com/bytom/consensus/algorithm"
+       "github.com/bytom/consensus/difficulty"
        "github.com/bytom/errors"
        "github.com/bytom/protocol"
        "github.com/bytom/protocol/bc"
@@ -81,7 +82,7 @@ func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager
                        Seed:              *nextBlockSeed,
                        TimestampMS:       bc.Millis(time.Now()),
                        BlockCommitment:   legacy.BlockCommitment{},
-                       Bits:              consensus.CalcNextRequiredDifficulty(&preBlock.BlockHeader, compareDiffBH),
+                       Bits:              difficulty.CalcNextRequiredDifficulty(&preBlock.BlockHeader, compareDiffBH),
                },
                Transactions: make([]*legacy.Tx, 0, len(txDescs)),
        }
index fe60ffd..2f9c67c 100644 (file)
@@ -5,6 +5,7 @@ import (
 
        "github.com/bytom/consensus"
        "github.com/bytom/consensus/algorithm"
+       "github.com/bytom/consensus/difficulty"
        "github.com/bytom/errors"
        "github.com/bytom/math/checked"
        "github.com/bytom/protocol/bc"
@@ -521,7 +522,7 @@ func ValidateBlock(b, prev *bc.Block, seedCaches *seed.SeedCaches) error {
        if err != nil {
                return err
        }
-       if !consensus.CheckProofOfWork(proofHash, b.BlockHeader.Bits) {
+       if !difficulty.CheckProofOfWork(proofHash, b.BlockHeader.Bits) {
                return errWorkProof
        }
 
index 5aefcb4..ac94f4d 100644 (file)
@@ -6,6 +6,7 @@ import (
 
        "golang.org/x/crypto/sha3"
 
+       "github.com/bytom/crypto"
        "github.com/bytom/crypto/ed25519"
        "github.com/bytom/math/checked"
 )
@@ -136,3 +137,17 @@ func opTxSigHash(vm *virtualMachine) error {
        }
        return vm.push(vm.context.TxSigHash(), false)
 }
+
+func opHash160(vm *virtualMachine) error {
+       data, err := vm.pop(false)
+       if err != nil {
+               return err
+       }
+
+       cost := int64(len(data) + 64)
+       if err = vm.applyCost(cost); err != nil {
+               return err
+       }
+
+       return vm.push(crypto.Ripemd160(data), false)
+}
index 0e6ed6d..da0cda9 100644 (file)
@@ -196,6 +196,7 @@ const (
 
        OP_SHA256        Op = 0xa8
        OP_SHA3          Op = 0xaa
+       OP_HASH160       Op = 0xab
        OP_CHECKSIG      Op = 0xac
        OP_CHECKMULTISIG Op = 0xad
        OP_TXSIGHASH     Op = 0xae
@@ -304,6 +305,7 @@ var (
 
                OP_SHA256:        {OP_SHA256, "SHA256", opSha256},
                OP_SHA3:          {OP_SHA3, "SHA3", opSha3},
+               OP_HASH160:       {OP_HASH160, "HASH160", opHash160},
                OP_CHECKSIG:      {OP_CHECKSIG, "CHECKSIG", opCheckSig},
                OP_CHECKMULTISIG: {OP_CHECKMULTISIG, "CHECKMULTISIG", opCheckMultiSig},
                OP_TXSIGHASH:     {OP_TXSIGHASH, "TXSIGHASH", opTxSigHash},
index 13539fd..1772b73 100644 (file)
@@ -52,6 +52,18 @@ func CoinbaseProgram(pubkeys []ed25519.PublicKey, nrequired int, height uint64)
        return builder.Build()
 }
 
+func P2PKHSigProgram(pubkeyHash []byte) ([]byte, error) {
+       builder := NewBuilder()
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_HASH160)
+       builder.AddData(pubkeyHash)
+       builder.AddOp(vm.OP_EQUALVERIFY)
+       builder.AddOp(vm.OP_TXSIGHASH)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddOp(vm.OP_CHECKSIG)
+       return builder.Build()
+}
+
 // P2SPMultiSigProgram generates the script for contorl transaction output
 func P2SPMultiSigProgram(pubkeys []ed25519.PublicKey, nrequired int) ([]byte, error) {
        builder := NewBuilder()