return account.Signer, nil
}
+// GetAliasByID return the account alias by given ID
func (m *Manager) GetAliasByID(id string) string {
var account Account
return account.Alias
}
-// CreateP2PKH generate an address for the select account
-func (m *Manager) CreateP2PKH(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
- cp, err := m.createP2PKH(ctx, accountID, change, expiresAt)
+// CreateAddress generate an address for the select account
+func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool, expiresAt time.Time) (cp *CtrlProgram, err error) {
+ account, err := m.findByID(ctx, accountID)
+ if err != nil {
+ return nil, err
+ }
+
+ if account.Quorum == 1 {
+ cp, err = m.createP2PKH(ctx, account, change, expiresAt)
+ } else {
+ cp, err = m.createP2SH(ctx, account, change, expiresAt)
+ }
if 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)
+func (m *Manager) createP2PKH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
+ idx, err := m.nextIndex(ctx)
if err != nil {
return nil, err
}
- if account.Quorum != 1 {
- return nil, ErrStandardQuorum
+ 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) createP2SH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
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)
+ derivedPKs := chainkd.XPubKeys(derivedXPubs)
+ signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
+ if err != nil {
+ return nil, err
+ }
+ scriptHash := crypto.Sha256(signScript)
// TODO: pass different params due to config
- address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
+ address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
if err != nil {
return nil, err
}
- control, err := vmutil.P2PKHSigProgram([]byte(pubHash))
+ control, err := vmutil.P2SHProgram(scriptHash)
if err != nil {
return nil, err
}
"github.com/bytom/blockchain/signers"
"github.com/bytom/blockchain/txbuilder"
+ "github.com/bytom/common"
+ "github.com/bytom/consensus"
"github.com/bytom/crypto/ed25519/chainkd"
chainjson "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/vmutil"
)
//DecodeSpendAction unmarshal JSON-encoded data of spend action
// UtxoToInputs convert an utxo to the txinput
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)
-
path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
sigInst := &txbuilder.SigningInstruction{}
+ if u.Address == "" {
+ sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+ return txInput, sigInst, nil
+ }
- //TODO: handle pay to script hash address
- if u.Address != "" {
+ address, err := common.DecodeAddress(u.Address, &consensus.MainNetParams)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ switch address.(type) {
+ case *common.AddressWitnessPubKeyHash:
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 {
+
+ case *common.AddressWitnessScriptHash:
sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+ path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
+ derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
+ derivedPKs := chainkd.XPubKeys(derivedXPubs)
+ script, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
+ if err != nil {
+ return nil, nil, err
+ }
+ sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
+
+ default:
+ return nil, nil, errors.New("unsupport address type")
}
+
return txInput, sigInst, nil
}
return receiver, nil
}
-// CreateAddress creates a new address receiver for an account
-func (m *Manager) CreateAddress(ctx context.Context, accountInfo string, expiresAt time.Time) (*txbuilder.Receiver, error) {
+// CreateAddressReceiver creates a new address receiver for an account
+func (m *Manager) CreateAddressReceiver(ctx context.Context, accountInfo string, expiresAt time.Time) (*txbuilder.Receiver, error) {
if expiresAt.IsZero() {
expiresAt = time.Now().Add(defaultReceiverExpiry)
}
accountID := accountInfo
-
if s, err := m.FindByAlias(ctx, accountInfo); err == nil {
accountID = s.ID
}
- program, err := m.CreateP2PKH(ctx, accountID, false, expiresAt)
+ program, err := m.CreateAddress(ctx, accountID, false, expiresAt)
if err != nil {
return nil, err
}
AccountInfo string `json:"account_info"`
ExpiresAt time.Time `json:"expires_at"`
}) Response {
- receiver, err := bcr.accounts.CreateAddress(ctx, ins.AccountInfo, ins.ExpiresAt)
+ receiver, err := bcr.accounts.CreateAddressReceiver(ctx, ins.AccountInfo, ins.ExpiresAt)
if err != nil {
return resWrapper(nil, err)
}
import (
"context"
stdjson "encoding/json"
+ "errors"
"github.com/bytom/common"
"github.com/bytom/consensus"
var retirementProgram = []byte{byte(vm.OP_FAIL)}
+// DecodeControlReceiverAction convert input data to action struct
func DecodeControlReceiverAction(data []byte) (Action, error) {
a := new(controlReceiverAction)
err := stdjson.Unmarshal(data, a)
return b.AddOutput(out)
}
+// DecodeControlAddressAction convert input data to action struct
func DecodeControlAddressAction(data []byte) (Action, error) {
a := new(controlAddressAction)
err := stdjson.Unmarshal(data, a)
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
+ }
+ redeemContract := address.ScriptAddress()
+ program := []byte{}
+
+ switch address.(type) {
+ case *common.AddressWitnessPubKeyHash:
+ program, err = vmutil.P2PKHSigProgram(redeemContract)
+ case *common.AddressWitnessScriptHash:
+ program, err = vmutil.P2SHProgram(redeemContract)
+ default:
+ return errors.New("unsupport address type")
+ }
if err != nil {
return err
}
return b.AddOutput(out)
}
+// DecodeControlProgramAction convert input data to action struct
func DecodeControlProgramAction(data []byte) (Action, error) {
a := new(controlProgramAction)
err := stdjson.Unmarshal(data, a)
return b.AddOutput(out)
}
+// DecodeSetTxRefDataAction convert input data to action struct
func DecodeSetTxRefDataAction(data []byte) (Action, error) {
a := new(setTxRefDataAction)
err := stdjson.Unmarshal(data, a)
return b.setReferenceData(a.Data)
}
+// DecodeRetireAction convert input data to action struct
func DecodeRetireAction(data []byte) (Action, error) {
a := new(retireAction)
err := stdjson.Unmarshal(data, a)
BytomcliCmd.AddCommand(listAccountsCmd)
BytomcliCmd.AddCommand(updateAccountTagsCmd)
BytomcliCmd.AddCommand(createAccountReceiverCmd)
+ BytomcliCmd.AddCommand(createAccountAddressCmd)
BytomcliCmd.AddCommand(createAssetCmd)
BytomcliCmd.AddCommand(listAssetsCmd)
func init() {
buildTransactionCmd.PersistentFlags().StringVarP(&buildType, "type", "t", "", "transaction type, valid types: 'issue', 'spend'")
buildTransactionCmd.PersistentFlags().StringVarP(&receiverProgram, "receiver", "r", "", "program of receiver")
+ buildTransactionCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "address of receiver")
buildTransactionCmd.PersistentFlags().StringVarP(&btmGas, "gas", "g", "20000000", "program of receiver")
buildTransactionCmd.PersistentFlags().BoolVar(&pretty, "pretty", false, "pretty print json result")
buildTransactionCmd.PersistentFlags().BoolVar(&alias, "alias", false, "use alias build transaction")
buildType = ""
btmGas = ""
receiverProgram = ""
+ address = ""
password = ""
pretty = false
alias = false
{"type": "retire", "asset_alias": "%s","amount": %s,"account_alias": "%s"}
]}`
+var buildControlAddressReqFmt = `
+ {"actions": [
+ {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"},
+ {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
+ {"type": "control_address", "asset_id": "%s", "amount": %s,"address": "%s"}
+ ]}`
+
+var buildControlAddressReqFmtByAlias = `
+ {"actions": [
+ {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_alias": "%s"},
+ {"type": "spend_account", "asset_id": "%s","amount": %s, "account_alias": "%s"},
+ {"type": "control_address", "asset_id": "%s", "amount": %s,"address": "%s"}
+ ]}`
+
var buildTransactionCmd = &cobra.Command{
Use: "build-transaction <accountID|alias> <assetID|alias> <amount>",
Short: "Build one transaction template,default use account id and asset id",
buildReqStr = fmt.Sprintf(buildRetireReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
break
}
- buildReqStr = fmt.Sprintf(buildRetireReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+ buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+ case "address":
+ if alias {
+ buildReqStr = fmt.Sprintf(buildControlAddressReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
+ break
+ }
+ buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
default:
jww.ERROR.Println("Invalid transaction template type")
os.Exit(ErrLocalExe)
// Check for valid program length for witness version 0, which is 20
// for P2WPKH.
if len(witnessProg) != 20 {
- return nil, errors.New("witness program must be 20 " +
- "bytes for p2wpkh")
+ return nil, errors.New("witness program must be 20 bytes for p2wpkh")
}
addr := &AddressWitnessPubKeyHash{
// Check for valid program length for witness version 0, which is 32
// for P2WSH.
if len(witnessProg) != 32 {
- return nil, errors.New("witness program must be 32 " +
- "bytes for p2wsh")
+ return nil, errors.New("witness program must be 32 bytes for p2wsh")
}
addr := &AddressWitnessScriptHash{
return a.witnessProgram[:]
}
-// Hash160 returns the witness program of the AddressWitnessPubKeyHash as a
+// Sha256 returns the witness program of the AddressWitnessPubKeyHash as a
// byte array.
func (a *AddressWitnessScriptHash) Sha256() *[32]byte {
return &a.witnessProgram
var seed [32]byte
sha3pool.Sum256(seed[:], make([]byte, 32))
- genesisBlock := &legacy.Block{
+ block := &legacy.Block{
BlockHeader: legacy.BlockHeader{
Version: 1,
Height: 0,
+ Nonce: 1267808,
Seed: bc.NewHash(seed),
TimestampMS: 1511318565142,
BlockCommitment: legacy.BlockCommitment{
Transactions: []*legacy.Tx{genesisCoinbaseTx},
}
- for i := uint64(0); i <= 10000000000000; i++ {
- genesisBlock.Nonce = i
- hash := genesisBlock.Hash()
-
- if difficulty.CheckProofOfWork(&hash, genesisBlock.Bits) {
+ for {
+ hash := block.Hash()
+ if difficulty.CheckProofOfWork(&hash, block.Bits) {
+ log.Info(block.Nonce)
break
}
+ block.Nonce++
}
-
- log.Infof("genesisBlock:%v", genesisBlock)
- return genesisBlock
+ return block
}
)
// test genesis
-func TestGenesis(t *testing.T) {
+func TestGenerateGenesisTx(t *testing.T) {
if tx := GenerateGenesisTx(); tx == nil {
t.Errorf("Generate genesis tx failed")
}
}
+
+func TestGenerateGenesisBlock(t *testing.T) {
+ if block := GenerateGenesisBlock(); block == nil {
+ t.Errorf("Generate genesis block failed")
+ }
+}
t.Fatal(err)
}
- controlProg, err := accountManager.CreateP2PKH(nil, testAccount.Signer.ID, false, time.Now())
+ controlProg, err := accountManager.CreateAddress(nil, testAccount.Signer.ID, false, time.Now())
if err != nil {
t.Fatal(err)
}
+ utxo := mockUTXO(controlProg)
+ tpl, tx, err := mockTx(utxo, testAccount)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := mockSign(tpl, hsm); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = validation.ValidateTx(legacy.MapTx(tx), mockBlock()); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestP2SH(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)
+ }
+
+ xpub1, err := hsm.XCreate("test_pub1", "password")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpub2, err := hsm.XCreate("test_pub2", "password")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testAccount, err := accountManager.Create(nil, []chainkd.XPub{xpub1.XPub, xpub2.XPub}, 2, "testAccount", nil, "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ controlProg, err := accountManager.CreateAddress(nil, testAccount.Signer.ID, false, time.Now())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ utxo := mockUTXO(controlProg)
+ tpl, tx, err := mockTx(utxo, testAccount)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := mockSign(tpl, hsm); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err = validation.ValidateTx(legacy.MapTx(tx), mockBlock()); 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
+}
+
+func mockUTXO(controlProg *account.CtrlProgram) *account.UTXO {
utxo := &account.UTXO{}
utxo.OutputID = bc.Hash{V0: 1}
utxo.SourceID = bc.Hash{V0: 2}
utxo.AccountID = controlProg.AccountID
utxo.Address = controlProg.Address
utxo.ControlProgramIndex = controlProg.KeyIndex
+ return utxo
+}
+
+func mockTx(utxo *account.UTXO, testAccount *account.Account) (*txbuilder.Template, *legacy.TxData, error) {
txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo, nil)
if err != nil {
- t.Fatal(err)
+ return nil, nil, 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)
- }
+ return b.Build()
+}
- err = txbuilder.Sign(nil, tpl, nil, "password", func(_ context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
+func mockSign(tpl *txbuilder.Template, hsm *pseudohsm.HSM) error {
+ return 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
+func mockBlock() *bc.Block {
+ return &bc.Block{
+ BlockHeader: &bc.BlockHeader{Height: 1},
}
- return chain, nil
}
return ErrBadValue
}
l := int64(len(vm.dataStack))
+ if n < 0 {
+ n = l
+ }
if n > l {
return ErrDataStackUnderflow
}
return builder.Build()
}
+// P2SHProgram generates the script for control with script hash
+func P2SHProgram(scriptHash []byte) ([]byte, error) {
+ builder := NewBuilder()
+ builder.AddOp(vm.OP_DUP)
+ builder.AddOp(vm.OP_SHA3)
+ builder.AddData(scriptHash)
+ builder.AddOp(vm.OP_EQUALVERIFY)
+ builder.AddInt64(-1)
+ builder.AddOp(vm.OP_SWAP)
+ builder.AddInt64(0)
+ builder.AddOp(vm.OP_CHECKPREDICATE)
+ return builder.Build()
+}
+
// P2SPMultiSigProgram generates the script for contorl transaction output
func P2SPMultiSigProgram(pubkeys []ed25519.PublicKey, nrequired int) ([]byte, error) {
builder := NewBuilder()