diff --git a/.gitignore b/.gitignore
index b3d9d3bf..159d95f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,5 +83,6 @@ android
# Temporary build files
rcc_cgo_*.go
+darwin
diff --git a/.travis.yml b/.travis.yml
index 6aaea2df..f4f4ee10 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,89 +1,89 @@
sudo: required
language: go
go:
-- 1.12.6
+ - 1.12.6
services:
-- docker
+ - docker
go_import_path: github.com/fibercrypto/fibercryptowallet
matrix:
include:
- - stage: RPI
- if: branch = release*$
- install:
- - DEFAULT_ARCH=rpi1 make install-docker-deps
- - DEFAULT_ARCH=rpi2 make install-docker-deps
- - DEFAULT_ARCH=rpi3 make install-docker-deps
- - make install-deps-no-envs
- - make install-coveralls
- script:
- - make clean
- - DEFAULT_TARGET=rpi1 make build-docker
- - DEFAULT_TARGET=rpi2 make build-docker
- - DEFAULT_TARGET=rpi3 make build-docker
- - make lint
- - make test
- - stage: Android
- if: branch = release*$
- install:
- - DEFAULT_ARCH=android make install-docker-deps
- - make install-deps-no-envs
- - make install-coveralls
- script:
- - make clean
- - DEFAULT_TARGET=android make build-docker
- - DEFAULT_TARGET=android-emulator make build-docker
- - make lint
- - make test
- - make test-cover-travis
- - stage: Linux
- install:
- - make install-docker-deps
- - make install-deps-no-envs
- - make install-linters
- - make install-coveralls
- script:
- - make clean
- - DEFAULT_TARGET=linux make build-docker
- - make test
- - make lint
- - make test-cover-travis
- - stage: Windows
- os: windows
- install:
- - choco install make -y
- - travis_wait make install-deps-Windows
- - make install-linters
- - make install-coveralls
- script:
- - make build
- - make test
- - make build-icon
- - make lint
- - make test-cover-travis
- - stage: MacOS
- os: osx
- osx_image: xcode10.2
- install:
- - make install-deps
- - make install-coveralls
- script:
- - make clean
- - make build
- - make test
- - make build-icon
- - make lint
+ - stage: RPI
+ if: branch = release*$
+ install:
+ - DEFAULT_ARCH=rpi1 make install-docker-deps
+ - DEFAULT_ARCH=rpi2 make install-docker-deps
+ - DEFAULT_ARCH=rpi3 make install-docker-deps
+ - make install-deps-no-envs
+ - make install-coveralls
+ script:
+ - make clean
+ - DEFAULT_TARGET=rpi1 make build-docker
+ - DEFAULT_TARGET=rpi2 make build-docker
+ - DEFAULT_TARGET=rpi3 make build-docker
+ - make lint
+ - make test
+ - stage: Android
+ if: branch = release*$
+ install:
+ - DEFAULT_ARCH=android make install-docker-deps
+ - make install-deps-no-envs
+ - make install-coveralls
+ script:
+ - make clean
+ - DEFAULT_TARGET=android make build-docker
+ - DEFAULT_TARGET=android-emulator make build-docker
+ - make lint
+ - make test
- make test-cover-travis
+ - stage: Linux
+ install:
+ - make install-docker-deps
+ - make install-deps-no-envs
+ - make install-linters
+ - make install-coveralls
+ script:
+ - make clean
+ - DEFAULT_TARGET=linux make build-docker
+ - make test
+ - make lint
+ - make test-cover-travis
+ - stage: Windows
+ os: windows
+ install:
+ - choco install make -y
+ - travis_wait make install-deps-Windows
+ - make install-linters
+ - make install-coveralls
+ script:
+ - make build
+ - make test
+ - make build-icon
+ - make lint
+ - make test-cover-travis
+ - stage: MacOS
+ os: osx
+ osx_image: xcode10.2
+ install:
+ - make install-deps
+ - make install-coveralls
+ script:
+ - make clean
+ - make build
+ - make test
+ - make build-icon
+ - make lint
+ - make test-cover-travis
notifications:
email: false
webhooks: https://fathomless-fjord-24024.herokuapp.com/notify
before_deploy:
- - export VERSION="$(git describe --tags --exact-match HEAD 2> /dev/null)"
- - export ARCH="$(uname -m)"
- - export OS="$(uname -s)"
- - make prepare-release
- - make build
- - ( cd deploy && tar czf ../fibercryptowallet-${VERSION}-${OS}-${ARCH}.tar.gz ./* )
- - pwd && ls -l .
+ - export VERSION="$(git describe --tags --exact-match HEAD 2> /dev/null)"
+ - export ARCH="$(uname -m)"
+ - export OS="$(uname -s)"
+ - make prepare-release
+ - make build
+ - ( cd deploy && tar czf ../fibercryptowallet-${VERSION}-${OS}-${ARCH}.tar.gz ./* )
+ - pwd && ls -l .
deploy:
provider: releases
api_key:
diff --git a/.travis/install-golangci-lint.sh b/.travis/install-golangci-lint.sh
index ee94ad03..e10f1253 100755
--- a/.travis/install-golangci-lint.sh
+++ b/.travis/install-golangci-lint.sh
@@ -384,4 +384,4 @@ CHECKSUM=${PROJECT_NAME}-${VERSION}-checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
-execute
\ No newline at end of file
+execute
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e37d8670..180d7b91 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
+- Spend in single transaction coins owned by multiple wallets (same altcoin plugin)
- Added logger for the app and proper error handling
- Built-in support for SkyWallet hardware wallet as signer for Skycoin transactions
- Built-in support iand GUI for applying operations upon SkyWallet hardware devices
diff --git a/Makefile b/Makefile
index d74259cb..2a1617db 100644
--- a/Makefile
+++ b/Makefile
@@ -232,7 +232,6 @@ clean: clean-test clean-build ## Remove temporary files
gen-mocks: ## Generate mocks for interface types
mockery -all -output src/coin/mocks -outpkg mocks -dir src/core
- find src/coin/mocks/ -name '*.go' -type f -print0
test-sky: ## Run Skycoin plugin test suite
go test -cover -timeout 30s github.com/fibercrypto/fibercryptowallet/src/coin/skycoin
diff --git a/README.md b/README.md
index ed6ca019..f8fd564d 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ Project files are organized as follows:
- `./src/coin/skycoin/models` : Skycoin implementation of golang core interfaces.
- `./src/coin/skycoin/blockchain` : Skycoin blockchain API.
- `./src/coin/skycoin/sign` : Skycoin sign API.
-- `vendor` : Project dependencies managed by `dep`.
+- `./vendor` : Project dependencies managed by `dep`.
### Architecture
diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf
index f399a31f..341c2f81 100644
--- a/qtquickcontrols2.conf
+++ b/qtquickcontrols2.conf
@@ -7,4 +7,4 @@ Style = Material
[Material]
Accent = Blue
-Variant = Dense
\ No newline at end of file
+Variant = Dense
diff --git a/resources/fonts/code-new-roman/license.txt b/resources/fonts/code-new-roman/license.txt
index 14c043d6..e47e0f90 100644
--- a/resources/fonts/code-new-roman/license.txt
+++ b/resources/fonts/code-new-roman/license.txt
@@ -38,4 +38,4 @@ TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
\ No newline at end of file
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/resources/images/icons/backspace.svg b/resources/images/icons/backspace.svg
index 27e5b8aa..af1122b3 100644
--- a/resources/images/icons/backspace.svg
+++ b/resources/images/icons/backspace.svg
@@ -37,4 +37,4 @@
-
\ No newline at end of file
+
diff --git a/resources/images/icons/backup.svg b/resources/images/icons/backup.svg
index e5c4951e..c573b2e8 100644
--- a/resources/images/icons/backup.svg
+++ b/resources/images/icons/backup.svg
@@ -86,4 +86,4 @@
-
\ No newline at end of file
+
diff --git a/resources/images/icons/license.svg b/resources/images/icons/license.svg
index 7ed4b446..0e086e63 100755
--- a/resources/images/icons/license.svg
+++ b/resources/images/icons/license.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/resources/images/icons/moon.svg b/resources/images/icons/moon.svg
index a22bc972..bfe4d5c8 100644
--- a/resources/images/icons/moon.svg
+++ b/resources/images/icons/moon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/resources/images/icons/qr.svg b/resources/images/icons/qr.svg
index ebb8d1ec..8b5c0b3a 100644
--- a/resources/images/icons/qr.svg
+++ b/resources/images/icons/qr.svg
@@ -28,4 +28,4 @@
-
\ No newline at end of file
+
diff --git a/resources/images/icons/sun.svg b/resources/images/icons/sun.svg
index f31d7354..adae7a68 100644
--- a/resources/images/icons/sun.svg
+++ b/resources/images/icons/sun.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/resources/platform/darwin/Info.plist b/resources/platform/darwin/Info.plist
index 49ae4dff..e0a1242b 100644
--- a/resources/platform/darwin/Info.plist
+++ b/resources/platform/darwin/Info.plist
@@ -9,7 +9,7 @@
CFBundleIconFile
appIcon.icns
CFBundleIdentifier
- com.simelo.FiberCryptoWallet
+ net.fibercrypto.wallet
CFBundleName
FiberCryptoWallet
CFBundlePackageType
diff --git a/resources/translations/update_translation_files.sh b/resources/translations/update_translation_files.sh
index e10e3a58..302a1128 100644
--- a/resources/translations/update_translation_files.sh
+++ b/resources/translations/update_translation_files.sh
@@ -4,4 +4,4 @@ lupdate ../../src/ui/*.qml ../../src/ui/Controls/*.qml ../../src/ui/Delegates/*.
lupdate ../../src/ui/*.qml ../../src/ui/Controls/*.qml ../../src/ui/Delegates/*.qml ../../src/ui/Dialogs/*.qml -source-language en_us -target-language es_es -ts FiberCryptoWallet_es.ts
lupdate ../../src/ui/*.qml ../../src/ui/Controls/*.qml ../../src/ui/Delegates/*.qml ../../src/ui/Dialogs/*.qml -source-language en_us -target-language fr_fr -ts FiberCryptoWallet_fr.ts
# echo '\033[1;37mSearching for strings to translate in Go sources...\033[0m'
-# find ../../src -path *.go -type f -printf "%p " | lupdate -tr-function-alias translate+=Translate -source-language en_us -target-language en_us -ts FiberCryptoWallet_en.ts
\ No newline at end of file
+# find ../../src -path *.go -type f -printf "%p " | lupdate -tr-function-alias translate+=Translate -source-language en_us -target-language en_us -ts FiberCryptoWallet_en.ts
diff --git a/src/coin/mocks/AltcoinPlugin.go b/src/coin/mocks/AltcoinPlugin.go
index 125b2b4d..3c99d3d3 100644
--- a/src/coin/mocks/AltcoinPlugin.go
+++ b/src/coin/mocks/AltcoinPlugin.go
@@ -93,6 +93,52 @@ func (_m *AltcoinPlugin) LoadPEX(netType string) (core.PEX, error) {
return r0, r1
}
+// LoadSignService provides a mock function with given fields:
+func (_m *AltcoinPlugin) LoadSignService() (core.BlockchainSignService, error) {
+ ret := _m.Called()
+
+ var r0 core.BlockchainSignService
+ if rf, ok := ret.Get(0).(func() core.BlockchainSignService); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.BlockchainSignService)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func() error); ok {
+ r1 = rf()
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// LoadTransactionAPI provides a mock function with given fields: netType
+func (_m *AltcoinPlugin) LoadTransactionAPI(netType string) (core.BlockchainTransactionAPI, error) {
+ ret := _m.Called(netType)
+
+ var r0 core.BlockchainTransactionAPI
+ if rf, ok := ret.Get(0).(func(string) core.BlockchainTransactionAPI); ok {
+ r0 = rf(netType)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.BlockchainTransactionAPI)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(netType)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
// LoadWalletEnvs provides a mock function with given fields:
func (_m *AltcoinPlugin) LoadWalletEnvs() []core.WalletEnv {
ret := _m.Called()
diff --git a/src/coin/mocks/BlockchainSignService.go b/src/coin/mocks/BlockchainSignService.go
new file mode 100644
index 00000000..2fda5274
--- /dev/null
+++ b/src/coin/mocks/BlockchainSignService.go
@@ -0,0 +1,34 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import core "github.com/fibercrypto/fibercryptowallet/src/core"
+import mock "github.com/stretchr/testify/mock"
+
+// BlockchainSignService is an autogenerated mock type for the BlockchainSignService type
+type BlockchainSignService struct {
+ mock.Mock
+}
+
+// Sign provides a mock function with given fields: txn, signSpec, pwd
+func (_m *BlockchainSignService) Sign(txn core.Transaction, signSpec []core.InputSignDescriptor, pwd core.PasswordReader) (core.Transaction, error) {
+ ret := _m.Called(txn, signSpec, pwd)
+
+ var r0 core.Transaction
+ if rf, ok := ret.Get(0).(func(core.Transaction, []core.InputSignDescriptor, core.PasswordReader) core.Transaction); ok {
+ r0 = rf(txn, signSpec, pwd)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Transaction)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(core.Transaction, []core.InputSignDescriptor, core.PasswordReader) error); ok {
+ r1 = rf(txn, signSpec, pwd)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
diff --git a/src/coin/mocks/BlockchainTransactionAPI.go b/src/coin/mocks/BlockchainTransactionAPI.go
new file mode 100644
index 00000000..af133a91
--- /dev/null
+++ b/src/coin/mocks/BlockchainTransactionAPI.go
@@ -0,0 +1,57 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import core "github.com/fibercrypto/fibercryptowallet/src/core"
+import mock "github.com/stretchr/testify/mock"
+
+// BlockchainTransactionAPI is an autogenerated mock type for the BlockchainTransactionAPI type
+type BlockchainTransactionAPI struct {
+ mock.Mock
+}
+
+// SendFromAddress provides a mock function with given fields: from, to, change, options
+func (_m *BlockchainTransactionAPI) SendFromAddress(from []core.WalletAddress, to []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
+ ret := _m.Called(from, to, change, options)
+
+ var r0 core.Transaction
+ if rf, ok := ret.Get(0).(func([]core.WalletAddress, []core.TransactionOutput, core.Address, core.KeyValueStore) core.Transaction); ok {
+ r0 = rf(from, to, change, options)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Transaction)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func([]core.WalletAddress, []core.TransactionOutput, core.Address, core.KeyValueStore) error); ok {
+ r1 = rf(from, to, change, options)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// Spend provides a mock function with given fields: unspent, new, change, options
+func (_m *BlockchainTransactionAPI) Spend(unspent []core.WalletOutput, new []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
+ ret := _m.Called(unspent, new, change, options)
+
+ var r0 core.Transaction
+ if rf, ok := ret.Get(0).(func([]core.WalletOutput, []core.TransactionOutput, core.Address, core.KeyValueStore) core.Transaction); ok {
+ r0 = rf(unspent, new, change, options)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Transaction)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func([]core.WalletOutput, []core.TransactionOutput, core.Address, core.KeyValueStore) error); ok {
+ r1 = rf(unspent, new, change, options)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
diff --git a/src/coin/mocks/KeyValueStorage.go b/src/coin/mocks/KeyValueStore.go
similarity index 67%
rename from src/coin/mocks/KeyValueStorage.go
rename to src/coin/mocks/KeyValueStore.go
index 277b6e1a..4dde59f7 100644
--- a/src/coin/mocks/KeyValueStorage.go
+++ b/src/coin/mocks/KeyValueStore.go
@@ -4,13 +4,13 @@ package mocks
import mock "github.com/stretchr/testify/mock"
-// KeyValueStorage is an autogenerated mock type for the KeyValueStorage type
-type KeyValueStorage struct {
+// KeyValueStore is an autogenerated mock type for the KeyValueStore type
+type KeyValueStore struct {
mock.Mock
}
// GetValue provides a mock function with given fields: key
-func (_m *KeyValueStorage) GetValue(key string) interface{} {
+func (_m *KeyValueStore) GetValue(key string) interface{} {
ret := _m.Called(key)
var r0 interface{}
@@ -26,6 +26,6 @@ func (_m *KeyValueStorage) GetValue(key string) interface{} {
}
// SetValue provides a mock function with given fields: key, value
-func (_m *KeyValueStorage) SetValue(key string, value interface{}) {
+func (_m *KeyValueStore) SetValue(key string, value interface{}) {
_m.Called(key, value)
}
diff --git a/src/coin/mocks/Wallet.go b/src/coin/mocks/Wallet.go
index 30f5660c..d35a0180 100644
--- a/src/coin/mocks/Wallet.go
+++ b/src/coin/mocks/Wallet.go
@@ -94,11 +94,11 @@ func (_m *Wallet) GetLoadedAddresses() (core.AddressIterator, error) {
}
// SendFromAddress provides a mock function with given fields: from, to, change, options
-func (_m *Wallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+func (_m *Wallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
ret := _m.Called(from, to, change, options)
var r0 core.Transaction
- if rf, ok := ret.Get(0).(func([]core.Address, []core.TransactionOutput, core.Address, core.KeyValueStorage) core.Transaction); ok {
+ if rf, ok := ret.Get(0).(func([]core.Address, []core.TransactionOutput, core.Address, core.KeyValueStore) core.Transaction); ok {
r0 = rf(from, to, change, options)
} else {
if ret.Get(0) != nil {
@@ -107,7 +107,7 @@ func (_m *Wallet) SendFromAddress(from []core.Address, to []core.TransactionOutp
}
var r1 error
- if rf, ok := ret.Get(1).(func([]core.Address, []core.TransactionOutput, core.Address, core.KeyValueStorage) error); ok {
+ if rf, ok := ret.Get(1).(func([]core.Address, []core.TransactionOutput, core.Address, core.KeyValueStore) error); ok {
r1 = rf(from, to, change, options)
} else {
r1 = ret.Error(1)
@@ -145,11 +145,11 @@ func (_m *Wallet) Sign(txn core.Transaction, signer core.TxnSigner, pwd core.Pas
}
// Spend provides a mock function with given fields: unspent, new, change, options
-func (_m *Wallet) Spend(unspent []core.TransactionOutput, new []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+func (_m *Wallet) Spend(unspent []core.TransactionOutput, new []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
ret := _m.Called(unspent, new, change, options)
var r0 core.Transaction
- if rf, ok := ret.Get(0).(func([]core.TransactionOutput, []core.TransactionOutput, core.Address, core.KeyValueStorage) core.Transaction); ok {
+ if rf, ok := ret.Get(0).(func([]core.TransactionOutput, []core.TransactionOutput, core.Address, core.KeyValueStore) core.Transaction); ok {
r0 = rf(unspent, new, change, options)
} else {
if ret.Get(0) != nil {
@@ -158,7 +158,7 @@ func (_m *Wallet) Spend(unspent []core.TransactionOutput, new []core.Transaction
}
var r1 error
- if rf, ok := ret.Get(1).(func([]core.TransactionOutput, []core.TransactionOutput, core.Address, core.KeyValueStorage) error); ok {
+ if rf, ok := ret.Get(1).(func([]core.TransactionOutput, []core.TransactionOutput, core.Address, core.KeyValueStore) error); ok {
r1 = rf(unspent, new, change, options)
} else {
r1 = ret.Error(1)
@@ -168,11 +168,11 @@ func (_m *Wallet) Spend(unspent []core.TransactionOutput, new []core.Transaction
}
// Transfer provides a mock function with given fields: to, options
-func (_m *Wallet) Transfer(to core.TransactionOutput, options core.KeyValueStorage) (core.Transaction, error) {
+func (_m *Wallet) Transfer(to core.TransactionOutput, options core.KeyValueStore) (core.Transaction, error) {
ret := _m.Called(to, options)
var r0 core.Transaction
- if rf, ok := ret.Get(0).(func(core.TransactionOutput, core.KeyValueStorage) core.Transaction); ok {
+ if rf, ok := ret.Get(0).(func(core.TransactionOutput, core.KeyValueStore) core.Transaction); ok {
r0 = rf(to, options)
} else {
if ret.Get(0) != nil {
@@ -181,7 +181,7 @@ func (_m *Wallet) Transfer(to core.TransactionOutput, options core.KeyValueStora
}
var r1 error
- if rf, ok := ret.Get(1).(func(core.TransactionOutput, core.KeyValueStorage) error); ok {
+ if rf, ok := ret.Get(1).(func(core.TransactionOutput, core.KeyValueStore) error); ok {
r1 = rf(to, options)
} else {
r1 = ret.Error(1)
diff --git a/src/coin/mocks/WalletAddress.go b/src/coin/mocks/WalletAddress.go
new file mode 100644
index 00000000..35a4fd3e
--- /dev/null
+++ b/src/coin/mocks/WalletAddress.go
@@ -0,0 +1,43 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import core "github.com/fibercrypto/fibercryptowallet/src/core"
+import mock "github.com/stretchr/testify/mock"
+
+// WalletAddress is an autogenerated mock type for the WalletAddress type
+type WalletAddress struct {
+ mock.Mock
+}
+
+// GetAddress provides a mock function with given fields:
+func (_m *WalletAddress) GetAddress() core.Address {
+ ret := _m.Called()
+
+ var r0 core.Address
+ if rf, ok := ret.Get(0).(func() core.Address); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Address)
+ }
+ }
+
+ return r0
+}
+
+// GetWallet provides a mock function with given fields:
+func (_m *WalletAddress) GetWallet() core.Wallet {
+ ret := _m.Called()
+
+ var r0 core.Wallet
+ if rf, ok := ret.Get(0).(func() core.Wallet); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Wallet)
+ }
+ }
+
+ return r0
+}
diff --git a/src/coin/mocks/WalletOutput.go b/src/coin/mocks/WalletOutput.go
new file mode 100644
index 00000000..58ac6516
--- /dev/null
+++ b/src/coin/mocks/WalletOutput.go
@@ -0,0 +1,43 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import core "github.com/fibercrypto/fibercryptowallet/src/core"
+import mock "github.com/stretchr/testify/mock"
+
+// WalletOutput is an autogenerated mock type for the WalletOutput type
+type WalletOutput struct {
+ mock.Mock
+}
+
+// GetOutput provides a mock function with given fields:
+func (_m *WalletOutput) GetOutput() core.TransactionOutput {
+ ret := _m.Called()
+
+ var r0 core.TransactionOutput
+ if rf, ok := ret.Get(0).(func() core.TransactionOutput); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.TransactionOutput)
+ }
+ }
+
+ return r0
+}
+
+// GetWallet provides a mock function with given fields:
+func (_m *WalletOutput) GetWallet() core.Wallet {
+ ret := _m.Called()
+
+ var r0 core.Wallet
+ if rf, ok := ret.Get(0).(func() core.Wallet); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(core.Wallet)
+ }
+ }
+
+ return r0
+}
diff --git a/src/coin/skycoin/models/blockchain.go b/src/coin/skycoin/models/blockchain.go
index 6f897be3..5cc9e71e 100644
--- a/src/coin/skycoin/models/blockchain.go
+++ b/src/coin/skycoin/models/blockchain.go
@@ -84,17 +84,17 @@ type SkycoinBlockchainInfo struct {
NumberOfBlocks *readable.BlockchainProgress
}
-type SkycoinBlockchainStatus struct { //Implements BlockchainStatus interface
+type SkycoinBlockchain struct { //Implements BlockchainStatus interface
lastTimeStatusRequested uint64 //nolint structcheck TODO: Not used
lastTimeSupplyRequested uint64
CacheTime uint64
cachedStatus *SkycoinBlockchainInfo
}
-func NewSkycoinBlockchainStatus(invalidCacheTime uint64) *SkycoinBlockchainStatus {
- return &SkycoinBlockchainStatus{CacheTime: invalidCacheTime}
+func NewSkycoinBlockchain(invalidCacheTime uint64) *SkycoinBlockchain {
+ return &SkycoinBlockchain{CacheTime: invalidCacheTime}
}
-func (ss *SkycoinBlockchainStatus) GetCoinValue(coinvalue core.CoinValueMetric, ticker string) (uint64, error) {
+func (ss *SkycoinBlockchain) GetCoinValue(coinvalue core.CoinValueMetric, ticker string) (uint64, error) {
logBlockchain.Info("Getting Coin value")
elapsed := uint64(time.Now().UTC().UnixNano()) - ss.lastTimeSupplyRequested
if elapsed > ss.CacheTime || ss.cachedStatus == nil {
@@ -122,7 +122,7 @@ func (ss *SkycoinBlockchainStatus) GetCoinValue(coinvalue core.CoinValueMetric,
}
}
-func (ss *SkycoinBlockchainStatus) GetLastBlock() (core.Block, error) {
+func (ss *SkycoinBlockchain) GetLastBlock() (core.Block, error) {
logBlockchain.Info("Getting last block")
elapsed := uint64(time.Now().UTC().UnixNano()) - ss.lastTimeSupplyRequested
if elapsed > ss.CacheTime || ss.cachedStatus == nil {
@@ -136,7 +136,7 @@ func (ss *SkycoinBlockchainStatus) GetLastBlock() (core.Block, error) {
return ss.cachedStatus.LastBlockInfo, nil
}
-func (ss *SkycoinBlockchainStatus) GetNumberOfBlocks() (uint64, error) {
+func (ss *SkycoinBlockchain) GetNumberOfBlocks() (uint64, error) {
logBlockchain.Info("Getting number of blocks")
if ss.cachedStatus == nil {
if ss.cachedStatus == nil {
@@ -151,12 +151,12 @@ func (ss *SkycoinBlockchainStatus) GetNumberOfBlocks() (uint64, error) {
return ss.cachedStatus.NumberOfBlocks.Current, nil
}
-func (ss *SkycoinBlockchainStatus) SetCacheTime(time uint64) {
+func (ss *SkycoinBlockchain) SetCacheTime(time uint64) {
logBlockchain.Info("Setting cache time")
ss.CacheTime = time
}
-func (ss *SkycoinBlockchainStatus) requestSupplyInfo() error {
+func (ss *SkycoinBlockchain) requestSupplyInfo() error {
logBlockchain.Info("Requesting supply info")
c, err := NewSkycoinApiClient(PoolSection)
@@ -207,7 +207,7 @@ func (ss *SkycoinBlockchainStatus) requestSupplyInfo() error {
return nil
}
-func (ss *SkycoinBlockchainStatus) requestStatusInfo() error {
+func (ss *SkycoinBlockchain) requestStatusInfo() error {
logBlockchain.Info("Requesting status information")
c, err := NewSkycoinApiClient(PoolSection)
if err != nil {
@@ -238,3 +238,26 @@ func (ss *SkycoinBlockchainStatus) requestStatusInfo() error {
return nil
}
+
+// SendFromAddress instantiates a transaction to send funds from specific source addresses
+// to multiple destination addresses
+func (ss *SkycoinBlockchain) SendFromAddress(from []core.WalletAddress, to []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
+ logBlockchain.Info("Sending coins from addresses via blockchain API")
+ addresses := make([]core.Address, len(from))
+ for i, wa := range from {
+ addresses[i] = wa.GetAddress()
+ }
+ createTxnFunc := skyAPICreateTxn
+ return createTransaction(addresses, to, nil, change, options, createTxnFunc)
+}
+
+// Spend instantiates a transaction that spends specific outputs to send to multiple destination addresses
+func (ss *SkycoinBlockchain) Spend(unspent []core.WalletOutput, new []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
+ logBlockchain.Info("Spending coins from outputs via blockchain API")
+ uxouts := make([]core.TransactionOutput, len(unspent))
+ for i, wu := range unspent {
+ uxouts[i] = wu.GetOutput()
+ }
+ createTxnFunc := skyAPICreateTxn
+ return createTransaction(nil, new, uxouts, change, options, createTxnFunc)
+}
diff --git a/src/coin/skycoin/models/blockchain_test.go b/src/coin/skycoin/models/blockchain_test.go
index 86fe59b7..c03f5eef 100644
--- a/src/coin/skycoin/models/blockchain_test.go
+++ b/src/coin/skycoin/models/blockchain_test.go
@@ -22,7 +22,7 @@ func TestSkycoinBlockchainStatusGetCoinValue(t *testing.T) {
)
global_mock.On("BlockchainProgress").Return(&readable.BlockchainProgress{}, nil)
- block := &SkycoinBlockchainStatus{CacheTime: 20}
+ block := &SkycoinBlockchain{CacheTime: 20}
val, err := block.GetCoinValue(core.CoinCurrentSupply, Sky)
require.NoError(t, err)
require.Equal(t, val, uint64(200111111))
@@ -56,7 +56,7 @@ func TestSkycoinBlockchainStatusGetNumberOfBlocks(t *testing.T) {
nil,
)
- block := &SkycoinBlockchainStatus{CacheTime: 20}
+ block := &SkycoinBlockchain{CacheTime: 20}
val, err := block.GetNumberOfBlocks()
require.NoError(t, err)
require.Equal(t, val, uint64(20))
@@ -76,7 +76,7 @@ func TestSkycoinBlockchainStatusGetLastBlock(t *testing.T) {
)
global_mock.On("BlockchainProgress").Return(&readable.BlockchainProgress{}, nil)
- status := &SkycoinBlockchainStatus{CacheTime: 20}
+ status := &SkycoinBlockchain{CacheTime: 20}
block, err := status.GetLastBlock()
require.NoError(t, err)
val, err2 := block.GetVersion()
diff --git a/src/coin/skycoin/models/main.go b/src/coin/skycoin/models/main.go
index 28b698b2..565a71e8 100644
--- a/src/coin/skycoin/models/main.go
+++ b/src/coin/skycoin/models/main.go
@@ -5,12 +5,15 @@ import (
"github.com/fibercrypto/fibercryptowallet/src/core"
"github.com/fibercrypto/fibercryptowallet/src/errors"
local "github.com/fibercrypto/fibercryptowallet/src/main"
+ appParams "github.com/fibercrypto/fibercryptowallet/src/params"
)
+// SkyFiberPlugin provide support for SkyFiber coins
type SkyFiberPlugin struct {
Params params.SkyFiberParams
}
+// ListSupportedAltcoins to enumerate supported assets and related metadata
func (p *SkyFiberPlugin) ListSupportedAltcoins() []core.AltcoinMetadata {
return []core.AltcoinMetadata{
core.AltcoinMetadata{
@@ -23,38 +26,43 @@ func (p *SkyFiberPlugin) ListSupportedAltcoins() []core.AltcoinMetadata {
core.AltcoinMetadata{
Name: CoinHoursName,
Ticker: CoinHoursTicker,
- Family: CoinHoursFamily,
+ Family: SkycoinFamily,
HasBip44: false,
Accuracy: 0,
},
core.AltcoinMetadata{
Name: CalculatedHoursName,
Ticker: CalculatedHoursTicker,
- Family: CalculatedHoursFamily,
+ Family: SkycoinFamily,
HasBip44: false,
Accuracy: 0,
},
}
}
+// ListSupportedFamilies classifies similar cryptocurrencies into a family
func (p *SkyFiberPlugin) ListSupportedFamilies() []string {
return []string{SkycoinFamily}
}
+// RegisterTo boilerplate to register this plugin against an altcoin manager and enable it
func (p *SkyFiberPlugin) RegisterTo(manager core.AltcoinManager) {
for _, info := range p.ListSupportedAltcoins() {
manager.RegisterAltcoin(info, p)
}
}
+// GetName provides concise human-readable caption o identify this plugin
func (p *SkyFiberPlugin) GetName() string {
return "SkyFiber"
}
+// GetDescription describes plugin and its features
func (p *SkyFiberPlugin) GetDescription() string {
return "FiberCrypto wallet connector for Skycoin and SkyFiber altcoins"
}
+// LoadWalletEnvs loads wallet environments to lookup and create wallets
func (p *SkyFiberPlugin) LoadWalletEnvs() []core.WalletEnv {
config := local.GetConfigManager()
@@ -76,15 +84,25 @@ func (p *SkyFiberPlugin) LoadWalletEnvs() []core.WalletEnv {
return wltEnvs
}
+// LoadPEX instantiates proxy object to interact with nodes nodes of the P2P network
func (p *SkyFiberPlugin) LoadPEX(netType string) (core.PEX, error) {
- var poolSection string
- if netType == "MainNet" {
- poolSection = PoolSection
- } else {
+ if netType != "MainNet" {
return nil, errors.ErrInvalidNetworkType
}
- return NewSkycoinPEX(poolSection), nil
+ return NewSkycoinPEX(PoolSection), nil
+}
+// LoadTransactionAPI blockchain transaction API entry poiny
+func (p *SkyFiberPlugin) LoadTransactionAPI(netType string) (core.BlockchainTransactionAPI, error) {
+ if netType != "MainNet" {
+ return nil, errors.ErrInvalidNetworkType
+ }
+ return NewSkycoinBlockchain(appParams.DataRefreshTimeout), nil
+}
+
+// LoadSignService sign service entry point
+func (p *SkyFiberPlugin) LoadSignService() (core.BlockchainSignService, error) {
+ return &SkycoinSignService{}, nil
}
func NewSkyFiberPlugin(params params.SkyFiberParams) core.AltcoinPlugin {
@@ -92,3 +110,8 @@ func NewSkyFiberPlugin(params params.SkyFiberParams) core.AltcoinPlugin {
Params: params,
}
}
+
+// Type assertions
+var (
+ _ core.AltcoinPlugin = &SkyFiberPlugin{}
+)
diff --git a/src/coin/skycoin/models/params.go b/src/coin/skycoin/models/params.go
index 3983da34..b6fc1886 100644
--- a/src/coin/skycoin/models/params.go
+++ b/src/coin/skycoin/models/params.go
@@ -18,10 +18,8 @@ const (
SkycoinDescription = params.SkycoinDescription
CoinHoursTicker = params.CoinHoursTicker
CoinHoursName = params.CoinHoursName
- CoinHoursFamily = params.CoinHoursFamily
CoinHoursDescription = params.CoinHoursDescription
CalculatedHoursTicker = params.CalculatedHoursTicker
CalculatedHoursName = params.CalculatedHoursName
- CalculatedHoursFamily = params.CalculatedHoursFamily
CalculatedHoursDescription = params.CalculatedHoursDescription
)
diff --git a/src/coin/skycoin/models/sign.go b/src/coin/skycoin/models/sign.go
new file mode 100644
index 00000000..986772e3
--- /dev/null
+++ b/src/coin/skycoin/models/sign.go
@@ -0,0 +1,19 @@
+package skycoin
+
+import (
+ "github.com/fibercrypto/fibercryptowallet/src/core"
+ "github.com/fibercrypto/fibercryptowallet/src/util"
+)
+
+// SkycoinSignService implements BlockchainSignService for multi-wallet transaction signing
+type SkycoinSignService struct{}
+
+// Sign creates a new transaction by (fully or partially) signing a given transaction
+func (sss *SkycoinSignService) Sign(txn core.Transaction, signSpec []core.InputSignDescriptor, pwd core.PasswordReader) (core.Transaction, error) {
+ return util.GenericMultiWalletSign(txn, signSpec, pwd)
+}
+
+// Type assertions
+var (
+ _ core.BlockchainSignService = &SkycoinSignService{}
+)
diff --git a/src/coin/skycoin/models/sky_test.go b/src/coin/skycoin/models/sky_test.go
index e6ac6a17..f35a2989 100644
--- a/src/coin/skycoin/models/sky_test.go
+++ b/src/coin/skycoin/models/sky_test.go
@@ -1,5 +1,12 @@
package skycoin
+/**
+ * This file contains test code copied from github.com/skycoin/skycoin codebase
+ * for it is needed by test suite and functions not exported in upstream package.
+ *
+ * Please only add in here code copied from Skycoin codebase. Make sure it's strictly necessary.
+ */
+
import (
"crypto/rand"
"path/filepath"
diff --git a/src/coin/skycoin/models/testdata/test.wlt b/src/coin/skycoin/models/testdata/test.wlt
index bb0512de..d782322c 100644
--- a/src/coin/skycoin/models/testdata/test.wlt
+++ b/src/coin/skycoin/models/testdata/test.wlt
@@ -36,4 +36,4 @@
"secret_key": "7418dd56fc6fef3943673c534bc4d21bfb00f18bf4ffacf8c6e06489299ca26a"
}
]
-}
\ No newline at end of file
+}
diff --git a/src/coin/skycoin/models/util_test.go b/src/coin/skycoin/models/util_test.go
new file mode 100644
index 00000000..4e6ab17d
--- /dev/null
+++ b/src/coin/skycoin/models/util_test.go
@@ -0,0 +1,84 @@
+package skycoin
+
+import (
+ "testing"
+
+ "github.com/skycoin/skycoin/src/cipher/bip39"
+
+ "github.com/fibercrypto/fibercryptowallet/src/core"
+ "github.com/fibercrypto/fibercryptowallet/src/util"
+ "github.com/skycoin/skycoin/src/cipher"
+ "github.com/skycoin/skycoin/src/coin"
+ "github.com/skycoin/skycoin/src/testutil"
+ "github.com/stretchr/testify/require"
+)
+
+func generateRandomKeyData(t *testing.T) *KeyData {
+ entropy, err := bip39.NewEntropy(128)
+ require.NoError(t, err)
+ mnemonic, err := bip39.NewMnemonic(entropy)
+ require.NoError(t, err)
+ require.NoError(t, err)
+ pubKey, secKey, err := cipher.GenerateDeterministicKeyPair([]byte(mnemonic))
+ require.NoError(t, err)
+
+ kd := &KeyData{
+ AddressIndex: 0,
+ Entropy: entropy,
+ Mnemonic: mnemonic,
+ PubKey: pubKey,
+ SecKey: secKey,
+ }
+
+ return kd
+}
+
+func makeUxBodyWithRandomSecret(t *testing.T) (coin.UxBody, *KeyData) {
+ keydata := generateRandomKeyData(t)
+ return coin.UxBody{
+ SrcTransaction: testutil.RandSHA256(t),
+ Address: cipher.AddressFromPubKey(keydata.PubKey),
+ Coins: 1e6,
+ Hours: 100,
+ }, keydata
+}
+
+func makeUxWithRandomSecret(t *testing.T) (coin.UxOut, *KeyData) {
+ body, kd := makeUxBodyWithRandomSecret(t)
+ return coin.UxOut{
+ Head: coin.UxHead{
+ Time: 100,
+ BkSeq: 2,
+ },
+ Body: body,
+ }, kd
+}
+
+func makeTransactionFromMultipleWallets(t *testing.T, n int) (coin.Transaction, []KeyData, []coin.UxOut) {
+ uxs := make([]coin.UxOut, n)
+ keysdata := make([]KeyData, n)
+ secs := make([]cipher.SecKey, n)
+
+ for i := 0; i < n; i++ {
+ ux, kd := makeUxWithRandomSecret(t)
+ uxs[i] = ux
+ secs[i] = kd.SecKey
+ keysdata[i] = *kd
+ }
+
+ return makeTransactionFromUxOuts(t, uxs, secs), keysdata, uxs
+}
+
+func makeSimpleWalletAddress(wallet core.Wallet, address core.Address) core.WalletAddress {
+ return &util.SimpleWalletAddress{
+ Wallet: wallet,
+ UxOut: address,
+ }
+}
+
+func makeSimpleWalletOutput(wallet core.Wallet, out core.TransactionOutput) core.WalletOutput {
+ return &util.SimpleWalletOutput{
+ Wallet: wallet,
+ UxOut: out,
+ }
+}
diff --git a/src/coin/skycoin/models/wallet.go b/src/coin/skycoin/models/wallet.go
index 5ea5606a..c4e23852 100644
--- a/src/coin/skycoin/models/wallet.go
+++ b/src/coin/skycoin/models/wallet.go
@@ -20,6 +20,7 @@ import (
"github.com/skycoin/skycoin/src/cipher/bip39"
"github.com/skycoin/skycoin/src/coin"
"github.com/skycoin/skycoin/src/readable"
+ "github.com/skycoin/skycoin/src/visor"
"github.com/skycoin/skycoin/src/wallet"
)
@@ -30,6 +31,7 @@ const (
CoinHour = params.CoinHoursTicker
CalculatedHour = params.CalculatedHoursTicker
+
walletExt = ".wlt"
WalletTimestampFormat = "2006_01_02"
@@ -37,7 +39,7 @@ const (
SignerIDRemoteWallet = "sky.remote"
)
-//Implements WalletIterator interface
+// SkycoinWalletIterator implements WalletIterator interface
type SkycoinWalletIterator struct {
current int
wallets []core.Wallet
@@ -105,7 +107,11 @@ func (wltSrv *SkycoinRemoteWallet) CreateWallet(label string, seed string, wltTy
}
defer ReturnSkycoinClient(c)
if IsEncrypted {
- password, err := pwd("Enter your password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletSet)
+ pwdCtx.SetValue(core.StrMethodName, "CreateWallet")
+ pwdCtx.SetValue(core.StrWalletLabel, label)
+ password, err := pwd("Enter password to encrypt wallet", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return nil, err
@@ -166,7 +172,11 @@ func (wltSrv *SkycoinRemoteWallet) Encrypt(walletName string, pwd core.PasswordR
return
}
defer ReturnSkycoinClient(c)
- password, err := pwd("Insert password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletStorage)
+ pwdCtx.SetValue(core.StrMethodName, "Encrypt")
+ pwdCtx.SetValue(core.StrWalletName, walletName)
+ password, err := pwd("Enter password to encrypt wallet", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return
@@ -187,7 +197,11 @@ func (wltSrv *SkycoinRemoteWallet) Decrypt(walletName string, pwd core.PasswordR
return
}
defer ReturnSkycoinClient(c)
- password, err := pwd("Insert password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletStorage)
+ pwdCtx.SetValue(core.StrMethodName, "Decrypt")
+ pwdCtx.SetValue(core.StrWalletName, walletName)
+ password, err := pwd("Enter password to decrypt wallet", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return
@@ -363,6 +377,7 @@ func (wlt *RemoteWallet) Sign(txn core.Transaction, signer core.TxnSigner, pwd c
func (wlt *RemoteWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordReader, index []int) (core.Transaction, error) {
client, err := NewSkycoinApiClient(PoolSection)
+ var password string = ""
if err != nil {
logWallet.WithError(err).Warn(err)
return nil, err
@@ -373,11 +388,19 @@ func (wlt *RemoteWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordR
logWallet.WithError(err).Warn(err)
return nil, errors.ErrInvalidTxn
}
- password, err := pwd(fmt.Sprintf("Enter password to decrypt wallet '%s'", wlt.Id))
- if err != nil {
- logWallet.WithError(err).Warn("Error getting password")
- return nil, err
+ if wlt.Encrypted {
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWallet)
+ pwdCtx.SetValue(core.StrMethodName, "Sign")
+ pwdCtx.SetValue(core.StrWalletName, wlt.Id)
+ pwdCtx.SetValue(core.StrWalletLabel, wlt.Label)
+ password, err = pwd("Enter password", pwdCtx)
+ if err != nil {
+ logWallet.WithError(err).Warn("Error getting password")
+ return nil, err
+ }
}
+
txnBytes, err := skyTxn.EncodeSkycoinTransaction()
if err != nil {
logWallet.WithError(err).Warn("Couldn't get Transaction Encoded")
@@ -424,7 +447,7 @@ func (wlt *RemoteWallet) GetId() string {
return wlt.Id
}
-func (wlt *RemoteWallet) Transfer(destination core.TransactionOutput, options core.KeyValueStorage) (core.Transaction, error) {
+func (wlt *RemoteWallet) Transfer(destination core.TransactionOutput, options core.KeyValueStore) (core.Transaction, error) {
logWallet.Info("Transfer from remote wallet")
amount, err := destination.GetCoins(SkycoinTicker)
if err != nil {
@@ -468,7 +491,7 @@ func (wlt *RemoteWallet) Transfer(destination core.TransactionOutput, options co
type createTxn func(*api.CreateTransactionRequest) (core.Transaction, error)
-func createTransaction(from []core.Address, to, uxOut []core.TransactionOutput, change core.Address, options core.KeyValueStorage, createTxnFunc createTxn) (core.Transaction, error) {
+func createTransaction(from []core.Address, to, uxOut []core.TransactionOutput, change core.Address, options core.KeyValueStore, createTxnFunc createTxn) (core.Transaction, error) {
logWallet.Info("Creating transaction...")
var req api.CreateTransactionRequest
req.IgnoreUnconfirmed = false
@@ -484,7 +507,7 @@ func createTransaction(from []core.Address, to, uxOut []core.TransactionOutput,
if uxOut != nil {
uxOuts := make([]string, 0)
for _, out := range uxOut {
- uxOuts = append(uxOuts, out.GetAddress().String())
+ uxOuts = append(uxOuts, out.GetId())
}
req.UxOuts = uxOuts
}
@@ -557,7 +580,7 @@ func createTransaction(from []core.Address, to, uxOut []core.TransactionOutput,
}
-func (wlt *RemoteWallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+func (wlt *RemoteWallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
logWallet.Info("Sending from address of remote wallets")
createTxnFunc := func(txnR *api.CreateTransactionRequest) (core.Transaction, error) {
logWallet.Info("Creating transaction for remote wallet")
@@ -584,7 +607,7 @@ func (wlt *RemoteWallet) SendFromAddress(from []core.Address, to []core.Transact
return createTransaction(from, to, nil, change, options, createTxnFunc)
}
-func (wlt *RemoteWallet) Spend(unspent, new []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+func (wlt *RemoteWallet) Spend(unspent, new []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
createTxnFunc := func(txnR *api.CreateTransactionRequest) (core.Transaction, error) {
logWallet.Info("Spend using remote wallet")
var req api.WalletCreateTransactionRequest
@@ -618,7 +641,12 @@ func (wlt *RemoteWallet) GenAddresses(addrType core.AddressType, startIndex, cou
return nil
}
defer ReturnSkycoinClient(c)
- password, err := pwd("Insert password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWallet)
+ pwdCtx.SetValue(core.StrMethodName, "GenAddresses")
+ pwdCtx.SetValue(core.StrWalletName, wlt.Id)
+ pwdCtx.SetValue(core.StrWalletLabel, wlt.Label)
+ password, err := pwd("Enter password", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return nil
@@ -693,16 +721,50 @@ func (wlt *RemoteWallet) SignTransaction(txn core.Transaction, pwdReader core.Pa
if strIdxs == nil {
indices = nil
} else {
- indices = make([]int, len(strIdxs))
- for i, strIdx := range strIdxs {
- indices[i], err = strconv.Atoi(strIdx)
+ indices, err = getHashIndices(txn.GetInputs(), strIdxs)
+ if err != nil {
+ logWallet.Error("Error parsing Skycoin transaction input indices array for signing")
+ return nil, err
+ }
+ }
+ signedTxn, err = wlt.signSkycoinTxn(txn, pwdReader, indices)
+ return
+}
+
+func getHashIndices(ins []core.TransactionInput, strIdxs []string) (indices []int, err error) {
+ cache := make(map[string]int, len(ins))
+ indices = make([]int, len(strIdxs))
+ scanIdx := 0
+ for i, strIdx := range strIdxs {
+ if strIdx[0] == '#' {
+ // Parse index
+ index, err := strconv.Atoi(strIdx[1:])
if err != nil {
return nil, errors.ErrIntegerInputsRequired
}
+ indices[i] = index
+ } else if index, isCached := cache[strIdx]; isCached {
+ // Found in previous scan
+ indices[i] = index
+ } else {
+ logWallet.Infof("Scanning inputs array looking for %s", strIdx)
+ // Continue scanning for UXID position in slice
+ notfound := true
+ for ; scanIdx < len(ins) && notfound; scanIdx++ {
+ uxID := ins[scanIdx].GetId()
+ logWallet.Infof("Scanning inputs array found %s", uxID)
+ cache[uxID] = scanIdx
+ if uxID == strIdx {
+ indices[i] = scanIdx
+ notfound = false
+ }
+ }
+ if notfound {
+ return nil, errors.ErrNotFound
+ }
}
}
- signedTxn, err = wlt.signSkycoinTxn(txn, pwdReader, indices)
- return
+ return indices, nil
}
func (wlt *RemoteWallet) GetSignerUID() core.UID {
@@ -817,7 +879,11 @@ func (wltSrv *SkycoinLocalWallet) GetWallet(id string) core.Wallet {
func (wltSrv *SkycoinLocalWallet) CreateWallet(label string, seed string, wltType string, IsEncrypted bool, pwd core.PasswordReader, scanAddressesN int) (core.Wallet, error) {
logWallet.Info("Creating Skycoin local wallet")
- password, err := pwd("Insert Password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletSet)
+ pwdCtx.SetValue(core.StrMethodName, "CreateWallet")
+ pwdCtx.SetValue(core.StrWalletLabel, label)
+ password, err := pwd("Insert Password", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
@@ -901,11 +967,17 @@ func (wltSrv *SkycoinLocalWallet) Encrypt(walletName string, password core.Passw
return
}
+ wltLabel := wlt.Label()
if wlt.IsEncrypted() {
return
}
- pwd, err := password("Insert Password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletStorage)
+ pwdCtx.SetValue(core.StrMethodName, "Encrypt")
+ pwdCtx.SetValue(core.StrWalletName, wltName)
+ pwdCtx.SetValue(core.StrWalletLabel, wltLabel)
+ pwd, err := password("Enter Password", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return
@@ -935,7 +1007,13 @@ func (wltSrv *SkycoinLocalWallet) Decrypt(walletName string, password core.Passw
if !wlt.IsEncrypted() {
return
}
- pwd, err := password("Insert Password")
+ wltLabel := wlt.Label()
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWalletStorage)
+ pwdCtx.SetValue(core.StrMethodName, "Decrypt")
+ pwdCtx.SetValue(core.StrWalletName, wltName)
+ pwdCtx.SetValue(core.StrWalletLabel, wltLabel)
+ pwd, err := password("Enter Password", pwdCtx)
if err != nil {
logWallet.WithError(err).Fatal("Something was wrong entering the password")
return
@@ -1041,22 +1119,33 @@ func (wlt *LocalWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordRe
var err error
var uxouts []coin.UxOut
var txnFee uint64
-
+ var resultTxn core.Transaction
walletDir := filepath.Join(wlt.WalletDir, wlt.Id)
skyWlt, err := wallet.Load(walletDir)
+ var originalInputs []api.CreatedTransactionInput
+
if err != nil {
logWallet.WithError(err).Warn("Couldn't load api client")
return nil, err
}
- if rTxn, isReadableTxn := txn.(skytypes.ReadableTxn); isReadableTxn {
+ rTxn, isReadableTxn := txn.(skytypes.ReadableTxn)
+ if isReadableTxn {
// Readable tranasctions should not need extra API calls
+
cTxn, err := rTxn.ToCreatedTransaction()
if err != nil {
+ logWallet.WithError(err).Warn("Failed to convert to readable transaction")
return nil, err
}
+ originalInputs = cTxn.In
if skyWlt.IsEncrypted() {
- pass, err := pwd("Type your password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWallet)
+ pwdCtx.SetValue(core.StrMethodName, "Sign")
+ pwdCtx.SetValue(core.StrWalletName, wlt.Id)
+ pwdCtx.SetValue(core.StrWalletLabel, wlt.Label)
+ pass, err := pwd("Enter password", pwdCtx)
if err != nil {
logWallet.WithError(err).Warn("Couldn't get password")
return nil, err
@@ -1119,6 +1208,7 @@ func (wlt *LocalWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordRe
}
} else {
// Raw transaction
+
unTxn, ok := txn.(*SkycoinUninjectedTransaction)
if !ok {
logWallet.WithError(err).Warn("Couldn't load transaction un injected")
@@ -1137,7 +1227,11 @@ func (wlt *LocalWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordRe
if skyWlt.IsEncrypted() {
- pass, err := pwd("Type your password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWallet)
+ pwdCtx.SetValue(core.StrMethodName, "Sign")
+ pwdCtx.SetValue(core.StrWalletName, wlt.Id)
+ pass, err := pwd("Enter password", pwdCtx)
if err != nil {
logWallet.WithError(err).Warn("Couldn't get password")
return nil, err
@@ -1153,9 +1247,11 @@ func (wlt *LocalWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordRe
uxouts = make([]coin.UxOut, 0)
for _, in := range unTxn.txn.In {
ux, err := clt.UxOut(in.String())
+
if err != nil {
return nil, err
}
+
addr, err := cipher.DecodeBase58Address(ux.OwnerAddress)
if err != nil {
return nil, err
@@ -1183,17 +1279,41 @@ func (wlt *LocalWallet) signSkycoinTxn(txn core.Transaction, pwd core.PasswordRe
if len(skyTxn.Sigs) == 0 {
skyTxn.Sigs = make([]cipher.Sig, len(skyTxn.In))
}
+
signedTxn, err := wallet.SignTransaction(skyWlt, skyTxn, index, uxouts)
+
if err != nil {
logWallet.WithError(err).Warn("Couldn't sign transaction using local wallet")
return nil, err
}
- // FIXME: Return readable SkycoinCreatedTransaction since UX data is available
- resultTxn, err := NewUninjectedTransaction(signedTxn, txnFee)
- if err != nil {
- logWallet.WithError(err).Warn("Couldn't create an un injected transaction")
- return nil, err
+
+ if isReadableTxn {
+ vins := make([]visor.TransactionInput, 0)
+ for _, ux := range uxouts {
+ vin, err := visor.NewTransactionInput(ux, 0)
+ if err != nil {
+ logWallet.WithError(err).Warn("Couldn't create a transaction input")
+ return nil, err
+ }
+ vins = append(vins, vin)
+ }
+
+ crtTxn, err := api.NewCreatedTransaction(signedTxn, vins)
+ crtTxn.In = originalInputs
+
+ if err != nil {
+ logWallet.WithError(err).Warn("Couldn't create an un SkycoinCreatedTransaction")
+ return nil, err
+ }
+
+ resultTxn = NewSkycoinCreatedTransaction(*crtTxn)
+ } else {
+ resultTxn, err = NewUninjectedTransaction(signedTxn, txnFee)
+ if err != nil {
+ return nil, err
+ }
}
+
return resultTxn, nil
}
@@ -1230,24 +1350,37 @@ func fromTxnResponse(txnResponse *api.CreateTransactionResponse) *SkycoinCreated
return NewSkycoinCreatedTransaction(txnResponse.Transaction)
}
-func (wlt *LocalWallet) Transfer(destination core.TransactionOutput, options core.KeyValueStorage) (core.Transaction, error) {
- logWallet.Info("Transfer from local wallet")
- amount, err := destination.GetCoins(SkycoinTicker)
+func skyAPICreateTxn(txnReq *api.CreateTransactionRequest) (core.Transaction, error) {
+ client, err := NewSkycoinApiClient(PoolSection)
if err != nil {
- logWallet.WithError(err).Warnf("Couldn't retrieve %s to transfer", params.SkycoinTicker)
+ logWallet.WithError(err).Warn("Couldn't load api client")
return nil, err
}
- to := destination.GetAddress()
+ defer ReturnSkycoinClient(client)
+ txnR, err := client.CreateTransaction(*txnReq)
+ if err != nil {
+ logWallet.WithError(err).Warn("Couldn't create transaction")
+ return nil, err
+ }
+ return fromTxnResponse(txnR), nil
+}
+func (wlt *LocalWallet) Transfer(to core.TransactionOutput, options core.KeyValueStore) (core.Transaction, error) {
+ logWallet.Info("Sending form local wallet")
quotient, err := util.AltcoinQuotient(Sky)
if err != nil {
logWallet.WithError(err).Warn("Couldn't get skycoin quotient")
return nil, err
}
+ amount, err := to.GetCoins(params.SkycoinTicker)
+ if err != nil {
+ logWallet.WithError(err).Warnf("Couldn't get ticker %s from TransactionOutput", params.SkycoinTicker)
+ return nil, err
+ }
strAmount := util.FormatCoins(amount, quotient)
var txnOutput SkycoinTransactionOutput
- txnOutput.skyOut.Address = to.String()
+ txnOutput.skyOut.Address = to.GetAddress().String()
txnOutput.skyOut.Coins = strAmount
addresses := make([]core.Address, 0)
iterAddr, err := wlt.GetLoadedAddresses()
@@ -1259,24 +1392,11 @@ func (wlt *LocalWallet) Transfer(destination core.TransactionOutput, options cor
addresses = append(addresses, iterAddr.Value())
}
- createTxnFunc := func(txnReq *api.CreateTransactionRequest) (core.Transaction, error) {
- client, err := NewSkycoinApiClient(PoolSection)
- if err != nil {
- logWallet.WithError(err).Warn("Couldn't load api client")
- return nil, err
- }
- defer ReturnSkycoinClient(client)
- txnR, err := client.CreateTransaction(*txnReq)
- if err != nil {
- logWallet.WithError(err).Warn("Couldn't create transaction")
- return nil, err
- }
- return fromTxnResponse(txnR), nil
- }
+ createTxnFunc := skyAPICreateTxn
return createTransaction(addresses, []core.TransactionOutput{&txnOutput}, nil, nil, options, createTxnFunc)
-
}
-func (wlt LocalWallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+
+func (wlt LocalWallet) SendFromAddress(from []core.Address, to []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
logWallet.Info("Sending from addresses in local wallet")
createTxnFunc := func(txnReq *api.CreateTransactionRequest) (core.Transaction, error) {
client, err := NewSkycoinApiClient(PoolSection)
@@ -1297,7 +1417,7 @@ func (wlt LocalWallet) SendFromAddress(from []core.Address, to []core.Transactio
return createTransaction(from, to, nil, change, options, createTxnFunc)
}
-func (wlt LocalWallet) Spend(unspent, new []core.TransactionOutput, change core.Address, options core.KeyValueStorage) (core.Transaction, error) {
+func (wlt LocalWallet) Spend(unspent, new []core.TransactionOutput, change core.Address, options core.KeyValueStore) (core.Transaction, error) {
logWallet.Info("Spending from local wallet")
createTxnFunc := func(txnReq *api.CreateTransactionRequest) (core.Transaction, error) {
client, err := NewSkycoinApiClient(PoolSection)
@@ -1391,7 +1511,12 @@ func (wlt *LocalWallet) GenAddresses(addrType core.AddressType, startIndex, coun
if walletLoaded.IsEncrypted() {
genAddressesInFile = func(w wallet.Wallet, n uint64) ([]cipher.Addresser, error) {
- password, err := pwd("Insert Password")
+ pwdCtx := util.NewKeyValueMap()
+ pwdCtx.SetValue(core.StrTypeName, core.TypeNameWallet)
+ pwdCtx.SetValue(core.StrMethodName, "GenAddresses")
+ pwdCtx.SetValue(core.StrWalletName, wlt.Id)
+ pwdCtx.SetValue(core.StrWalletLabel, wlt.Label)
+ password, err := pwd("Enter password", pwdCtx)
if err != nil {
logWallet.WithError(err).Error("Something was wrong entering the password")
return nil, nil
@@ -1523,12 +1648,10 @@ func (wlt *LocalWallet) SignTransaction(txn core.Transaction, pwdReader core.Pas
if strIdxs == nil {
indices = nil
} else {
- indices = make([]int, len(strIdxs))
- for i, strIdx := range strIdxs {
- indices[i], err = strconv.Atoi(strIdx)
- if err != nil {
- return nil, errors.ErrIntegerInputsRequired
- }
+ indices, err = getHashIndices(txn.GetInputs(), strIdxs)
+ if err != nil {
+ logWallet.Error("Error parsing Skycoin transaction input indices array for signing")
+ return nil, err
}
}
signedTxn, err = wlt.signSkycoinTxn(txn, pwdReader, indices)
diff --git a/src/coin/skycoin/models/wallet_test.go b/src/coin/skycoin/models/wallet_test.go
index bc08417b..49ece0b9 100644
--- a/src/coin/skycoin/models/wallet_test.go
+++ b/src/coin/skycoin/models/wallet_test.go
@@ -1,12 +1,15 @@
package skycoin
import (
+ "fmt"
"io/ioutil"
"math"
"strconv"
"strings"
"testing"
+ "github.com/skycoin/skycoin/src/visor"
+
"github.com/fibercrypto/fibercryptowallet/src/coin/skycoin/params"
"github.com/fibercrypto/fibercryptowallet/src/coin/skycoin/testsuite"
"github.com/fibercrypto/fibercryptowallet/src/core"
@@ -109,7 +112,7 @@ func TestSkycoinRemoteWalletCreateWallet(t *testing.T) {
mockSkyApiCreateWallet(global_mock, &wltOpt2, "walletNonEncrypted", false)
wltSrv := &SkycoinRemoteWallet{poolSection: PoolSection}
- pwdReader := func(message string) (string, error) {
+ pwdReader := func(message string, _ core.KeyValueStore) (string, error) {
return "pwd", nil
}
@@ -129,7 +132,7 @@ func TestSkycoinRemoteWalletEncrypt(t *testing.T) {
global_mock.On("EncryptWallet", "wallet", "pwd").Return(&api.WalletResponse{}, nil)
wltSrv := &SkycoinRemoteWallet{poolSection: PoolSection}
- pwdReader := func(message string) (string, error) {
+ pwdReader := func(message string, _ core.KeyValueStore) (string, error) {
return "pwd", nil
}
@@ -141,7 +144,7 @@ func TestSkycoinRemoteWalletDecrypt(t *testing.T) {
global_mock.On("DecryptWallet", "wallet", "pwd").Return(&api.WalletResponse{}, nil)
wltSrv := &SkycoinRemoteWallet{poolSection: PoolSection}
- pwdReader := func(message string) (string, error) {
+ pwdReader := func(message string, _ core.KeyValueStore) (string, error) {
return "pwd", nil
}
@@ -232,9 +235,10 @@ func TestRemoteWalletSignSkycoinTxn(t *testing.T) {
wlt := &RemoteWallet{
Id: "wallet",
+ Encrypted: true,
poolSection: PoolSection,
}
- pwdReader := func(message string) (string, error) {
+ pwdReader := func(string, core.KeyValueStore) (string, error) {
return "password", nil
}
ret, err := wlt.Sign(&unTxn, nil, pwdReader, nil)
@@ -556,7 +560,7 @@ func TestRemoteWalletGenAddresses(t *testing.T) {
Id: "wallet",
poolSection: PoolSection,
}
- pwdReader := func(message string) (string, error) {
+ pwdReader := func(message string, _ core.KeyValueStore) (string, error) {
return "pwd", nil
}
iter := wlt.GenAddresses(0, 0, 2, pwdReader)
@@ -801,7 +805,7 @@ func makeLocalWalletsFromKeyData(t *testing.T, keysData []KeyData) []core.Wallet
var err error
if w, isFound = walletsCache[kd.Mnemonic]; !isFound {
if w = walletSet.GetWallet(walletID); w == nil {
- w, err = walletSet.CreateWallet(walletID, kd.Mnemonic, wallet.WalletTypeDeterministic, false, func(string) (string, error) { return "", nil }, 0)
+ w, err = walletSet.CreateWallet(walletID, kd.Mnemonic, wallet.WalletTypeDeterministic, false, util.EmptyPassword, 0)
require.NoError(t, err)
}
walletsCache[kd.Mnemonic] = w
@@ -832,7 +836,7 @@ func TestTransactionSignInput(t *testing.T) {
wallets := makeLocalWalletsFromKeyData(t, keysData)
// Input is already signed
- _, err = wallets[0].Sign(uiTxn, nil, util.EmptyPassword, []string{"0"})
+ _, err = wallets[0].Sign(uiTxn, nil, util.EmptyPassword, []string{"#0"})
testutil.RequireError(t, err, "Transaction is fully signed")
isFullySigned, err = uiTxn.IsFullySigned()
require.NoError(t, err)
@@ -843,7 +847,7 @@ func TestTransactionSignInput(t *testing.T) {
isFullySigned, err = uiTxn.IsFullySigned()
require.NoError(t, err)
require.False(t, isFullySigned)
- signedCoreTxn, err = wallets[1].Sign(uiTxn, nil, nil, []string{"1"})
+ signedCoreTxn, err = wallets[1].Sign(uiTxn, nil, nil, []string{"#1"})
require.NoError(t, err)
signedTxn, isUninjected := signedCoreTxn.(*SkycoinUninjectedTransaction)
require.True(t, isUninjected)
@@ -854,7 +858,25 @@ func TestTransactionSignInput(t *testing.T) {
isFullySigned, err = signedTxn.IsFullySigned()
require.NoError(t, err)
require.True(t, isFullySigned)
- _, err = wallets[1].Sign(signedTxn, nil, nil, []string{"1"})
+ _, err = wallets[1].Sign(signedTxn, nil, nil, []string{"#1"})
+ testutil.RequireError(t, err, "Transaction is fully signed")
+ // Repeat using UXID
+ isFullySigned, err = uiTxn.IsFullySigned()
+ require.NoError(t, err)
+ require.False(t, isFullySigned)
+ uxId := txn.In[1].Hex()
+ signedCoreTxn, err = wallets[1].Sign(uiTxn, nil, nil, []string{uxId})
+ require.NoError(t, err)
+ signedTxn, isUninjected = signedCoreTxn.(*SkycoinUninjectedTransaction)
+ require.True(t, isUninjected)
+ require.NotEqual(t, uiTxn.txn, signedTxn.txn)
+ isFullySigned, err = uiTxn.IsFullySigned()
+ require.NoError(t, err)
+ require.False(t, isFullySigned)
+ isFullySigned, err = signedTxn.IsFullySigned()
+ require.NoError(t, err)
+ require.True(t, isFullySigned)
+ _, err = wallets[1].Sign(signedTxn, nil, nil, []string{"#1"})
testutil.RequireError(t, err, "Transaction is fully signed")
// Transaction has no sigs; sigs array is initialized
@@ -862,7 +884,7 @@ func TestTransactionSignInput(t *testing.T) {
isFullySigned, err = uiTxn.IsFullySigned()
require.NoError(t, err)
require.False(t, isFullySigned)
- signedCoreTxn, err = wallets[2].Sign(uiTxn, nil, nil, []string{"2"})
+ signedCoreTxn, err = wallets[2].Sign(uiTxn, nil, nil, []string{"#2"})
require.NoError(t, err)
signedTxn, isUninjected = signedCoreTxn.(*SkycoinUninjectedTransaction)
require.True(t, isUninjected)
@@ -876,14 +898,14 @@ func TestTransactionSignInput(t *testing.T) {
require.False(t, signedTxn.txn.Sigs[2].Null())
// Signing the rest of the inputs individually works
- signedCoreTxn, err = wallets[1].Sign(signedTxn, nil, nil, []string{"1"})
+ signedCoreTxn, err = wallets[1].Sign(signedTxn, nil, nil, []string{"#1"})
require.NoError(t, err)
signedTxn, isUninjected = signedCoreTxn.(*SkycoinUninjectedTransaction)
require.True(t, isUninjected)
isFullySigned, err = signedTxn.IsFullySigned()
require.NoError(t, err)
require.False(t, isFullySigned)
- signedCoreTxn, err = wallets[0].Sign(signedTxn, nil, nil, []string{"0"})
+ signedCoreTxn, err = wallets[0].Sign(signedTxn, nil, nil, []string{"#0"})
require.NoError(t, err)
signedTxn, isUninjected = signedCoreTxn.(*SkycoinUninjectedTransaction)
require.True(t, isUninjected)
@@ -937,7 +959,7 @@ func TestTransactionSignInputs(t *testing.T) {
// Valid signing
h := txn.HashInner()
- signedCoreTxn, err := wallet.Sign(uiTxn, nil, util.EmptyPassword, []string{"0", "1"})
+ signedCoreTxn, err := wallet.Sign(uiTxn, nil, util.EmptyPassword, []string{"#0", "#1"})
require.NoError(t, err)
signedTxn, isUninjected := signedCoreTxn.(*SkycoinUninjectedTransaction)
require.True(t, isUninjected)
@@ -971,6 +993,14 @@ func makeLocalWallet(t *testing.T) core.Wallet {
return wallet
}
+func makeSkycoinBlockchain() core.BlockchainTransactionAPI {
+ return NewSkycoinBlockchain(0)
+}
+
+func makeSkycoinSignService() core.BlockchainSignService {
+ return &SkycoinSignService{}
+}
+
func TestLocalWalletTransfer(t *testing.T) {
CleanGlobalMock()
destinationAddress := testutil.MakeAddress()
@@ -1201,3 +1231,318 @@ func TestSkycoinWalletTypes(t *testing.T) {
require.Equal(t, wallet.WalletTypeBip44, wltSet.DefaultWalletType())
require.Equal(t, []string{wallet.WalletTypeDeterministic, wallet.WalletTypeBip44}, wltSet.SupportedWalletTypes())
}
+
+func TestSkycoinBlockchainSendFromAddress(t *testing.T) {
+ CleanGlobalMock()
+
+ startAddress1 := testutil.MakeAddress()
+ startAddress2 := testutil.MakeAddress()
+
+ destinationAddress := testutil.MakeAddress()
+ changeAddress := (testutil.MakeAddress()).String()
+ sky := 500
+ hash := testutil.RandSHA256(t)
+
+ toAddr := &SkycoinTransactionOutput{
+ skyOut: readable.TransactionOutput{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ Hours: uint64(250),
+ },
+ }
+ fromAddr := []*SkycoinAddress{
+ &SkycoinAddress{
+ address: startAddress1.String(),
+ },
+ &SkycoinAddress{
+ address: startAddress2.String(),
+ },
+ }
+ chgAddr := &SkycoinAddress{
+ address: changeAddress,
+ }
+
+ opt1 := NewTransferOptions()
+ opt1.SetValue("BurnFactor", "0.5")
+ opt1.SetValue("CoinHoursSelectionType", "auto")
+
+ req1 := api.CreateTransactionRequest{
+ IgnoreUnconfirmed: false,
+ HoursSelection: api.HoursSelection{
+ Type: "auto",
+ Mode: "share",
+ ShareFactor: "0.5",
+ },
+ ChangeAddress: &changeAddress,
+ To: []api.Receiver{
+ api.Receiver{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ },
+ },
+ Addresses: []string{startAddress1.String(), startAddress2.String()},
+ }
+
+ opt2 := NewTransferOptions()
+ opt2.SetValue("BurnFactor", "0.5")
+ opt2.SetValue("CoinHoursSelectionType", "manual")
+
+ req2 := api.CreateTransactionRequest{
+ IgnoreUnconfirmed: false,
+ HoursSelection: api.HoursSelection{
+ Type: "manual",
+ },
+ ChangeAddress: &changeAddress,
+ To: []api.Receiver{
+ api.Receiver{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ Hours: "250",
+ },
+ },
+ Addresses: []string{startAddress1.String(), startAddress2.String()},
+ }
+
+ txn := &coin.Transaction{
+ Length: 100,
+ Type: 0,
+ InnerHash: hash,
+ }
+ ctxnR, err := api.NewCreateTransactionResponse(txn, nil)
+ ctxnR.Transaction.Fee = strconv.Itoa(sky)
+ require.NoError(t, err)
+
+ mockSkyApiCreateTransaction(global_mock, &req1, ctxnR)
+ mockSkyApiCreateTransaction(global_mock, &req2, ctxnR)
+
+ bc := makeSkycoinBlockchain()
+ wlt := &LocalWallet{}
+
+ //Testing Hours selection to auto
+ from := []core.WalletAddress{makeSimpleWalletAddress(wlt, fromAddr[0]), makeSimpleWalletAddress(wlt, fromAddr[1])}
+ to := []core.TransactionOutput{toAddr}
+ txnResult, err := bc.SendFromAddress(from, to, chgAddr, opt1)
+ require.NoError(t, err)
+ require.NotNil(t, txnResult)
+ val, err := txnResult.ComputeFee(params.CoinHoursTicker)
+ require.NoError(t, err)
+ require.Equal(t, util.FormatCoins(uint64(sky), 10), util.FormatCoins(uint64(val), 10))
+ require.Equal(t, ctxnR.Transaction.TxID, txnResult.GetId())
+
+ //Testing Hours selection to manual
+ from = []core.WalletAddress{makeSimpleWalletAddress(wlt, fromAddr[0]), makeSimpleWalletAddress(wlt, fromAddr[1])}
+ to = []core.TransactionOutput{toAddr}
+ txnResult, err = bc.SendFromAddress(from, to, chgAddr, opt2)
+ require.NoError(t, err)
+ require.NotNil(t, txnResult)
+ val, err = txnResult.ComputeFee(params.CoinHoursTicker)
+ require.NoError(t, err)
+ require.Equal(t, util.FormatCoins(uint64(sky), 10), util.FormatCoins(uint64(val), 10))
+ require.Equal(t, ctxnR.Transaction.TxID, txnResult.GetId())
+
+}
+
+func TestSkycoinBlockchainSpend(t *testing.T) {
+ CleanGlobalMock()
+
+ hash := testutil.RandSHA256(t)
+ sky := 500
+ //chgAddr :=
+ changeAddr := testutil.MakeAddress().String()
+ chgAddr := &SkycoinAddress{
+ address: changeAddr,
+ poolSection: "",
+ }
+ destinationAddress := testutil.MakeAddress()
+
+ toAddr := &SkycoinTransactionOutput{
+ skyOut: readable.TransactionOutput{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ Hours: uint64(250),
+ },
+ }
+
+ uxOuts := make([]coin.UxOut, 2)
+ for i := 0; i < 2; i++ {
+ ux, _, err := makeUxOutWithSecret(t)
+ require.NoError(t, err)
+ uxOuts[i] = ux
+ }
+
+ skyOuts := make([]core.TransactionOutput, len(uxOuts))
+ for i := 0; i < len(uxOuts); i++ {
+ ux := uxOuts[i]
+ quot, err := util.AltcoinQuotient(params.SkycoinTicker)
+ require.NoError(t, err)
+ sky := util.FormatCoins(ux.Body.Coins, quot)
+ skOut := SkycoinTransactionOutput{
+ spent: false,
+ skyOut: readable.TransactionOutput{
+ Address: ux.Body.Address.String(),
+ Hash: ux.Body.Hash().String(),
+ Coins: sky,
+ Hours: ux.Body.Hours,
+ },
+ }
+ skyOuts[i] = &skOut
+ }
+
+ wltOuts := make([]core.WalletOutput, len(uxOuts))
+ for i := 0; i < len(uxOuts); i++ {
+ wltOuts[i] = makeSimpleWalletOutput(nil, skyOuts[i])
+ }
+
+ uxOutsStr := make([]string, len(uxOuts))
+ for i := 0; i < len(uxOuts); i++ {
+ uxOutsStr[i] = uxOuts[i].Hash().String()
+ }
+
+ opt1 := NewTransferOptions()
+ opt1.SetValue("BurnFactor", "0.5")
+ opt1.SetValue("CoinHoursSelectionType", "auto")
+
+ req1 := api.CreateTransactionRequest{
+ UxOuts: uxOutsStr,
+ IgnoreUnconfirmed: false,
+ To: []api.Receiver{
+ api.Receiver{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ },
+ },
+ HoursSelection: api.HoursSelection{
+ Type: "auto",
+ Mode: "share",
+ ShareFactor: "0.5",
+ },
+ ChangeAddress: &changeAddr,
+ }
+
+ opt2 := NewTransferOptions()
+ opt2.SetValue("BurnFactor", "0.5")
+ opt2.SetValue("CoinHoursSelectionType", "manual")
+
+ req2 := api.CreateTransactionRequest{
+ UxOuts: uxOutsStr,
+ IgnoreUnconfirmed: false,
+ HoursSelection: api.HoursSelection{
+ Type: "manual",
+ },
+ ChangeAddress: &changeAddr,
+ To: []api.Receiver{
+ api.Receiver{
+ Address: destinationAddress.String(),
+ Coins: strconv.Itoa(sky),
+ Hours: "250",
+ },
+ },
+ }
+
+ txn := coin.Transaction{
+ InnerHash: hash,
+ Type: 0,
+ Length: 100,
+ }
+
+ crtTxn, err := api.NewCreateTransactionResponse(&txn, nil)
+ require.NoError(t, err)
+ crtTxn.Transaction.Fee = strconv.Itoa(sky)
+
+ mockSkyApiCreateTransaction(global_mock, &req1, crtTxn)
+ mockSkyApiCreateTransaction(global_mock, &req2, crtTxn)
+
+ bc := makeSkycoinBlockchain()
+
+ to := []core.TransactionOutput{toAddr}
+ //Testing Hours selection auto
+ txnR, err := bc.Spend(wltOuts, to, chgAddr, opt1)
+ require.NoError(t, err)
+ require.NotNil(t, txnR)
+ require.Equal(t, txnR.GetId(), crtTxn.Transaction.TxID)
+ val, err := txnR.ComputeFee(params.CoinHoursTicker)
+ require.NoError(t, err)
+ require.Equal(t, util.FormatCoins(uint64(sky), 10), util.FormatCoins(uint64(val), 10))
+
+ //Testing Hours selection manual
+ txnR2, err := bc.Spend(wltOuts, to, chgAddr, opt2)
+ require.NoError(t, err)
+ require.NotNil(t, txnR2)
+ require.Equal(t, txnR2.GetId(), crtTxn.Transaction.TxID)
+ val2, err := txnR2.ComputeFee(params.CoinHoursTicker)
+ require.NoError(t, err)
+ require.Equal(t, util.FormatCoins(uint64(sky), 10), util.FormatCoins(uint64(val2), 10))
+}
+
+func TestSkycoinSignServiceSign(t *testing.T) {
+ CleanGlobalMock()
+
+ txn, keyData, uxOuts := makeTransactionFromMultipleWallets(t, 3)
+ for _, ux := range uxOuts {
+ mockSkyApiUxOut(global_mock, ux)
+ }
+
+ ins := make([]visor.TransactionInput, 0)
+ for _, out := range uxOuts {
+ in, err := visor.NewTransactionInput(out, out.Head.Time)
+ require.NoError(t, err)
+
+ ins = append(ins, in)
+ }
+
+ pwdReader := func(_ string, _ core.KeyValueStore) (string, error) {
+ return "", nil
+ }
+
+ signer := makeSkycoinSignService()
+ wallets := makeLocalWalletsFromKeyData(t, keyData)
+
+ require.NotEqual(t, wallets[0], wallets[1])
+
+ isds := make([]core.InputSignDescriptor, 0)
+ for i, wlt := range wallets {
+ descriptor := core.InputSignDescriptor{
+ InputIndex: fmt.Sprintf("#%d", i),
+ SignerID: "", // Use wallet
+ Wallet: wlt,
+ }
+ isds = append(isds, descriptor)
+
+ }
+
+ //SkycoinCreatedTransaction
+ sigs := txn.Sigs
+ txn.Sigs = []cipher.Sig{}
+ apiCreTxn, err := api.NewCreatedTransaction(&txn, ins)
+ txn.Sigs = sigs
+ apiCreTxn.Sigs = make([]string, 0)
+ require.NoError(t, err)
+ require.NotNil(t, apiCreTxn)
+ require.Equal(t, apiCreTxn.InnerHash, txn.HashInner().Hex())
+ skyCreTxn := NewSkycoinCreatedTransaction(*apiCreTxn)
+
+ signedTxn, err := signer.Sign(skyCreTxn, isds, pwdReader)
+ require.NoError(t, err)
+ require.NotNil(t, signedTxn)
+ err = signedTxn.VerifySigned()
+ require.NoError(t, err)
+ //require.Equal(t, txn.Hash().String(), signedTxn.GetId())
+
+ //SkycoinUninjectedTransaction
+ txn.Sigs = []cipher.Sig{}
+ skyUninTxn := SkycoinUninjectedTransaction{
+ txn: &txn,
+ fee: 300,
+ }
+
+ signedTxn = nil
+
+ signedTxn, err = signer.Sign(&skyUninTxn, isds, pwdReader)
+ require.NoError(t, err)
+ require.NotNil(t, signedTxn)
+
+ signed, err := signedTxn.IsFullySigned()
+ require.NoError(t, err)
+ require.Equal(t, true, signed)
+
+}
diff --git a/src/coin/skycoin/params/params.go b/src/coin/skycoin/params/params.go
index 85615007..de28ab72 100644
--- a/src/coin/skycoin/params/params.go
+++ b/src/coin/skycoin/params/params.go
@@ -16,16 +16,24 @@ var (
// Constparams
const (
- SkycoinTicker = "SKY"
- SkycoinName = "Skycoin"
- SkycoinFamily = "SkyFiber"
- SkycoinDescription = "Skycoin is an entire cryptocurrency ecosystem aimed at eliminating mining rewards, developing energy-efficient custom hardware, speeding up transaction confirmation times, and the advancement of a more secure and private Internet"
+ // SkycoinTicker Skycoin coin identifier
+ SkycoinTicker = "SKY"
+ // SkycoinName human readable name associated to Skycoin
+ SkycoinName = "Skycoin"
+ // SkycoinFamily identifies Skyfiber coins
+ SkycoinFamily = "SkyFiber"
+ // SkycoinDescription verbose explanaitiion of Skycoin
+ SkycoinDescription = "Skycoin is an entire cryptocurrency ecosystem aimed at eliminating mining rewards, developing energy-efficient custom hardware, speeding up transaction confirmation times, and the advancement of a more secure and private Internet"
+ // CoinHoursTicker internal identifier to refer to Skycoin coin hours
CoinHoursTicker = "SCH"
- CoinHoursName = "Coin Hours"
- CoinHoursFamily = "SkyFiber"
- CoinHoursDescription = "Coin Hours is the parallel asset used for transaction fee, for creating scarcity, and to increase transaction privacy"
+ // CoinHoursName is the readable name for coin hours
+ CoinHoursName = "Coin Hours"
+ // CoinHoursDescription verbose explanaitiion of coin hours
+ CoinHoursDescription = "Coin Hours is the parallel asset used for transaction fee, for creating scarcity, and to increase transaction privacy"
+ // CoinHoursTicker internal identifier to refer to accumulated coin hours
CalculatedHoursTicker = "SCH#ACC"
- CalculatedHoursName = "Calculated Hours"
- CalculatedHoursFamily = "SkyFiber"
+ // CoinHoursName is the readable name for accumulated coin hours
+ CalculatedHoursName = "Calculated Hours"
+ // CalculatedHoursDescription verbose explanaitiion of accumulated coin hours
CalculatedHoursDescription = "Calculated Hours are Coin Hours calculated considering the time since an output was created"
)
diff --git a/src/coin/skycoin/testsuite/env.go b/src/coin/skycoin/testsuite/env.go
index 84030cc0..6e88c717 100644
--- a/src/coin/skycoin/testsuite/env.go
+++ b/src/coin/skycoin/testsuite/env.go
@@ -8,6 +8,7 @@ import (
const (
TestIDToken = "fibercryptotest"
ManyAddressesFilename = "many-addresses.golden"
+ Seed0000Filename = "seed-0000.golden"
)
func GetSkycoinCipherTestDataDir() string {
diff --git a/src/core/meta.go b/src/core/blockchain.go
similarity index 50%
rename from src/core/meta.go
rename to src/core/blockchain.go
index 87acc092..952e9ba7 100644
--- a/src/core/meta.go
+++ b/src/core/blockchain.go
@@ -19,3 +19,13 @@ type BlockchainStatus interface {
// GetNumberOfBlocks determine number of blocks in the blockchain
GetNumberOfBlocks() (uint64, error)
}
+
+// BlockchainAPI abstract interface for transactions management and utility functions for specific blockchain.
+// The service should use the blockchain node to implement given interface.
+type BlockchainTransactionAPI interface {
+ // SendFromAddress instantiates a transaction to send funds from specific source addresses
+ // to multiple destination addresses
+ SendFromAddress(from []WalletAddress, to []TransactionOutput, change Address, options KeyValueStore) (Transaction, error)
+ // Spend instantiate a transaction that spends specific outputs to send to multiple destination addresses
+ Spend(unspent []WalletOutput, new []TransactionOutput, change Address, options KeyValueStore) (Transaction, error)
+}
diff --git a/src/core/main.go b/src/core/main.go
index c2b77ab5..7df3c731 100644
--- a/src/core/main.go
+++ b/src/core/main.go
@@ -42,6 +42,10 @@ type AltcoinPlugin interface {
LoadWalletEnvs() []WalletEnv
// LoadPEX instantiates proxy object to interact with nodes nodes of the P2P network
LoadPEX(netType string) (PEX, error)
+ // LoadTransactionAPI blockchain transaction API entry poiny
+ LoadTransactionAPI(netType string) (BlockchainTransactionAPI, error)
+ // LoadSignService sign service entry point
+ LoadSignService() (BlockchainSignService, error)
}
// AltcoinManager defines the contract for altcoin repositories
diff --git a/src/core/sign.go b/src/core/sign.go
new file mode 100644
index 00000000..d510592c
--- /dev/null
+++ b/src/core/sign.go
@@ -0,0 +1,17 @@
+package core
+
+// InputSignDescriptor specifies how to sign a specific transaction input
+type InputSignDescriptor struct {
+ // InputIndex absolute (e.g. UXID) input identifier or relative (e.g. array index) in transaction context
+ InputIndex string
+ // SignerID selects a given signing strategy. If empty, default strategy will be chosen
+ SignerID UID
+ // Wallet placeholder containing private keys to sign transaction input
+ Wallet Wallet
+}
+
+// BlockchainSignService implement multi-wallet transaction signing for the blockchain
+type BlockchainSignService interface {
+ // Sign creates a new transaction by (fully or partially) signing a given transaction
+ Sign(txn Transaction, signSpec []InputSignDescriptor, pwd PasswordReader) (Transaction, error)
+}
diff --git a/src/core/storage.go b/src/core/storage.go
index 4e17ee30..b1d7317b 100644
--- a/src/core/storage.go
+++ b/src/core/storage.go
@@ -1,9 +1,93 @@
package core
-// KeyValueStorage provides read / write access to values given a key
-type KeyValueStorage interface {
+// KeyValueStore provides read / write access to values given a key
+type KeyValueStore interface {
// GetValue lookup value for key
GetValue(key string) interface{}
// SetValue bind value o known key
SetValue(key string, value interface{})
}
+
+const (
+ // StrSignerID option key for storing signer ID
+ StrSignerID = "signer.id"
+ // StrMethodType option key for method type name
+ StrTypeName = "api.typename"
+ // StrMethodName option key for method name
+ StrMethodName = "api.method"
+ // StrWalletLabel option key for wallet label
+ StrWalletLabel = "wallet.label"
+ // StrWalletName option key for wallet name
+ StrWalletName = "wallet.id"
+ // StrCoinTicker option key for coin ticker ID
+ StrCoinTicker = "coin.ticker"
+ // StrSenderObject option key for object that triggered an action
+ StrSenderObject = "call.self"
+
+ // TypeNameAddress Address type name
+ TypeNameAddress = "Address"
+ // TypeNameAddressIterator AddressIterator type name
+ TypeNameAddressIterator = "AddressIterator"
+ // TypeNameAltcoinManager AltcoinManager type name
+ TypeNameAltcoinManager = "AltcoinManager"
+ // TypeNameAltcoinPlugin AltcoinPlugin type name
+ TypeNameAltcoinPlugin = "AltcoinPlugin"
+ // TypeNameBlock Block type name
+ TypeNameBlock = "Block"
+ // TypeNameBlockchainSignService BlockchainSignService type name
+ TypeNameBlockchainSignService = "BlockchainSignService"
+ // TypeNameBlockchainStatus BlockchainStatus type name
+ TypeNameBlockchainStatus = "BlockchainStatus"
+ // TypeNameBlockchainTransactionAPI BlockchainTransactionAPI type name
+ TypeNameBlockchainTransactionAPI = "BlockchainTransactionAPI"
+ // TypeNameCryptoAccount CryptoAccount type name
+ TypeNameCryptoAccount = "CryptoAccount"
+ // TypeNameKeyValueStore KeyValueStore type name
+ TypeNameKeyValueStore = "KeyValueStore"
+ // TypeNameMultiPool MultiPool type name
+ TypeNameMultiPool = "MultiPool"
+ // TypeNameMultiPoolSection MultiPoolSection type name
+ TypeNameMultiPoolSection = "MultiPoolSection"
+ // TypeNamePEX PEX type name
+ TypeNamePEX = "PEX"
+ // TypeNamePexNode PexNode type name
+ TypeNamePexNode = "PexNode"
+ // TypeNamePexNodeIterator PexNodeIterator type name
+ TypeNamePexNodeIterator = "PexNodeIterator"
+ // TypeNamePexNodeSet PexNodeSet type name
+ TypeNamePexNodeSet = "PexNodeSet"
+ // TypeNamePooledObjectFactory PooledObjectFactory type name
+ TypeNamePooledObjectFactory = "PooledObjectFactory"
+ // TypeNameSeedGenerator SeedGenerator type name
+ TypeNameSeedGenerator = "SeedGenerator"
+ // TypeNameTransaction Transaction type name
+ TypeNameTransaction = "Transaction"
+ // TypeNameTransactionInput TransactionInput type name
+ TypeNameTransactionInput = "TransactionInput"
+ // TypeNameTransactionInputIterator TransactionInputIterator type name
+ TypeNameTransactionInputIterator = "TransactionInputIterator"
+ // TypeNameTransactionIterator TransactionIterator type name
+ TypeNameTransactionIterator = "TransactionIterator"
+ // TypeNameTransactionOutput TransactionOutput type name
+ TypeNameTransactionOutput = "TransactionOutput"
+ // TypeNameTransactionOutputIterator TransactionOutputIterator type name
+ TypeNameTransactionOutputIterator = "TransactionOutputIterator"
+ // TypeNameTxnSigner TxnSigner type name
+ TypeNameTxnSigner = "TxnSigner"
+ // TypeNameTxnSignerIterator TxnSignerIterator type name
+ TypeNameTxnSignerIterator = "TxnSignerIterator"
+ // TypeNameWallet Wallet type name
+ TypeNameWallet = "Wallet"
+ // TypeNameWalletAddress WalletAddress type name
+ TypeNameWalletAddress = "WalletAddress"
+ // TypeNameWalletEnv WalletEnv type name
+ TypeNameWalletEnv = "WalletEnv"
+ // TypeNameWalletIterator WalletIterator type name
+ TypeNameWalletIterator = "WalletIterator"
+ // TypeNameWalletOutput WalletOutput type name
+ TypeNameWalletOutput = "WalletOutput"
+ // TypeNameWalletSet WalletSet type name
+ TypeNameWalletSet = "WalletSet"
+ // TypeNameWalletStorage WalletStorage type name
+ TypeNameWalletStorage = "WalletStorage"
+)
diff --git a/src/core/textutil.go b/src/core/textutil.go
index c75f6dc2..4e1ddd2d 100644
--- a/src/core/textutil.go
+++ b/src/core/textutil.go
@@ -1,4 +1,4 @@
package core
-// PasswordReadeer secure retrieval of passwords from users
-type PasswordReader func(message string) (string, error)
+// PasswordReader secure retrieval of passwords from users
+type PasswordReader func(string, KeyValueStore) (string, error)
diff --git a/src/core/wallet.go b/src/core/wallet.go
index 536a9738..0b01f1a7 100644
--- a/src/core/wallet.go
+++ b/src/core/wallet.go
@@ -53,12 +53,12 @@ type Wallet interface {
// SetLabel establishes a label for this wallet
SetLabel(wltName string)
// Transfer instantiates unsigned transaction to send funds from any wallet address to single destination
- Transfer(to TransactionOutput, options KeyValueStorage) (Transaction, error)
+ Transfer(to TransactionOutput, options KeyValueStore) (Transaction, error)
// SendFromAddress instantiates unsigned transaction to send funds from specific source addresses
// to multiple destination addresses
- SendFromAddress(from []Address, to []TransactionOutput, change Address, options KeyValueStorage) (Transaction, error)
+ SendFromAddress(from []Address, to []TransactionOutput, change Address, options KeyValueStore) (Transaction, error)
// Spend instantiate unsigned transaction spending specific outputs to send to multiple destination addresses
- Spend(unspent, new []TransactionOutput, change Address, options KeyValueStorage) (Transaction, error)
+ Spend(unspent, new []TransactionOutput, change Address, options KeyValueStore) (Transaction, error)
// GenAddresses discover new addresses based on default hierarchically deterministic derivation sequences
// FIXME: Support account index to be fully compatible with BIP44
GenAddresses(addrType AddressType, startIndex, count uint32, pwd PasswordReader) AddressIterator
@@ -66,11 +66,27 @@ type Wallet interface {
GetCryptoAccount() CryptoAccount
// GetLoadedAddresses iterates over wallet addresses discovered and known to have previous history and coins
GetLoadedAddresses() (AddressIterator, error)
- // Sign creates a new transaction by (fully or partially) choosing a strategy to sign another transaction
+ // Sign creates a new transaction by (fully or partially) choosing a strategy to sign given transaction
// If signer instance is nil then default wallet strategy should be used for signing
Sign(txn Transaction, signer TxnSigner, pwd PasswordReader, index []string) (Transaction, error)
}
+// WalletOutput binds transaction output to originating wallet
+type WalletOutput interface {
+ // GetWallet return wallet
+ GetWallet() Wallet
+ // GetOutput return transaction output.
+ GetOutput() TransactionOutput
+}
+
+// WalletAddress aggregates address with originating wallet
+type WalletAddress interface {
+ // GetWallet return wallet
+ GetWallet() Wallet
+ // GetOutput return transaction output.
+ GetAddress() Address
+}
+
// SeedGenerator establishes the contract for generating BIP39-compatible mnemonics
type SeedGenerator interface {
// GenerateMnemonic generates a valid BIP-39 mnemonic phrase
@@ -79,7 +95,7 @@ type SeedGenerator interface {
VerifyMnemonic(seed string) (bool, error)
}
-// WalletEnvironment is the entry point to manage wallets
+// WalletEnv is the entry point to manage wallets
type WalletEnv interface {
// GetStorage provides access to wallet data store
GetStorage() WalletStorage
diff --git a/src/errors/error.go b/src/errors/error.go
index 1f1c0b2b..63e260f8 100644
--- a/src/errors/error.go
+++ b/src/errors/error.go
@@ -31,8 +31,12 @@ var (
ErrInvalidNetworkType = errors.New("Invalid netType")
// ErrInvalidID invalid ID
ErrInvalidID = errors.New("Invalid Id")
+ // ErrNotFound target item not found in collection
+ ErrNotFound = errors.New("Item not found in collection")
// ErrParseTxID invalid string value for transaction hash ID
ErrParseTxID = errors.New("Error parsing transaction hash")
+ // ErrParseSHA256 invalid SHA256 hash
+ ErrParseSHA256 = errors.New("Error parsing SHA256 hash")
// ErrParseTxnFee invalid string value for transaction fee
ErrParseTxnFee = errors.New("Error parsing transaction fee")
// ErrParseTxnCoins transaction coins can not be parsed
@@ -49,4 +53,6 @@ var (
ErrInvalidWalletEntropy = errors.New("Entropy must be 128 or 256")
// ErrInvalidValue invalid value was supplied in to function
ErrInvalidValue = errors.New("Value errors")
+ // ErrWalletCantSign wallet can not sign transactions
+ ErrWalletCantSign = errors.New("Wallet does not support transaction signing")
)
diff --git a/src/models/blockchainModels.go b/src/models/blockchainModels.go
index 4e1d939f..c6f8754b 100644
--- a/src/models/blockchainModels.go
+++ b/src/models/blockchainModels.go
@@ -6,6 +6,7 @@ import (
"github.com/fibercrypto/fibercryptowallet/src/coin/skycoin/models" //callable as skycoin
"github.com/fibercrypto/fibercryptowallet/src/core"
+ "github.com/fibercrypto/fibercryptowallet/src/params"
"github.com/fibercrypto/fibercryptowallet/src/util"
qtcore "github.com/therecipe/qt/core"
@@ -43,7 +44,7 @@ func (blockchainStatus *BlockchainStatusModel) init() {
blockchainStatus.SetCurrentCoinHoursSupplyDefault("0")
blockchainStatus.SetTotalCoinHoursSupplyDefault("0")
blockchainStatus.SetLoading(true)
- blockchainStatus.infoRequester = skycoin.NewSkycoinBlockchainStatus(1000000) //FIXME: set correct value
+ blockchainStatus.infoRequester = skycoin.NewSkycoinBlockchain(params.DataRefreshTimeout)
}
func (blockchainStatus *BlockchainStatusModel) update() {
diff --git a/src/models/bridgeModel.go b/src/models/bridgeModel.go
new file mode 100644
index 00000000..3a2823df
--- /dev/null
+++ b/src/models/bridgeModel.go
@@ -0,0 +1,53 @@
+package models
+
+import (
+ "sync"
+
+ "github.com/therecipe/qt/core"
+)
+
+type QBridge struct {
+ core.QObject
+ _ func() `constructor:"init"`
+ _ func() `slot:"lock"`
+ _ func() `slot:"unlock"`
+ _ func(message string) `signal:"getPassword"`
+ _ func(string) `slot:"setResult"`
+ _ func() string `slot:"getResult"`
+ result string
+ sem sync.Mutex
+ use sync.Mutex
+}
+
+func (b *QBridge) init() {
+
+ b.ConnectLock(b.lock)
+ b.ConnectUnlock(b.unlock)
+ b.ConnectSetResult(b.setResult)
+ b.ConnectGetResult(b.getResult)
+}
+
+func (b *QBridge) BeginUse() {
+ b.use.Lock()
+}
+
+func (b *QBridge) EndUse() {
+ b.use.Unlock()
+}
+
+func (b *QBridge) lock() {
+ b.sem.Lock()
+
+}
+
+func (b *QBridge) setResult(result string) {
+ b.result = result
+}
+
+func (b *QBridge) getResult() string {
+ return b.result
+}
+
+func (b *QBridge) unlock() {
+ b.sem.Unlock()
+}
diff --git a/src/models/models.go b/src/models/models.go
index 92b81b8a..3056ab3f 100644
--- a/src/models/models.go
+++ b/src/models/models.go
@@ -15,4 +15,6 @@ func init() {
ModelAddresses_QmlRegisterType2("OutputsModels", 1, 0, "QAddresses")
ModelOutputs_QmlRegisterType2("OutputsModels", 1, 0, "QOutputs")
QTransaction_QmlRegisterType2("Transactions", 1, 0, "QTransaction")
+ QBridge_QmlRegisterType2("Utils", 1, 0, "QBridge")
+
}
diff --git a/src/models/walletsManager.go b/src/models/walletsManager.go
index c13dec1d..030952bd 100644
--- a/src/models/walletsManager.go
+++ b/src/models/walletsManager.go
@@ -30,31 +30,35 @@ type WalletManager struct {
addresseseByWallets map[string][]*QAddress
outputsByAddress map[string][]*QOutput
altManager core.AltcoinManager
- _ func() `slot:"updateWalletEnvs"`
- _ func(wltId, address string) `slot:"updateOutputs"`
- _ func(string) `slot:"updateAddresses"`
- _ func() `slot:"updateWallets"`
- _ func() `constructor:"init"`
- _ func(seed string, label string, walletType string, password string, scanN int) *QWallet `slot:"createEncryptedWallet"`
- _ func(seed string, label string, walletType string, scanN int) *QWallet `slot:"createUnencryptedWallet"`
- _ func(entropy int) string `slot:"getNewSeed"`
- _ func(seed string) int `slot:"verifySeed"`
- _ func(id string, n int, password string) `slot:"newWalletAddress"`
- _ func(id string, password string) int `slot:"encryptWallet"`
- _ func(id string, password string) int `slot:"decryptWallet"`
- _ func() []*QWallet `slot:"getWallets"`
- _ func(id string) []*QAddress `slot:"getAddresses"`
- _ func(id string, source string, password string, index []int, qTxn *QTransaction) *QTransaction `slot:"signTxn"`
- _ func(wltId string, destinationAddress string, amount string) *QTransaction `slot:"sendTo"`
- _ func(id, label string) *QWallet `slot:"editWallet"`
- _ func(wltId, address string) []*QOutput `slot:"getOutputs"`
- _ func(txn *QTransaction) bool `slot:"broadcastTxn"`
- _ func(wltId string, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction `slot:"sendFromAddresses"`
- _ func(wltId string, outs, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction `slot:"sendFromOutputs"`
- _ func() []*QAddress `slot:"getAllAddresses"`
- _ func(wltId string) []*QOutput `slot:"getOutputsFromWallet"`
- _ func() string `slot:"getDefaultWalletType"`
- _ func() []string `slot:"getAvailableWalletTypes"`
+ signer core.BlockchainSignService
+ transactionAPI core.BlockchainTransactionAPI
+
+ _ func() `slot:"updateWalletEnvs"`
+ _ func(wltId, address string) `slot:"updateOutputs"`
+ _ func(string) `slot:"updateAddresses"`
+ _ func() `slot:"updateWallets"`
+ _ func() `constructor:"init"`
+ _ func(seed string, label string, walletType string, password string, scanN int) *QWallet `slot:"createEncryptedWallet"`
+ _ func(seed string, label string, walletType string, scanN int) *QWallet `slot:"createUnencryptedWallet"`
+ _ func(entropy int) string `slot:"getNewSeed"`
+ _ func(seed string) int `slot:"verifySeed"`
+ _ func(id string, n int, password string) `slot:"newWalletAddress"`
+ _ func(id string, password string) int `slot:"encryptWallet"`
+ _ func(id string, password string) int `slot:"decryptWallet"`
+ _ func() []*QWallet `slot:"getWallets"`
+ _ func(id string) []*QAddress `slot:"getAddresses"`
+ _ func(wltIds, addresses []string, source string, pwd interface{}, index []int, qTxn *QTransaction) *QTransaction `slot:"signTxn"`
+ _ func(wltId string, destinationAddress string, amount string) *QTransaction `slot:"sendTo"`
+ _ func(id, label string) *QWallet `slot:"editWallet"`
+ _ func(wltId, address string) []*QOutput `slot:"getOutputs"`
+ _ func(txn *QTransaction) bool `slot:"broadcastTxn"`
+ _ func(wltIds, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction `slot:"sendFromAddresses"`
+ _ func(wltIds, outs, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction `slot:"sendFromOutputs"`
+ _ func() []*QAddress `slot:"getAllAddresses"`
+ _ func(wltId string) []*QOutput `slot:"getOutputsFromWallet"`
+ _ func() string `slot:"getDefaultWalletType"`
+ _ func(wltIds, addresses []string, source string, bridgeForPassword *QBridge, index []int, qTxn *QTransaction) `slot:"signAndBroadcastTxnAsync"`
+ _ func() []string `slot:"getAvailableWalletTypes"`
}
func (walletM *WalletManager) init() {
@@ -83,15 +87,17 @@ func (walletM *WalletManager) init() {
walletM.ConnectUpdateWallets(walletM.updateWallets)
walletM.ConnectUpdateAddresses(walletM.updateAddresses)
walletM.ConnectUpdateOutputs(walletM.updateOutputs)
+ walletM.ConnectSignAndBroadcastTxnAsync(walletM.signAndBroadcastTxnAsync)
walletM.ConnectGetDefaultWalletType(walletM.getDefaultWalletType)
walletM.ConnectGetAvailableWalletTypes(walletM.getAvailableWalletTypes)
walletM.addresseseByWallets = make(map[string][]*QAddress, 0)
walletM.outputsByAddress = make(map[string][]*QOutput, 0)
walletM.altManager = local.LoadAltcoinManager()
-
walletM.SeedGenerator = new(sky.SeedService)
walletManager = walletM
walletM.updateWalletEnvs()
+ walletM.updateSigner()
+ walletM.updateTransactionAPI()
})
walletM = walletManager
@@ -120,6 +126,35 @@ func (walletM *WalletManager) getAvailableWalletTypes() []string {
return walletM.WalletEnv.GetWalletSet().SupportedWalletTypes()
}
+func (walletM *WalletManager) updateSigner() {
+ logWalletManager.Info("Updating Signers")
+ signers := make([]core.BlockchainSignService, 0)
+
+ for _, plug := range walletM.altManager.ListRegisteredPlugins() {
+ sing, err := plug.LoadSignService()
+ if err != nil {
+ logWalletManager.WithError(err).Errorf("Error loading signer from %s plugin", plug.GetName())
+ }
+ signers = append(signers, sing)
+ }
+
+ walletM.signer = signers[0]
+}
+
+func (walletM *WalletManager) updateTransactionAPI() {
+ logWalletManager.Info("Updating TransactionAPI")
+ txnAPIS := make([]core.BlockchainTransactionAPI, 0)
+
+ for _, plug := range walletM.altManager.ListRegisteredPlugins() {
+ txnAPI, err := plug.LoadTransactionAPI("MainNet")
+ if err != nil {
+ logWalletManager.WithError(err).Errorf("Error loading transaction API from %s plugin", plug.GetName())
+ }
+ txnAPIS = append(txnAPIS, txnAPI)
+ }
+
+ walletM.transactionAPI = txnAPIS[0]
+}
func (walletM *WalletManager) updateWalletEnvs() {
logWalletManager.Info("Updating WalletEnvs")
walletsEnvs := make([]core.WalletEnv, 0)
@@ -294,6 +329,15 @@ func (walletM *WalletManager) broadcastTxn(txn *QTransaction) bool {
logWalletManager.WithError(err).Warn("Error loading PEX")
return false
}
+ isSigned, err := txn.txn.IsFullySigned()
+ if err != nil {
+ logWalletManager.WithError(err).Warn("Error checking if transaction if fully signed")
+ return false
+ }
+ if !isSigned {
+ logWalletManager.Warn("Transaction is not fully signed")
+ return false
+ }
err = pex.BroadcastTxn(txn.txn)
if err != nil {
logWalletManager.WithError(err).Warn("Error broadcasting transaction")
@@ -303,17 +347,27 @@ func (walletM *WalletManager) broadcastTxn(txn *QTransaction) bool {
return true
}
-func (walletM *WalletManager) sendFromOutputs(wltId string, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction {
+func (walletM *WalletManager) sendFromOutputs(wltIds []string, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction {
logWalletManager.Info("Creating transaction")
- wlt := walletM.WalletEnv.GetWalletSet().GetWallet(wltId)
- if wlt == nil {
- logWalletManager.Warn("Couldn't load wallet to create transaction")
- return nil
+ wltCache := make(map[string]core.Wallet, 0)
+ wlts := make([]core.Wallet, 0)
+ for _, wltId := range wltIds {
+ var wlt core.Wallet
+ wlt, exist := wltCache[wltId]
+ if !exist {
+ wlt = walletM.WalletEnv.GetWalletSet().GetWallet(wltId)
+ if wlt == nil {
+ logWalletManager.Warn("Couldn't load wallet to create transaction")
+ return nil
+ }
+ wltCache[wltId] = wlt
+ }
+ wlts = append(wlts, wlt)
}
+
outputsFrom := make([]core.TransactionOutput, 0)
for _, outAddr := range from {
- addr := util.NewGenericAddress(outAddr)
- out := util.NewGenericOutput(&addr)
+ out := util.NewGenericOutput(nil, outAddr)
outputsFrom = append(outputsFrom, &out)
}
outputsTo := make([]core.TransactionOutput, 0)
@@ -323,7 +377,7 @@ func (walletM *WalletManager) sendFromOutputs(wltId string, from, addrTo, skyTo,
ch = coinHoursTo[i]
}
addr := util.NewGenericAddress(addrTo[i])
- out := util.NewGenericOutput(&addr)
+ out := util.NewGenericOutput(&addr, "")
// FIXME: Remove explicit reference to Skycoin
err := out.PushCoins(sky.Sky, skyTo[i])
if err != nil {
@@ -346,8 +400,23 @@ func (walletM *WalletManager) sendFromOutputs(wltId string, from, addrTo, skyTo,
} else {
opt.SetValue("CoinHoursSelectionType", "manual")
}
+ var txn core.Transaction
+ var err error
+ if len(wltCache) > 1 {
+ fmt.Println("MULTI TXN")
+ walletsOutputs := make([]core.WalletOutput, 0)
+ for i, wlt := range wlts {
+ walletsOutputs = append(walletsOutputs, &util.SimpleWalletOutput{
+ Wallet: wlt,
+ UxOut: outputsFrom[i],
+ })
+ }
+ txn, err = walletM.transactionAPI.Spend(walletsOutputs, outputsTo, &changeAddr, opt)
+ } else {
+ fmt.Println("SIMPLE TXN")
+ txn, err = wlts[0].Spend(outputsFrom, outputsTo, &changeAddr, opt)
+ }
- txn, err := wlt.Spend(outputsFrom, outputsTo, &changeAddr, opt)
if err != nil {
logWalletManager.WithError(err).Info("Error creating transaction")
return nil
@@ -360,12 +429,24 @@ func (walletM *WalletManager) sendFromOutputs(wltId string, from, addrTo, skyTo,
}
return qTransaction
}
-func (walletM *WalletManager) sendFromAddresses(wltId string, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction {
- wlt := walletM.WalletEnv.GetWalletSet().GetWallet(wltId)
- if wlt == nil {
- logWalletManager.Warn("Couldn't load wallet to create transaction")
- return nil
+func (walletM *WalletManager) sendFromAddresses(wltIds []string, from, addrTo, skyTo, coinHoursTo []string, change string, automaticCoinHours bool, burnFactor string) *QTransaction {
+ fmt.Printf("WLTS %v\n", wltIds)
+ wltCache := make(map[string]core.Wallet, 0)
+ wlts := make([]core.Wallet, 0)
+ for _, wltId := range wltIds {
+ var wlt core.Wallet
+ wlt, exist := wltCache[wltId]
+ if !exist {
+ wlt = walletM.WalletEnv.GetWalletSet().GetWallet(wltId)
+ if wlt == nil {
+ logWalletManager.Warn("Couldn't load wallet to create transaction")
+ return nil
+ }
+ wltCache[wltId] = wlt
+ }
+ wlts = append(wlts, wlt)
}
+
addrsFrom := make([]core.Address, 0)
for _, addr := range from {
@@ -378,17 +459,17 @@ func (walletM *WalletManager) sendFromAddresses(wltId string, from, addrTo, skyT
ch = coinHoursTo[i]
}
addr := util.NewGenericAddress(addrTo[i])
- out := util.NewGenericOutput(&addr)
+ out := util.NewGenericOutput(&addr, "")
// FIXME: Remove explicit reference to Skycoin
err := out.PushCoins(sky.Sky, skyTo[i])
if err != nil {
- logWalletManager.WithError(err).Warn("Error parsing value for %s", sky.Sky)
+ logWalletManager.WithError(err).Warnf("Error parsing value for %s", sky.Sky)
return nil
}
// FIXME: Remove explicit reference to Skycoin
err = out.PushCoins(sky.CoinHour, ch)
if err != nil {
- logWalletManager.WithError(err).Warn("Error parsing value for %s", sky.Sky)
+ logWalletManager.WithError(err).Warnf("Error parsing value for %s", sky.Sky)
return nil
}
outputsTo = append(outputsTo, &out)
@@ -402,8 +483,23 @@ func (walletM *WalletManager) sendFromAddresses(wltId string, from, addrTo, skyT
} else {
opt.SetValue("CoinHoursSelectionType", "manual")
}
+ var txn core.Transaction
+ var err error
+ if len(wltCache) > 1 {
+ walletsAddresses := make([]core.WalletAddress, 0)
+ for i, wlt := range wlts {
+ walletsAddresses = append(walletsAddresses, &util.SimpleWalletAddress{
+ Wallet: wlt,
+ UxOut: addrsFrom[i],
+ })
+ }
+ fmt.Println("MULTIPLE TRANSACTION")
+ txn, err = walletM.transactionAPI.SendFromAddress(walletsAddresses, outputsTo, changeAddr, opt)
+ } else {
+ fmt.Println("SINGLE TRANSACTION")
+ txn, err = wlts[0].SendFromAddress(addrsFrom, outputsTo, changeAddr, opt)
+ }
- txn, err := wlt.SendFromAddress(addrsFrom, outputsTo, changeAddr, opt)
if err != nil {
logWalletManager.WithError(err).Info("Error creating transaction")
return nil
@@ -453,7 +549,7 @@ func (walletM *WalletManager) sendTo(wltId, destinationAddress, amount string) *
logWalletManager.Warn("Couldn't load wallet to create transaction")
return nil
}
- txOut := util.NewGenericOutput(&addr)
+ txOut := util.NewGenericOutput(&addr, "")
// FIXME: Remove explicit reference to Skycoin
err := txOut.PushCoins(sky.Sky, amount)
if err != nil {
@@ -475,35 +571,115 @@ func (walletM *WalletManager) sendTo(wltId, destinationAddress, amount string) *
}
-func (walletM *WalletManager) signTxn(id, source, password string, index []int, qTxn *QTransaction) *QTransaction {
+func (walletM *WalletManager) signTxn(wltIds, address []string, source string, tmpPwd interface{}, index []int, qTxn *QTransaction) *QTransaction {
+ pwd, isPwdReader := tmpPwd.(core.PasswordReader)
+ if !isPwdReader {
+ return nil
+ }
logWalletManager.Info("Signig transaction")
- // Get wallet
- wlt := walletM.WalletEnv.GetWalletSet().GetWallet(id)
- if wlt == nil {
- logWalletManager.Warn("Couldn't load wallet to Sign transaction")
+
+ if len(wltIds) != len(address) {
+ logWalletManager.Error("Wallets and addresses provided are incorrect")
return nil
}
- signer := util.LookupSignerByUID(wlt, core.UID(source))
- txn, err := wlt.Sign(qTxn.txn, signer, func(message string) (string, error) {
- return password, nil
- }, nil) // TODO Get index for sign specific txn indexes
+
+ wltCache := make(map[string]core.Wallet)
+ wltByAddr := make(map[string]core.Wallet)
+ wlts := make([]core.Wallet, 0)
+
+ for i, wltId := range wltIds {
+ var wlt core.Wallet
+ wlt, exist := wltCache[wltId]
+ if !exist {
+ wlt = walletM.WalletEnv.GetWalletSet().GetWallet(wltId)
+ if wlt == nil {
+ logWalletManager.Warn("Couldn't load wallet to Sign transaction")
+ return nil
+ }
+ wltCache[wltId] = wlt
+ }
+ wltByAddr[address[i]] = wlt
+ wlts = append(wlts, wlt)
+ }
+
+ var txn core.Transaction
+ var err error
+
+ if len(wltCache) > 1 {
+ signDescriptors := make([]core.InputSignDescriptor, 0)
+ for _, in := range qTxn.txn.GetInputs() {
+ sd := core.InputSignDescriptor{
+ InputIndex: in.GetId(),
+ SignerID: core.UID(source),
+ Wallet: wltByAddr[in.GetSpentOutput().GetAddress().String()],
+ }
+ signDescriptors = append(signDescriptors, sd)
+ }
+ txn, err = walletM.signer.Sign(qTxn.txn, signDescriptors, pwd)
+ } else {
+ signer, err2 := util.LookupSignServiceForWallet(wlts[0], core.UID(source))
+ if err2 != nil {
+ logWalletManager.WithError(err).Warnf("No signer %s for wallet %v", source, wlts[0])
+ return nil
+ }
+ txn, err = wlts[0].Sign(qTxn.txn, signer, pwd, nil)
+ }
+
if err != nil {
logWalletManager.WithError(err).Warn("Error signing txn")
return nil
}
+
qTxn, err = NewQTransactionFromTransaction(txn)
if err != nil {
logWalletManager.WithError(err).Warn("Error converting transaction")
return nil
}
return qTxn
+
+}
+
+func (walletM *WalletManager) signAndBroadcastTxnAsync(wltIds, addresses []string, source string, bridgeForPassword *QBridge, index []int, qTxn *QTransaction) {
+ channel := make(chan *QTransaction)
+ go func() {
+ pwd := func(message string, ctx core.KeyValueStore) (string, error) {
+ bridgeForPassword.BeginUse()
+ defer bridgeForPassword.EndUse()
+ bridgeForPassword.lock()
+ suffix := ""
+ v := ctx.GetValue(core.StrWalletLabel)
+ if v == nil {
+ v = ctx.GetValue(core.StrWalletName)
+ }
+ if v != nil {
+ if str, isStr := v.(string); isStr {
+ suffix = " for " + str
+ }
+ }
+ bridgeForPassword.GetPassword(message + suffix)
+ bridgeForPassword.lock()
+ pass := bridgeForPassword.getResult()
+ bridgeForPassword.unlock()
+ return pass, nil
+ }
+
+ channel <- walletM.signTxn(wltIds, addresses, source, pwd, index, qTxn)
+ }()
+
+ go func() {
+ txn := <-channel
+ if txn != nil {
+ walletM.broadcastTxn(txn)
+ }
+
+ }()
}
func (walletM *WalletManager) createEncryptedWallet(seed, label, wltType, password string, scanN int) *QWallet {
logWalletManager.Info("Creating encrypted wallet")
- pwd := func(message string) (string, error) {
- return password, nil
- }
+ pwd := util.ConstantPassword(password)
+ // NOTE: No easy way to get plain passwords in memory
+ password = ""
fmt.Println("WALLET TYPE ", wltType)
wlt, err := walletM.WalletEnv.GetWalletSet().CreateWallet(label, seed, wltType, true, pwd, scanN)
if err != nil {
@@ -518,9 +694,7 @@ func (walletM *WalletManager) createEncryptedWallet(seed, label, wltType, passwo
func (walletM *WalletManager) createUnencryptedWallet(seed, label, wltType string, scanN int) *QWallet {
logWalletManager.Info("Creating encrypted wallet")
- pwd := func(message string) (string, error) {
- return "", nil
- }
+ pwd := util.EmptyPassword
wlt, err := walletM.WalletEnv.GetWalletSet().CreateWallet(label, seed, wltType, false, pwd, scanN)
if err != nil {
@@ -560,9 +734,9 @@ func (walletM *WalletManager) verifySeed(seed string) int {
func (walletM *WalletManager) encryptWallet(id, password string) int {
logWalletManager.Info("Encrypting wallet")
- pwd := func(message string) (string, error) {
- return password, nil
- }
+ pwd := util.ConstantPassword(password)
+ // NOTE: No easy way to get plain passwords in memory
+ password = ""
walletM.WalletEnv.GetStorage().Encrypt(id, pwd)
ret, err := walletM.WalletEnv.GetStorage().IsEncrypted(id)
if err != nil {
@@ -577,9 +751,9 @@ func (walletM *WalletManager) encryptWallet(id, password string) int {
func (walletM *WalletManager) decryptWallet(id, password string) int {
logWalletManager.Info("Decrypt wallet")
- pwd := func(message string) (string, error) {
- return password, nil
- }
+ pwd := util.ConstantPassword(password)
+ // NOTE: No easy way to get plain passwords in memory
+ password = ""
walletM.WalletEnv.GetStorage().Decrypt(id, pwd)
ret, err := walletM.WalletEnv.GetStorage().IsEncrypted(id)
if err != nil {
@@ -595,9 +769,9 @@ func (walletM *WalletManager) decryptWallet(id, password string) int {
func (walletM *WalletManager) newWalletAddress(id string, n int, password string) {
logWalletManager.Info("Creating new wallet addresses")
wlt := walletM.WalletEnv.GetWalletSet().GetWallet(id)
- pwd := func(message string) (string, error) {
- return password, nil
- }
+ pwd := util.ConstantPassword(password)
+ // NOTE: No easy way to get plain passwords in memory
+ password = ""
wltEntriesLen := 0
it, err := wlt.GetLoadedAddresses()
if err != nil {
diff --git a/src/params/params.go b/src/params/params.go
new file mode 100644
index 00000000..02d80422
--- /dev/null
+++ b/src/params/params.go
@@ -0,0 +1,6 @@
+package params
+
+const (
+ // Default refresh timeout for API data
+ DataRefreshTimeout = 1000000 //FIXME: set correct value
+)
diff --git a/src/ui/Controls/TextArea.qml b/src/ui/Controls/TextArea.qml
index 2801b083..3caabe4c 100644
--- a/src/ui/Controls/TextArea.qml
+++ b/src/ui/Controls/TextArea.qml
@@ -29,4 +29,4 @@ TextArea {
contextMenu.popup()
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/Controls/TextField.qml b/src/ui/Controls/TextField.qml
index d082156a..4294ebfa 100644
--- a/src/ui/Controls/TextField.qml
+++ b/src/ui/Controls/TextField.qml
@@ -29,4 +29,4 @@ TextField {
contextMenu.popup()
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/CustomMenuItem.qml b/src/ui/CustomMenuItem.qml
index 0d3e48e2..9b4b207b 100644
--- a/src/ui/CustomMenuItem.qml
+++ b/src/ui/CustomMenuItem.qml
@@ -26,4 +26,4 @@ MenuItem {
opacity: parent.highlighted ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 500; easing.type: Easing.OutQuint } }
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/Delegates/OutputsListAddressDelegate.qml b/src/ui/Delegates/OutputsListAddressDelegate.qml
index 6c1ed619..900d77b5 100644
--- a/src/ui/Delegates/OutputsListAddressDelegate.qml
+++ b/src/ui/Delegates/OutputsListAddressDelegate.qml
@@ -83,4 +83,4 @@ Item {
} // ListView
// Roles: outputID, addressSky, addressCoinHours
-}
\ No newline at end of file
+}
diff --git a/src/ui/Delegates/PasswordRequesterDelegate.qml b/src/ui/Delegates/PasswordRequesterDelegate.qml
new file mode 100644
index 00000000..c2f2528a
--- /dev/null
+++ b/src/ui/Delegates/PasswordRequesterDelegate.qml
@@ -0,0 +1,34 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Controls.Material 2.12
+import QtQuick.Layouts 1.12
+import OutputsModels 1.0
+
+
+Item {
+ id: passwordRequesterDelegate
+
+ TextField {
+ id: textFieldPassword
+ text: name
+ placeholderText: qsTr("Password")
+ selectByMouse: true
+ echoMode: TextField.Password
+ focus: true
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: true
+ }
+
+ Button {
+ id: buttonForgot
+ text: qsTr("I forgot my password")
+ flat: true
+ highlighted: hovered
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ Layout.fillWidth: true
+
+ onClicked: {
+ passwordForgotten()
+ }
+ }
+}
diff --git a/src/ui/Dialogs/DialogAddAddresses.qml b/src/ui/Dialogs/DialogAddAddresses.qml
index 611bd0e2..749619cd 100644
--- a/src/ui/Dialogs/DialogAddAddresses.qml
+++ b/src/ui/Dialogs/DialogAddAddresses.qml
@@ -33,4 +33,4 @@ Dialog {
focus: true
}
} // ColumnLayout (root)
-}
\ No newline at end of file
+}
diff --git a/src/ui/Dialogs/DialogAddLoadWallet.qml b/src/ui/Dialogs/DialogAddLoadWallet.qml
index 12e9181d..1d50cc66 100644
--- a/src/ui/Dialogs/DialogAddLoadWallet.qml
+++ b/src/ui/Dialogs/DialogAddLoadWallet.qml
@@ -230,4 +230,4 @@ Dialog {
anchors.rightMargin: -dialogAddWallet.rightPadding + 1
}
} // Flickable
-}
\ No newline at end of file
+}
diff --git a/src/ui/Dialogs/DialogEditWallet.qml b/src/ui/Dialogs/DialogEditWallet.qml
index bd11c281..1a3de027 100644
--- a/src/ui/Dialogs/DialogEditWallet.qml
+++ b/src/ui/Dialogs/DialogEditWallet.qml
@@ -40,4 +40,4 @@ Dialog {
standardButton(Dialog.Ok).enabled = text && text != originalWalletName
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/Dialogs/DialogSelectAddressByWallet.qml b/src/ui/Dialogs/DialogSelectAddressByWallet.qml
index 4d1ba3f4..8525fc44 100644
--- a/src/ui/Dialogs/DialogSelectAddressByWallet.qml
+++ b/src/ui/Dialogs/DialogSelectAddressByWallet.qml
@@ -90,4 +90,4 @@ Dialog {
ScrollIndicator.vertical: ScrollIndicator { }
} // ListView
} // ColumnLayout
-}
\ No newline at end of file
+}
diff --git a/src/ui/MenuThemeAccent.qml b/src/ui/MenuThemeAccent.qml
index 65f52c11..13594e93 100644
--- a/src/ui/MenuThemeAccent.qml
+++ b/src/ui/MenuThemeAccent.qml
@@ -125,4 +125,4 @@ Menu {
} // Rectangle
} // Repeater
} // Grid
-}
\ No newline at end of file
+}
diff --git a/src/ui/MultiplePasswordRequester.qml b/src/ui/MultiplePasswordRequester.qml
new file mode 100644
index 00000000..bbace1d2
--- /dev/null
+++ b/src/ui/MultiplePasswordRequester.qml
@@ -0,0 +1,56 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Controls.Material 2.12
+import QtQuick.Layouts 1.12
+
+// Resource imports
+// import "qrc:/ui/src/ui/Controls"
+import "Controls" // For quick UI development, switch back to resources when making a release
+
+Item {
+ id: root
+
+ property alias text: textFieldPassword.text
+ property alias placeholderText: textFieldPassword.placeholderText
+ property alias allowPasswordRecovery: buttonForgot.visible
+
+ signal passwordForgotten()
+
+ function forceTextFocus() {
+ textFieldPassword.forceActiveFocus()
+ }
+
+ function clear() {
+ textFieldPassword.clear()
+ }
+
+ implicitHeight: textFieldPassword.implicitHeight + buttonForgot.implicitHeight
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ TextField {
+ id: textFieldPassword
+
+ placeholderText: qsTr("Password")
+ selectByMouse: true
+ echoMode: TextField.Password
+ focus: true
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: true
+ }
+
+ Button {
+ id: buttonForgot
+ text: qsTr("I forgot my password")
+ flat: true
+ highlighted: hovered
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+ Layout.fillWidth: true
+
+ onClicked: {
+ passwordForgotten()
+ }
+ }
+ }
+}
diff --git a/src/ui/PageSend.qml b/src/ui/PageSend.qml
index da60bc82..57c0f47a 100644
--- a/src/ui/PageSend.qml
+++ b/src/ui/PageSend.qml
@@ -3,6 +3,7 @@ import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
import Transactions 1.0
+import Utils 1.0
// Resource imports
// import "qrc:/ui/src/ui/Dialogs"
@@ -35,33 +36,38 @@ Page {
var isEncrypted
var walletSelected
+ var walletSelecteds
if (advancedMode){
- var outs = stackView.currentItem.advancedPage.getSelectedOutputs()
- var addrs = stackView.currentItem.advancedPage.getSelectedAddresses()
- walletSelected = stackView.currentItem.advancedPage.getSelectedWallet()[0]
+ var outs = stackView.currentItem.advancedPage.getSelectedOutputsWithWallets()
+ var addrs = stackView.currentItem.advancedPage.getSelectedAddressesWithWallets()
+ //walletSelecteds = stackView.currentItem.advancedPage.getSelectedWallet()
var destinationSummary = stackView.currentItem.advancedPage.getDestinationsSummary()
var changeAddress = stackView.currentItem.advancedPage.getChangeAddress()
var automaticCoinHours = stackView.currentItem.advancedPage.getAutomaticCoinHours()
var burnFactor = stackView.currentItem.advancedPage.getBurnFactor()
- if (outs.length > 0){
+ if (outs[0].length > 0){
console.log(outs)
- txn = walletManager.sendFromOutputs(walletSelected, outs, destinationSummary[0], destinationSummary[1], destinationSummary[2], changeAddress, automaticCoinHours, burnFactor)
+ txn = walletManager.sendFromOutputs(outs[1], outs[0], destinationSummary[0], destinationSummary[1], destinationSummary[2], changeAddress, automaticCoinHours, burnFactor)
} else {
- if (addrs.length == 0){
- addrs = stackView.currentItem.advancedPage.getAllAddresses()
-
+ if (addrs[0].length == 0){
+ addrs = stackView.currentItem.advancedPage.getAllAddressesWithWallets()
}
- txn = walletManager.sendFromAddresses(walletSelected, addrs, destinationSummary[0], destinationSummary[1], destinationSummary[2], changeAddress, automaticCoinHours, burnFactor)
+ txn = walletManager.sendFromAddresses(addrs[1], addrs[0], destinationSummary[0], destinationSummary[1], destinationSummary[2], changeAddress, automaticCoinHours, burnFactor)
}
- isEncrypted = stackView.currentItem.advancedPage.walletIsEncrypted()[0]
+ isEncrypted = stackView.currentItem.advancedPage.walletIsEncrypted()
} else{
walletSelected = stackView.currentItem.simplePage.getSelectedWallet()
isEncrypted = stackView.currentItem.simplePage.walletIsEncrypted()
+ var addrs = []
+ addrs.push([])
+ addrs.push([])
+ addrs[0].push(stackView.currentItem.simplePage.getDestinationAddress())
+ addrs[1].push(walletSelected)
txn = walletManager.sendTo(walletSelected, stackView.currentItem.simplePage.getDestinationAddress(), stackView.currentItem.simplePage.getAmount())
}
console.log("HT "+txn.hoursTraspassed)
- dialogSendTransaction.showPasswordField = isEncrypted// get if the current wallet is encrypted
+ dialogSendTransaction.showPasswordField = false//isEncrypted// get if the current wallet is encrypted
//dialogSendTransaction.previewDate = "2019-02-26 15:27"
dialogSendTransaction.previewType = TransactionDetails.Type.Send
dialogSendTransaction.previewAmount = txn.amount
@@ -70,7 +76,7 @@ Page {
dialogSendTransaction.previewtransactionID = txn.transactionId
dialogSendTransaction.inputs = txn.inputs
dialogSendTransaction.outputs = txn.outputs
- dialogSendTransaction.wallet = walletSelected
+ dialogSendTransaction.walletsAddresses = addrs
dialogSendTransaction.open()
@@ -168,7 +174,7 @@ Page {
DialogSendTransaction {
id: dialogSendTransaction
anchors.centerIn: Overlay.overlay
- property string wallet
+ property var walletsAddresses
readonly property real maxHeight: (expanded ? 490 : 340) + (showPasswordField ? 140 : 0)
width: applicationWindow.width > 640 ? 640 - 40 : applicationWindow.width - 40
height: applicationWindow.height > maxHeight ? maxHeight - 40 : applicationWindow.height - 40
@@ -177,8 +183,34 @@ Page {
modal: true
focus: true
onAccepted: {
- var signedTxn = walletManager.signTxn(wallet,"", dialogSendTransaction.passwordText, [], txn)
- var injected = walletManager.broadcastTxn(signedTxn)
+ walletManager.signAndBroadcastTxnAsync(walletsAddresses[1], walletsAddresses[0],"", bridgeForPassword, [], txn)
+ //var signedTxn = walletManager.signTxn(walletsAddresses[1], walletsAddresses[0],"", bridgeForPassword, [], txn)
+ //var injected = walletManager.broadcastTxn(signedTxn)
}
}
+
+ DialogGetPassword{
+ id: getPasswordDialog
+ anchors.centerIn: Overlay.overlay
+ property int nAddress
+ width: applicationWindow.width > 540 ? 540 - 120 : applicationWindow.width - 40
+ height: applicationWindow.height > 570 ? 570 - 180 : applicationWindow.height - 40
+
+ focus: true
+ modal: true
+ onClosed:{
+ bridgeForPassword.setResult(getPasswordDialog.password)
+ bridgeForPassword.unlock()
+ }
+ }
+
+ QBridge{
+ id: bridgeForPassword
+
+ onGetPassword:{
+ getPasswordDialog.title = message
+ getPasswordDialog.clear()
+ getPasswordDialog.open()
+ }
+ }
}
diff --git a/src/ui/Settings.qml b/src/ui/Settings.qml
index fc193ce2..087abb7c 100644
--- a/src/ui/Settings.qml
+++ b/src/ui/Settings.qml
@@ -179,4 +179,4 @@ Page {
}
}
} // Dialog
-}
\ No newline at end of file
+}
diff --git a/src/ui/SubPageSendAdvanced.qml b/src/ui/SubPageSendAdvanced.qml
index 1564dcb2..6a32674a 100644
--- a/src/ui/SubPageSendAdvanced.qml
+++ b/src/ui/SubPageSendAdvanced.qml
@@ -51,20 +51,28 @@ Page {
minFeeAmount = valCH/10
}
- function getSelectedAddresses(){
+ function getSelectedAddressesWithWallets(){
var indexs = comboBoxWalletsAddressesSendFrom.getCheckedDelegates()
var addresses = []
+ addresses.push([])
+ addresses.push([])
for (var i =0;i< indexs.length; i++){
- addresses.push(comboBoxWalletsAddressesSendFrom.model.addresses[indexs[i]].address)
+ addresses[0].push(comboBoxWalletsAddressesSendFrom.model.addresses[indexs[i]].address)
+ addresses[1].push(comboBoxWalletsAddressesSendFrom.model.addresses[indexs[i]].walletId)
+ //addresses.push(comboBoxWalletsAddressesSendFrom.model.addresses[indexs[i]].address)
}
return addresses
}
- function getSelectedOutputs(){
+ function getSelectedOutputsWithWallets(){
var indexs = comboBoxWalletsUnspentOutputsSendFrom.getCheckedDelegates()
var outputs = []
+ outputs.push([])
+ outputs.push([])
for (var i =0;i< indexs.length; i++){
- outputs.push(comboBoxWalletsUnspentOutputsSendFrom.model.outputs[indexs[i]].outputID)
+ outputs[0].push(comboBoxWalletsUnspentOutputsSendFrom.model.outputs[indexs[i]].outputID)
+ outputs[1].push(comboBoxWalletsUnspentOutputsSendFrom.model.outputs[indexs[i]].walletOwner)
+ //outputs.push(comboBoxWalletsUnspentOutputsSendFrom.model.outputs[indexs[i]].outputID)
}
return outputs
}
@@ -83,7 +91,11 @@ Page {
var indexs = comboBoxWalletsSendFrom.getCheckedDelegates()
var enc = []
for (var i = 0; i < indexs.length; i++){
- enc.push(comboBoxWalletsSendFrom.model.wallets[indexs[i]].encryptionEnabled)
+ var walletEncrypted = []
+ walletEncrypted.push(comboBoxWalletsSendFrom.model.wallets[indexs[i]].fileName)
+ walletEncrypted.push(comboBoxWalletsSendFrom.model.wallets[indexs[i]].name)
+ walletEncrypted.push(comboBoxWalletsSendFrom.model.wallets[indexs[i]].encryptionEnabled)
+ enc.push(walletEncrypted)
}
return enc
}
@@ -111,10 +123,14 @@ Page {
return sliderCoinHoursShareFactor.value
}
- function getAllAddresses(){
+ function getAllAddressesWithWallets(){
var addrs = []
+ addrs.push([])
+ addrs.push([])
for (var i = 0; i < listAddresses.count; i++){
- addrs.push(listAddresses.addresses[i].address)
+ addrs[0].push(listAddresses.addresses[i].address)
+ addrs[1].push(listAddresses.addresses[i].walletId)
+ //addrs.push(listAddresses.addresses[i].address)
}
return addrs
}
diff --git a/src/ui/SubPageSendSimple.qml b/src/ui/SubPageSendSimple.qml
index 9eba2159..4dd974ff 100644
--- a/src/ui/SubPageSendSimple.qml
+++ b/src/ui/SubPageSendSimple.qml
@@ -11,6 +11,7 @@ import "Controls" // For quick UI development, switch back to resources when mak
Page {
id: root
property string walletSelected
+ property string walletSelectedName
property bool walletEncrypted: false
property string amount
property string destinationAddress
@@ -24,7 +25,7 @@ Page {
return destinationAddress
}
function walletIsEncrypted(){
- return walletEncrypted
+ return [walletSelected, walletSelectedName, walletEncrypted]
}
signal qrCodeRequested(var data)
@@ -73,6 +74,7 @@ Page {
onActivated: {
root.walletSelected = comboBoxWalletsSendFrom.model.wallets[comboBoxWalletsSendFrom.currentIndex].fileName
+ root.walletSelectedName = comboBoxWalletsSendFrom.model.wallets[comboBoxWalletsSendFrom.currentIndex].name
root.walletEncrypted = comboBoxWalletsSendFrom.model.wallets[comboBoxWalletsSendFrom.currentIndex].encryptionEnabled
}
} // ComboBox
diff --git a/src/ui/ToolButtonQR.qml b/src/ui/ToolButtonQR.qml
index db5fceec..d1761ead 100644
--- a/src/ui/ToolButtonQR.qml
+++ b/src/ui/ToolButtonQR.qml
@@ -13,4 +13,4 @@ ToolButton {
ToolTip.text: qsTr("Show QR code")
ToolTip.visible: hovered // TODO: pressed when mobile?
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
-}
\ No newline at end of file
+}
diff --git a/src/ui/Utils/QRCode.qml b/src/ui/Utils/QRCode.qml
index fd4d8d83..82f35494 100644
--- a/src/ui/Utils/QRCode.qml
+++ b/src/ui/Utils/QRCode.qml
@@ -48,4 +48,4 @@ Canvas {
onValueChanged : {
requestPaint()
}
-}
\ No newline at end of file
+}
diff --git a/src/ui/WalletSettings.qml b/src/ui/WalletSettings.qml
index 0eace3b3..208f014d 100644
--- a/src/ui/WalletSettings.qml
+++ b/src/ui/WalletSettings.qml
@@ -41,4 +41,4 @@ ColumnLayout {
checked: false
}
}
-}
\ No newline at end of file
+}
diff --git a/src/util/coin.go b/src/util/coin.go
index de13f0b4..bf3016d1 100644
--- a/src/util/coin.go
+++ b/src/util/coin.go
@@ -5,9 +5,10 @@ import (
"github.com/fibercrypto/fibercryptowallet/src/errors"
)
-func NewGenericOutput(addr core.Address) GenericOutput {
+func NewGenericOutput(addr core.Address, id string) GenericOutput {
return GenericOutput{
Address: addr,
+ id: id,
Balance: make(map[string]uint64),
}
}
@@ -16,11 +17,12 @@ func NewGenericOutput(addr core.Address) GenericOutput {
type GenericOutput struct {
Address core.Address
Balance map[string]uint64
+ id string
}
// GetId provides transaction output ID
func (gOut *GenericOutput) GetId() string {
- return ""
+ return gOut.id
}
// IsSpent determines whether there exists a confirmed transaction with an input spending this output
diff --git a/src/util/sign.go b/src/util/sign.go
index 398e7ea8..2fecebe2 100644
--- a/src/util/sign.go
+++ b/src/util/sign.go
@@ -50,7 +50,12 @@ func SignTransaction(signerID core.UID, txn core.Transaction, pwd core.PasswordR
if signer == nil {
return nil, errors.ErrInvalidID
}
- return signer.SignTransaction(txn, pwd, indices)
+ // Add signer ID in key value store context
+ pwdReader := func(msg string, ctx core.KeyValueStore) (string, error) {
+ newCtx := NewKeyValuesWithDefaults(NewMapWithSingleKey(core.StrSignerID, string(signerID)), ctx)
+ return pwd(msg, newCtx)
+ }
+ return signer.SignTransaction(txn, pwdReader, indices)
}
// GetSignerDescription human readable caption for signing strategy identified by UID
@@ -61,3 +66,58 @@ func GetSignerDescription(signerID core.UID) (string, error) {
}
return signer.GetSignerDescription(), nil
}
+
+// LookupSignServiceForWallet instantiate signing straegy identified by UID. Fall back to wallet if empty
+func LookupSignServiceForWallet(wlt core.Wallet, signerID core.UID) (core.TxnSigner, error) {
+ if signerID == "" {
+ wltSigner, isTxnSigner := wlt.(core.TxnSigner)
+ if !isTxnSigner {
+ logUtil.WithError(errors.ErrInvalidID).Errorf("Wallet %v can not sign transactions", wlt)
+ return nil, errors.ErrWalletCantSign
+ }
+ return wltSigner, nil
+ }
+ if signer := LookupSignService(signerID); signer != nil {
+ return signer, nil
+ }
+ return nil, errors.ErrInvalidID
+}
+
+type signingKeyPair struct {
+ wallet core.Wallet
+ signerID core.UID
+}
+
+// GenericMultiWalletSign generic strategy for using multiple wallets to sign a transaction
+func GenericMultiWalletSign(txn core.Transaction, signSpec []core.InputSignDescriptor, pwd core.PasswordReader) (signedTxn core.Transaction, err error) {
+ groups := make(map[signingKeyPair][]string)
+ // Aggregate inputs by wallet,signer combination
+ for _, descriptor := range signSpec {
+ key := signingKeyPair{
+ wallet: descriptor.Wallet,
+ signerID: descriptor.SignerID,
+ }
+ inputs, isNotEmpty := groups[key]
+ if !isNotEmpty {
+ inputs = []string{}
+ }
+ groups[key] = append(inputs, descriptor.InputIndex)
+ }
+ signedTxn = txn
+
+ for signPair, indices := range groups {
+
+ signer, err := LookupSignServiceForWallet(signPair.wallet, signPair.signerID)
+ if err != nil {
+ logUtil.WithError(err).Errorf("Unknown signer %s specified for signing inputs %v of wallet %v", string(signPair.signerID), indices, signPair.wallet)
+ return nil, errors.ErrInvalidID
+ }
+ signedTxn, err = signPair.wallet.Sign(signedTxn, signer, pwd, indices)
+ if err != nil {
+ logUtil.WithError(err).Errorf("Error signing inputs %v of wallet %v with signer %s", indices, signPair.wallet, string(signPair.signerID))
+ return nil, err
+ }
+
+ }
+ return signedTxn, nil
+}
diff --git a/src/util/storage.go b/src/util/storage.go
index fc494810..110fd85d 100644
--- a/src/util/storage.go
+++ b/src/util/storage.go
@@ -9,14 +9,17 @@ type KeyValueMap struct {
values map[string]interface{}
}
+// GetValue lookup value for key
func (tOpt *KeyValueMap) GetValue(key string) interface{} {
return tOpt.values[key]
}
+// SetValue bind value o known key
func (tOpt *KeyValueMap) SetValue(key string, value interface{}) {
tOpt.values[key] = value
}
+// NewKeyValueMap instantiate key value map storage
func NewKeyValueMap() *KeyValueMap {
tOptions := KeyValueMap{
values: make(map[string]interface{}),
@@ -24,7 +27,48 @@ func NewKeyValueMap() *KeyValueMap {
return &tOptions
}
+// NewKeyValueMap instantiate key value map storage
+func NewMapWithSingleKey(k string, v interface{}) *KeyValueMap {
+ store := NewKeyValueMap()
+ store.values[k] = v
+ return store
+}
+
+// KeyValuesWithDefaults retrieve and set values of another KeyValueStore with defaults for keys not found
+type KeyValuesWithDefaults struct {
+ values, defaults core.KeyValueStore
+}
+
+// GetValue lookup value for key
+func (tOpt *KeyValuesWithDefaults) GetValue(key string) interface{} {
+ v := tOpt.values.GetValue(key)
+ if v != nil {
+ return v
+ }
+ return tOpt.defaults.GetValue(key)
+}
+
+// SetValue bind value o known key
+func (tOpt *KeyValuesWithDefaults) SetValue(key string, value interface{}) {
+ tOpt.values.SetValue(key, value)
+}
+
+// NewKeyValuesWithDefaults instantiate key value map storage
+func NewKeyValuesWithDefaults(values, defaults core.KeyValueStore) core.KeyValueStore {
+ if defaults == nil {
+ return values
+ }
+ if values == nil {
+ values = NewKeyValueMap()
+ }
+ tOptions := KeyValuesWithDefaults{
+ values: values,
+ defaults: defaults,
+ }
+ return &tOptions
+}
+
// Type assertions
var (
- _ core.KeyValueStorage = &KeyValueMap{}
+ _ core.KeyValueStore = &KeyValueMap{}
)
diff --git a/src/util/storage_test.go b/src/util/storage_test.go
new file mode 100644
index 00000000..ab6cf1b8
--- /dev/null
+++ b/src/util/storage_test.go
@@ -0,0 +1,54 @@
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestKeyValuesNoDefaults(t *testing.T) {
+ m := NewKeyValueMap()
+ require.Equal(t, m, NewKeyValuesWithDefaults(m, nil))
+}
+
+func TestKeyValueiWithDefaultsGetValue(t *testing.T) {
+ // Values override defaults
+ m1 := NewMapWithSingleKey("k1", "v1")
+ m2 := NewMapWithSingleKey("k1", "v2")
+ s := NewKeyValuesWithDefaults(m1, m2)
+ require.Equal(t, "v1", m1.GetValue("k1"))
+ require.Equal(t, "v2", m2.GetValue("k1"))
+ require.Equal(t, "v1", s.GetValue("k1"))
+
+ // Not in values and not in defaults
+ require.Nil(t, m1.GetValue("z"))
+ require.Nil(t, m2.GetValue("z"))
+ require.Nil(t, s.GetValue("z"))
+
+ // Defaults returned if no value found
+ m2.SetValue("k2", "v3")
+ require.Nil(t, m1.GetValue("k2"))
+ require.Equal(t, "v3", m2.GetValue("k2"))
+ require.Equal(t, "v1", m1.GetValue("k1"))
+ require.Equal(t, "v2", m2.GetValue("k1"))
+ require.Nil(t, m1.GetValue("z"))
+ require.Nil(t, m2.GetValue("z"))
+ require.Equal(t, "v3", s.GetValue("k2"))
+ require.Equal(t, "v1", s.GetValue("k1"))
+ require.Nil(t, s.GetValue("z"))
+
+ // Values returned even if no defaults found
+ m1.SetValue("k3", "v4")
+ require.Equal(t, "v4", m1.GetValue("k3"))
+ require.Nil(t, m2.GetValue("k3"))
+ require.Nil(t, m1.GetValue("k2"))
+ require.Equal(t, "v3", m2.GetValue("k2"))
+ require.Equal(t, "v1", m1.GetValue("k1"))
+ require.Equal(t, "v2", m2.GetValue("k1"))
+ require.Nil(t, m1.GetValue("z"))
+ require.Nil(t, m2.GetValue("z"))
+ require.Equal(t, "v4", s.GetValue("k3"))
+ require.Equal(t, "v3", s.GetValue("k2"))
+ require.Equal(t, "v1", s.GetValue("k1"))
+ require.Nil(t, s.GetValue("z"))
+}
diff --git a/src/util/textutil.go b/src/util/textutil.go
index aa8970b4..50fac68f 100644
--- a/src/util/textutil.go
+++ b/src/util/textutil.go
@@ -1,6 +1,17 @@
package util
+import (
+ "github.com/fibercrypto/fibercryptowallet/src/core"
+)
+
// EmptyPassword read no password
-func EmptyPassword(string) (string, error) {
+func EmptyPassword(string, core.KeyValueStore) (string, error) {
return "", nil
}
+
+// ConstantPassword always return same known password
+func ConstantPassword(pwdText string) core.PasswordReader {
+ return func(string, core.KeyValueStore) (string, error) {
+ return pwdText, nil
+ }
+}
diff --git a/src/util/util.go b/src/util/util.go
index c6d600ab..4f735a16 100644
--- a/src/util/util.go
+++ b/src/util/util.go
@@ -1,9 +1,12 @@
package util
import (
+ "github.com/fibercrypto/fibercryptowallet/src/util/logging"
"strconv"
)
+var logUtil = logging.MustGetLogger("FiberCrypto util")
+
func Min(a, b int) int {
if a <= b {
return a
diff --git a/src/util/wallet.go b/src/util/wallet.go
new file mode 100644
index 00000000..2504bef3
--- /dev/null
+++ b/src/util/wallet.go
@@ -0,0 +1,43 @@
+package util
+
+import (
+ "github.com/fibercrypto/fibercryptowallet/src/core"
+)
+
+// SimpleWalletOutput put together transacion output with originating wallet
+type SimpleWalletOutput struct {
+ Wallet core.Wallet
+ UxOut core.TransactionOutput
+}
+
+// GetWallet return wallet
+func (wo *SimpleWalletOutput) GetWallet() core.Wallet {
+ return wo.Wallet
+}
+
+// GetOutput return transaction output.
+func (wo *SimpleWalletOutput) GetOutput() core.TransactionOutput {
+ return wo.UxOut
+}
+
+// SimpleWalletAddress put together address with owner wallet
+type SimpleWalletAddress struct {
+ Wallet core.Wallet
+ UxOut core.Address
+}
+
+// GetWallet return wallet
+func (wa *SimpleWalletAddress) GetWallet() core.Wallet {
+ return wa.Wallet
+}
+
+// GetAddress return address
+func (wa *SimpleWalletAddress) GetAddress() core.Address {
+ return wa.UxOut
+}
+
+// Type assertions
+var (
+ _ core.WalletOutput = &SimpleWalletOutput{}
+ _ core.WalletAddress = &SimpleWalletAddress{}
+)
diff --git a/vendor/github.com/skycoin/skycoin/src/wallet/transaction.go b/vendor/github.com/skycoin/skycoin/src/wallet/transaction.go
index a92cdccc..f35b68b0 100644
--- a/vendor/github.com/skycoin/skycoin/src/wallet/transaction.go
+++ b/vendor/github.com/skycoin/skycoin/src/wallet/transaction.go
@@ -140,7 +140,7 @@ func SignTransaction(w Wallet, txn *coin.Transaction, signIndexes []int, uxOuts
}
if len(toSign) != len(addrs) {
- return nil, NewError(errors.New("Wallet cannot sign all requested inputs"))
+ return nil, NewError(errors.New(fmt.Sprintf("Wallet cannot sign %v %v all requested inputs %s Outs %v", addrs, toSign, w.GetEntries()[0].SkycoinAddress(), uxOuts)))
}
// Sign the selected inputs