"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"
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 {
//CtrlProgram is structure of account control program
type CtrlProgram struct {
AccountID string
+ Address string
KeyIndex uint64
ControlProgram []byte
Change bool
"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"
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")
}
}
}
- 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
}
}
}
-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
}
AssetID []byte
Amount uint64
AccountID string
+ Address string
ProgramIndex uint64
Program []byte
SourceID []byte
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
+}
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}
}
ControlProgram: accountUTXO.Program,
RefDataHash: bc.NewHash(rawRefData),
AccountID: src.AccountID,
+ Address: accountUTXO.Address,
ControlProgramIndex: accountUTXO.ProgramIndex,
})
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
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
+}
package blockchain
import (
- "time"
"net/http"
+ "time"
log "github.com/sirupsen/logrus"
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))
switch action {
case "control_account":
decoder = a.accounts.DecodeControlAction
+ case "control_address":
+ decoder = txbuilder.DecodeControlAddressAction
case "control_program":
decoder = txbuilder.DecodeControlProgramAction
case "control_receiver":
"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)}
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)
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)
--- /dev/null
+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)
+}
--- /dev/null
+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)
+}
--- /dev/null
+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
+}
--- /dev/null
+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)
+}
--- /dev/null
+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
+}
"github.com/bytom/math/checked"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/legacy"
-
- log "github.com/sirupsen/logrus"
)
var (
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)
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)
+ }
}
}
}
--- /dev/null
+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)
+ }
+ }
+}
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"
)
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
}
// 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"`
}
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
}
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)
-}
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,
}},
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}},
+ },
},
}
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) {
type accountOutput struct {
rawOutput
AccountID string
+ Address string
keyIndex uint64
change bool
}
newOut := &accountOutput{
rawOutput: *out,
AccountID: cp.AccountID,
+ Address: cp.Address,
keyIndex: cp.KeyIndex,
change: cp.Change,
}
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 {
"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"
)
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{}
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 {
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{}
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{}
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{}
}
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
/*
"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},
"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},
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]")
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")
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"
genesisBlock.Nonce = i
hash := genesisBlock.Hash()
- if consensus.CheckProofOfWork(&hash, genesisBlock.Bits) {
+ if difficulty.CheckProofOfWork(&hash, genesisBlock.Bits) {
break
}
}
-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"
)
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)
-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
//{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)),
},
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
--- /dev/null
+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
+}
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"
return false
}
- if consensus.CheckProofOfWork(proofHash, header.Bits) {
+ if difficulty.CheckProofOfWork(proofHash, header.Bits) {
return true
}
}
"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"
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)),
}
"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"
if err != nil {
return err
}
- if !consensus.CheckProofOfWork(proofHash, b.BlockHeader.Bits) {
+ if !difficulty.CheckProofOfWork(proofHash, b.BlockHeader.Bits) {
return errWorkProof
}
"golang.org/x/crypto/sha3"
+ "github.com/bytom/crypto"
"github.com/bytom/crypto/ed25519"
"github.com/bytom/math/checked"
)
}
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)
+}
OP_SHA256 Op = 0xa8
OP_SHA3 Op = 0xaa
+ OP_HASH160 Op = 0xab
OP_CHECKSIG Op = 0xac
OP_CHECKMULTISIG Op = 0xad
OP_TXSIGHASH Op = 0xae
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},
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()