Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #146 - add functions for long transactions #160

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ script:
- make test-integration-emulator
# Test that dependencies can be updated
- make clean
- make dep
# - make dep

after_script:
- kill -s KILL $(pgrep emulator)
Expand Down
258 changes: 196 additions & 62 deletions src/cli/transaction_sign.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cli

import (
"errors"
"fmt"
"os"
"runtime"

"github.com/skycoin/hardware-wallet-go/src/skywallet/wire"

"github.com/gogo/protobuf/proto"

gcli "github.com/urfave/cli"
Expand Down Expand Up @@ -59,6 +62,10 @@ func transactionSignCmd() gcli.Command {
coins := c.Int64Slice("coin")
hours := c.Int64Slice("hour")
addressIndex := c.IntSlice("addressIndex")
coinName := "Skycoin"
version := 1
lockTime := 0
txHash := "dkdji9e2oidhash"

device := skyWallet.NewDevice(skyWallet.DeviceTypeFromString(c.String("deviceType")))
if device == nil {
Expand All @@ -83,82 +90,209 @@ func transactionSignCmd() gcli.Command {
return
}

var transactionInputs []*messages.SkycoinTransactionInput
var transactionOutputs []*messages.SkycoinTransactionOutput
for i, input := range inputs {
var transactionInput messages.SkycoinTransactionInput
transactionInput.HashIn = proto.String(input)
transactionInput.Index = proto.Uint32(uint32(inputIndex[i]))
transactionInputs = append(transactionInputs, &transactionInput)
}
for i, output := range outputs {
var transactionOutput messages.SkycoinTransactionOutput
transactionOutput.Address = proto.String(output)
transactionOutput.Coin = proto.Uint64(uint64(coins[i]))
transactionOutput.Hour = proto.Uint64(uint64(hours[i]))
if i < len(addressIndex) {
transactionOutput.AddressIndex = proto.Uint32(uint32(addressIndex[i]))
}
transactionOutputs = append(transactionOutputs, &transactionOutput)
}
if len(inputs) > 7 || len(outputs) > 7 {
state := 0
index := 0

msg, err := device.TransactionSign(transactionInputs, transactionOutputs)
if err != nil {
log.Error(err)
return
}
msg, err := device.SignTx(len(outputs), len(inputs), coinName, version, lockTime, txHash)

for {
switch msg.Kind {
case uint16(messages.MessageType_MessageType_ResponseTransactionSign):
signatures, err := skyWallet.DecodeResponseTransactionSign(msg)
for {
if err != nil {
log.Error(err)
return
}
fmt.Println(signatures)
return
case uint16(messages.MessageType_MessageType_Success):
fmt.Println("Should end with ResponseTransactionSign request")
return
case uint16(messages.MessageType_MessageType_ButtonRequest):
msg, err = device.ButtonAck()
if err != nil {
log.Error(err)
switch msg.Kind {
case uint16(messages.MessageType_MessageType_TxRequest):
txRequest := &messages.TxRequest{}
err = proto.Unmarshal(msg.Data, txRequest)
if err != nil {
log.Error(err)
return
}
switch *txRequest.RequestType {
case messages.TxRequest_TXINPUT:
if state == 0 { // Sending Inputs for InnerHash
msg, err = sendInputs(device, &inputs, &inputIndex, version, lockTime, &index, &state)
} else if state == 2 { // Sending Inputs for Signatures
err = printSignatures(&msg)
if err != nil {
log.Error(err)
return
}
msg, err = sendInputs(device, &inputs, &inputIndex, version, lockTime, &index, &state)
} else {
log.Error("protocol error: unexpected TxRequest type")
return
}
case messages.TxRequest_TXOUTPUT:
if state == 1 { // Sending Outputs for InnerHash
msg, err = sendOutputs(device, &outputs, &addressIndex, &coins, &hours, version, lockTime, &index, &state)
} else {
log.Error("protocol error: unexpected TxRequest type")
return
}
case messages.TxRequest_TXFINISHED:
if state == 3 {
err = printSignatures(&msg)
if err != nil {
log.Error(err)
}
return
}
log.Error("protocol error: unexpected TXFINISHED message")
return
}
case uint16(messages.MessageType_MessageType_Failure):
failMsg, err := skyWallet.DecodeFailMsg(msg)
if err != nil {
log.Error(err)
return
}
fmt.Printf("Failed with message: %s\n", failMsg)
return
}
case uint16(messages.MessageType_MessageType_PassphraseRequest):
var passphrase string
fmt.Printf("Input passphrase: ")
fmt.Scanln(&passphrase)
msg, err = device.PassphraseAck(passphrase)
if err != nil {
log.Error(err)
return
}
case uint16(messages.MessageType_MessageType_PinMatrixRequest):
var pinEnc string
fmt.Printf("PinMatrixRequest response: ")
fmt.Scanln(&pinEnc)
msg, err = device.PinMatrixAck(pinEnc)
if err != nil {
log.Error(err)
case uint16(messages.MessageType_MessageType_ButtonRequest):
msg, err = device.ButtonAck()
default:
log.Error("unexpected response message type from hardware wallet.")
return
}
case uint16(messages.MessageType_MessageType_Failure):
failMsg, err := skyWallet.DecodeFailMsg(msg)
if err != nil {
log.Error(err)
return
}
} else {
var transactionInputs []*messages.SkycoinTransactionInput
var transactionOutputs []*messages.SkycoinTransactionOutput
for i, input := range inputs {
var transactionInput messages.SkycoinTransactionInput
transactionInput.HashIn = proto.String(input)
transactionInput.Index = proto.Uint32(uint32(inputIndex[i]))
transactionInputs = append(transactionInputs, &transactionInput)
}
for i, output := range outputs {
var transactionOutput messages.SkycoinTransactionOutput
transactionOutput.Address = proto.String(output)
transactionOutput.Coin = proto.Uint64(uint64(coins[i]))
transactionOutput.Hour = proto.Uint64(uint64(hours[i]))
if i < len(addressIndex) {
transactionOutput.AddressIndex = proto.Uint32(uint32(addressIndex[i]))
}
transactionOutputs = append(transactionOutputs, &transactionOutput)
}

fmt.Printf("Failed with message: %s\n", failMsg)
return
default:
log.Errorf("received unexpected message type: %s", messages.MessageType(msg.Kind))
msg, err := device.TransactionSign(transactionInputs, transactionOutputs)
if err != nil {
log.Error(err)
return
}

for {
switch msg.Kind {
case uint16(messages.MessageType_MessageType_ResponseTransactionSign):
signatures, err := skyWallet.DecodeResponseTransactionSign(msg)
if err != nil {
log.Error(err)
return
}
fmt.Println(signatures)
return
case uint16(messages.MessageType_MessageType_Success):
fmt.Println("Should end with ResponseTransactionSign request")
return
case uint16(messages.MessageType_MessageType_ButtonRequest):
msg, err = device.ButtonAck()
if err != nil {
log.Error(err)
return
}
case uint16(messages.MessageType_MessageType_PassphraseRequest):
var passphrase string
fmt.Printf("Input passphrase: ")
fmt.Scanln(&passphrase)
msg, err = device.PassphraseAck(passphrase)
if err != nil {
log.Error(err)
return
}
case uint16(messages.MessageType_MessageType_PinMatrixRequest):
var pinEnc string
fmt.Printf("PinMatrixRequest response: ")
fmt.Scanln(&pinEnc)
msg, err = device.PinMatrixAck(pinEnc)
if err != nil {
log.Error(err)
return
}
case uint16(messages.MessageType_MessageType_Failure):
failMsg, err := skyWallet.DecodeFailMsg(msg)
if err != nil {
log.Error(err)
return
}

fmt.Printf("Failed with message: %s\n", failMsg)
return
default:
log.Errorf("received unexpected message type: %s", messages.MessageType(msg.Kind))
return
}
}
}
},
}
}

func sendInputs(device *skyWallet.Device, inputs *[]string, inputIndex *[]int, version int, lockTime int, index *int, state *int) (wire.Message, error) {
var txInputs []*messages.TxAck_TransactionType_TxInputType
startIndex := *index
for i, input := range (*inputs)[*index:] {
if len(txInputs) == 7 {
return device.TxAck(txInputs, []*messages.TxAck_TransactionType_TxOutputType{}, version, lockTime)
}
var txInput messages.TxAck_TransactionType_TxInputType
txInput.AddressN = []uint32{*proto.Uint32(uint32((*inputIndex)[startIndex+i]))}
txInput.HashIn = proto.String(input)
txInputs = append(txInputs, &txInput)
*index++
}
if len(txInputs) != 0 {
*index = 0
*state++
return device.TxAck(txInputs, nil, version, lockTime)
}
return wire.Message{}, errors.New("empty inputs")
}

func sendOutputs(device *skyWallet.Device, outputs *[]string, addressIndex *[]int, coins *[]int64, hours *[]int64, version int, lockTime int, index *int, state *int) (wire.Message, error) {
var txOutputs []*messages.TxAck_TransactionType_TxOutputType
startIndex := *index
for i, output := range (*outputs)[*index:] {
if len(txOutputs) == 7 {
return device.TxAck(nil, txOutputs, version, lockTime)
}
var txOutput messages.TxAck_TransactionType_TxOutputType
txOutput.Address = proto.String(output)
if i < len(*addressIndex) {
txOutput.AddressN = []uint32{uint32((*addressIndex)[startIndex+i])}
}
txOutput.Coins = proto.Uint64(uint64((*coins)[startIndex+i]))
txOutput.Hours = proto.Uint64(uint64((*hours)[startIndex+i]))
txOutputs = append(txOutputs, &txOutput)
*index++
}
if len(txOutputs) != 0 {
*index = 0
*state++
return device.TxAck(nil, txOutputs, version, lockTime)
}
return wire.Message{}, errors.New("empty outputs")
}

func printSignatures(msg *wire.Message) error {
txRequest := &messages.TxRequest{}
err := proto.Unmarshal(msg.Data, txRequest)
if err != nil {
return err
}
for _, sign := range txRequest.SignResult {
fmt.Println(*sign.Signature)
}
return nil
}
39 changes: 39 additions & 0 deletions src/skywallet/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,45 @@ func MessageTransactionSign(inputs []*messages.SkycoinTransactionInput, outputs
return chunks, nil
}

// MessageSignTx prepare MessageSignTx request
func MessageSignTx(outputsCount int, inputsCount int, coinName string, version int, lockTime int, txHash string) ([][64]byte, error) {
signTxMessage := &messages.SignTx{
OutputsCount: proto.Uint32(uint32(outputsCount)),
InputsCount: proto.Uint32(uint32(inputsCount)),
CoinName: proto.String(coinName),
Version: proto.Uint32(uint32(version)),
LockTime: proto.Uint32(uint32(lockTime)),
TxHash: proto.String(txHash),
}
data, err := proto.Marshal(signTxMessage)
if err != nil {
return [][64]byte{}, err
}

chunks := makeSkyWalletMessage(data, messages.MessageType_MessageType_SignTx)
return chunks, nil
}

// MessageTxAck prepare MessageTxAck request
func MessageTxAck(inputs []*messages.TxAck_TransactionType_TxInputType, outputs []*messages.TxAck_TransactionType_TxOutputType, version int, lockTime int) ([][64]byte, error) {
tx := &messages.TxAck_TransactionType{
Inputs: inputs,
Outputs: outputs,
LockTime: proto.Uint32(uint32(lockTime)),
Version: proto.Uint32(uint32(version)),
}
txAckMessage := &messages.TxAck{
Tx: tx,
}
data, err := proto.Marshal(txAckMessage)

if err != nil {
return [][64]byte{}, err
}
chunks := makeSkyWalletMessage(data, messages.MessageType_MessageType_TxAck)
return chunks, nil
}

// MessageWipe prepare MessageWipe request
func MessageWipe() ([][64]byte, error) {
wipeDevice := &messages.WipeDevice{}
Expand Down
30 changes: 30 additions & 0 deletions src/skywallet/skywallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,36 @@ func (d *Device) TransactionSign(inputs []*messages.SkycoinTransactionInput, out
return d.Driver.SendToDevice(d.dev, transactionSignChunks)
}

// SignTx Ask the device to sign a long transaction using the given information.
func (d *Device) SignTx(outputsCount int, inputsCount int, coinName string, version int, lockTime int, txHash string) (wire.Message, error) {
if err := d.Connect(); err != nil {
return wire.Message{}, err
}
defer d.Disconnect()

signTxChunks, err := MessageSignTx(outputsCount, inputsCount, coinName, version, lockTime, txHash)

if err != nil {
return wire.Message{}, err
}

return d.Driver.SendToDevice(d.dev, signTxChunks)
}

// TxAck Ask the device to continue a long transaction using the given information.
func (d *Device) TxAck(inputs []*messages.TxAck_TransactionType_TxInputType, outputs []*messages.TxAck_TransactionType_TxOutputType, version int, lockTime int) (wire.Message, error) {
if err := d.Connect(); err != nil {
return wire.Message{}, err
}
defer d.Disconnect()
txAckChunks, err := MessageTxAck(inputs, outputs, version, lockTime)
if err != nil {
return wire.Message{}, err
}

return d.Driver.SendToDevice(d.dev, txAckChunks)
}

// Wipe wipes out device configuration
func (d *Device) Wipe() (wire.Message, error) {
if err := d.Connect(); err != nil {
Expand Down