From: Paladz Date: Tue, 26 Dec 2017 09:10:35 +0000 (+0800) Subject: Address (#213) X-Git-Tag: v1.0.5~398 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=7136d89b769f752d7eb85ad40c46384f2b5c8cc5;p=bytom%2Fbytom.git Address (#213) * 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 --- diff --git a/blockchain/account/accounts.go b/blockchain/account/accounts.go index 57489dc2..18af0011 100755 --- a/blockchain/account/accounts.go +++ b/blockchain/account/accounts.go @@ -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 diff --git a/blockchain/account/builder.go b/blockchain/account/builder.go index eb3e4dad..07bb3920 100644 --- a/blockchain/account/builder.go +++ b/blockchain/account/builder.go @@ -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 } diff --git a/blockchain/account/indexer.go b/blockchain/account/indexer.go index 8eb7ac5d..03fd2fb9 100755 --- a/blockchain/account/indexer.go +++ b/blockchain/account/indexer.go @@ -26,6 +26,7 @@ type UTXO struct { AssetID []byte Amount uint64 AccountID string + Address string ProgramIndex uint64 Program []byte SourceID []byte diff --git a/blockchain/account/receivers.go b/blockchain/account/receivers.go index bf43c042..b091550e 100755 --- a/blockchain/account/receivers.go +++ b/blockchain/account/receivers.go @@ -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 +} diff --git a/blockchain/account/reserve.go b/blockchain/account/reserve.go index 4d300884..a0e7bd1a 100755 --- a/blockchain/account/reserve.go +++ b/blockchain/account/reserve.go @@ -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 diff --git a/blockchain/receivers.go b/blockchain/receivers.go index af14e29e..dbaa2f77 100755 --- a/blockchain/receivers.go +++ b/blockchain/receivers.go @@ -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 +} diff --git a/blockchain/rpc_reactor.go b/blockchain/rpc_reactor.go index 9e64f6e2..1e32993d 100644 --- a/blockchain/rpc_reactor.go +++ b/blockchain/rpc_reactor.go @@ -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)) diff --git a/blockchain/transact.go b/blockchain/transact.go index 45aae11f..7ae5665a 100644 --- a/blockchain/transact.go +++ b/blockchain/transact.go @@ -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": diff --git a/blockchain/txbuilder/actions.go b/blockchain/txbuilder/actions.go index e2897611..5813b06e 100644 --- a/blockchain/txbuilder/actions.go +++ b/blockchain/txbuilder/actions.go @@ -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) diff --git a/blockchain/txbuilder/builder.go b/blockchain/txbuilder/builder.go old mode 100644 new mode 100755 index 1b4a2549..5cdb8dc7 --- a/blockchain/txbuilder/builder.go +++ b/blockchain/txbuilder/builder.go @@ -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 index 00000000..4120fe90 --- /dev/null +++ b/blockchain/txbuilder/data_witness.go @@ -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) +} diff --git a/blockchain/txbuilder/finalize.go b/blockchain/txbuilder/finalize.go old mode 100644 new mode 100755 diff --git a/blockchain/txbuilder/rawtxsig_witness.go b/blockchain/txbuilder/rawtxsig_witness.go new file mode 100755 index 00000000..7cf15733 --- /dev/null +++ b/blockchain/txbuilder/rawtxsig_witness.go @@ -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 index 00000000..5111f9ce --- /dev/null +++ b/blockchain/txbuilder/signature_program.go @@ -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 ... 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 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 index 00000000..0f28216e --- /dev/null +++ b/blockchain/txbuilder/signature_witness.go @@ -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 index 00000000..a2c1d602 --- /dev/null +++ b/blockchain/txbuilder/signing_instruction.go @@ -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 +} diff --git a/blockchain/txbuilder/txbuilder.go b/blockchain/txbuilder/txbuilder.go old mode 100644 new mode 100755 index d3ace5f0..00a02fc7 --- a/blockchain/txbuilder/txbuilder.go +++ b/blockchain/txbuilder/txbuilder.go @@ -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 index 00000000..1731a5c7 --- /dev/null +++ b/blockchain/txbuilder/txbuilder_test.go @@ -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) + } + } +} diff --git a/blockchain/txbuilder/types.go b/blockchain/txbuilder/types.go old mode 100644 new mode 100755 index 3fa6948b..43cfd3c6 --- a/blockchain/txbuilder/types.go +++ b/blockchain/txbuilder/types.go @@ -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"` } diff --git a/blockchain/txbuilder/witness.go b/blockchain/txbuilder/witness.go old mode 100644 new mode 100755 index 87b241a0..8daee37b --- a/blockchain/txbuilder/witness.go +++ b/blockchain/txbuilder/witness.go @@ -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) -} diff --git a/blockchain/txbuilder/witness_test.go b/blockchain/txbuilder/witness_test.go old mode 100644 new mode 100755 index 153d1ac5..192f3abb --- a/blockchain/txbuilder/witness_test.go +++ b/blockchain/txbuilder/witness_test.go @@ -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) { diff --git a/blockchain/wallet/indexer.go b/blockchain/wallet/indexer.go index 150f7416..3f6de8ea 100644 --- a/blockchain/wallet/indexer.go +++ b/blockchain/wallet/indexer.go @@ -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 { diff --git a/cmd/bytomcli/example/issue.go b/cmd/bytomcli/example/issue.go index 9328f458..48beca95 100644 --- a/cmd/bytomcli/example/issue.go +++ b/cmd/bytomcli/example/issue.go @@ -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{} diff --git a/cmd/bytomcli/example/spend.go b/cmd/bytomcli/example/spend.go index 1f8116f6..5a37b0af 100644 --- a/cmd/bytomcli/example/spend.go +++ b/cmd/bytomcli/example/spend.go @@ -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{} diff --git a/cmd/bytomcli/example/wallet.go b/cmd/bytomcli/example/wallet.go index f6006811..0e2fa470 100644 --- a/cmd/bytomcli/example/wallet.go +++ b/cmd/bytomcli/example/wallet.go @@ -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 /* diff --git a/cmd/bytomcli/main.go b/cmd/bytomcli/main.go index f17db0d4..f7b00c60 100755 --- a/cmd/bytomcli/main.go +++ b/cmd/bytomcli/main.go @@ -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") diff --git a/config/genesis.go b/config/genesis.go index cce2848c..a63d351c 100644 --- a/config/genesis.go +++ b/config/genesis.go @@ -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 } } diff --git a/consensus/difficulty.go b/consensus/difficulty/difficulty.go similarity index 91% rename from consensus/difficulty.go rename to consensus/difficulty/difficulty.go index 0703afd7..703080e7 100755 --- a/consensus/difficulty.go +++ b/consensus/difficulty/difficulty.go @@ -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) diff --git a/consensus/difficulty_test.go b/consensus/difficulty/difficulty_test.go similarity index 59% rename from consensus/difficulty_test.go rename to consensus/difficulty/difficulty_test.go index b53e7ab9..46d3a2af 100644 --- a/consensus/difficulty_test.go +++ b/consensus/difficulty/difficulty_test.go @@ -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)), }, diff --git a/consensus/general.go b/consensus/general.go index 5c83f60e..92eb3ca0 100644 --- a/consensus/general.go +++ b/consensus/general.go @@ -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 index 00000000..5a5febea --- /dev/null +++ b/integration_test/standard_transaction_test.go @@ -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 +} diff --git a/mining/cpuminer/cpuminer.go b/mining/cpuminer/cpuminer.go index 862b49c0..ab349b13 100644 --- a/mining/cpuminer/cpuminer.go +++ b/mining/cpuminer/cpuminer.go @@ -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 } } diff --git a/mining/mining.go b/mining/mining.go index 256d2abc..ab2c9345 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -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)), } diff --git a/protocol/validation/validation.go b/protocol/validation/validation.go index fe60ffdb..2f9c67ca 100644 --- a/protocol/validation/validation.go +++ b/protocol/validation/validation.go @@ -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 } diff --git a/protocol/vm/crypto.go b/protocol/vm/crypto.go index 5aefcb43..ac94f4d5 100644 --- a/protocol/vm/crypto.go +++ b/protocol/vm/crypto.go @@ -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) +} diff --git a/protocol/vm/ops.go b/protocol/vm/ops.go index 0e6ed6d3..da0cda9a 100644 --- a/protocol/vm/ops.go +++ b/protocol/vm/ops.go @@ -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}, diff --git a/protocol/vm/vmutil/script.go b/protocol/vm/vmutil/script.go index 13539fd4..1772b738 100644 --- a/protocol/vm/vmutil/script.go +++ b/protocol/vm/vmutil/script.go @@ -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()