From c276ddc09e53035752bce8669f3c460f8d748998 Mon Sep 17 00:00:00 2001 From: Luca Moser Date: Fri, 29 Mar 2019 09:55:03 +0100 Subject: [PATCH] require addresses to be 90 trytes long (#102) * require addresses to be 90 trytes long * adjusts readme and examples --- README.md | 6 +- api/.examples/api_examples_test.go | 8 +- .../find_transaction_objects_test.go | 5 +- api/integration/find_transactions_test.go | 2 +- api/integration/get_account_data_test.go | 6 +- api/integration/get_balances_test.go | 19 +-- .../get_bundles_from_addresses_test.go | 4 +- api/integration/get_inputs_test.go | 4 +- api/integration/get_new_address_test.go | 27 +--- api/integration/gocks/g_find_transactions.go | 1 + api/integration/is_address_used_test.go | 6 +- api/integration/prepare_transfers_test.go | 12 +- .../were_addresses_spent_from_test.go | 8 - api/iricalls.go | 7 +- api/types.go | 15 -- api/wrappers.go | 137 ++++++++---------- guards/.examples/guards_examples_test.go | 9 +- guards/guards.go | 5 + guards/validators/validator.go | 18 +++ 19 files changed, 137 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 3ec95b60b..a6cf87aac 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ const mwm = 14 // how many milestones back to start the random walk from const depth = 3 -// can be 90 trytes long (with checksum) +// must be 90 trytes long (with checksum) const recipientAddress = "BBBB....." func main() { @@ -140,6 +140,7 @@ func main() { // optionally define a message and tag transfers := bundle.Transfers{ { + // must be 90 trytes long (include the checksum) Address: recipientAddress, Value: 80, }, @@ -148,6 +149,7 @@ func main() { // create inputs for the transfer inputs := []Input{ { + // must be 90 trytes long (include the checksum) Address: "CCCCC....", Security: SecurityLevelMedium, KeyIndex: 0, @@ -158,7 +160,7 @@ func main() { // create an address for the remainder. // in this case we will have 20 iotas as the remainder, since we spend 100 from our input // address and only send 80 to the recipient. - remainderAddress, err := address.GenerateAddress(seed, 1, SecurityLevelMedium) + remainderAddress, err := address.GenerateAddress(seed, 1, SecurityLevelMedium, true) must(err) // we don't need to set the security level or timestamp in the options because we supply diff --git a/api/.examples/api_examples_test.go b/api/.examples/api_examples_test.go index 543455195..9d76e30ec 100644 --- a/api/.examples/api_examples_test.go +++ b/api/.examples/api_examples_test.go @@ -159,7 +159,7 @@ func ExampleFindTransactions() { // o: *Balances, The object describing the result of the balance query. // o: error, Returned for invalid addresses and internal errors. func ExampleGetBalances() { - balances, err := iotaAPI.GetBalances(trinary.Hashes{"DJDMZD9G9VMGR9UKMEYJWYRLUDEVWTPQJXIQAAXFGMXXSCONBGCJKVQQZPXFMVHAAPAGGBMDXESTZ9999"}, 100) + balances, err := iotaAPI.GetBalances(trinary.Hashes{"LWVVGCWMYKZGMBE9GOCB9J9QALRKWGAVISAEXEOM9NVCGJCCGSNBXXGYQDNZBXBWCEM9RMFHYBCSFWE9XEHAPSXHRY"}, 100) if err != nil { // handle error return @@ -271,7 +271,7 @@ func ExampleStoreTransactions() {} // o: []bool, The spent states of the addresses. // o: error, Returned for internal errors. func ExampleWereAddressesSpentFrom() { - spentStates, err := iotaAPI.WereAddressesSpentFrom("AETRKPXQNEK9GWM9ILSODEOZEFDDROCNKYQLWBDHWAEQJIGMSOJSETHNAMZOWDIVVMYPOPSFJRZYMDNRDQSGLFVZNY") + spentStates, err := iotaAPI.WereAddressesSpentFrom("LWVVGCWMYKZGMBE9GOCB9J9QALRKWGAVISAEXEOM9NVCGJCCGSNBXXGYQDNZBXBWCEM9RMFHYBCSFWE9XEHAPSXHRY") if err != nil { // handle error return @@ -415,6 +415,7 @@ func ExamplePrepareTransfers() { // optionally define a message and tag transfers := bundle.Transfers{ { + // must be 90 trytes long (inlcude the checksum) Address: "ASDEF...", Value: 80, }, @@ -423,6 +424,7 @@ func ExamplePrepareTransfers() { // create inputs for the transfer inputs := []api.Input{ { + // must be 90 trytes long (inlcude the checksum) Address: "BCEDFA...", Security: consts.SecurityLevelMedium, KeyIndex: 0, @@ -433,7 +435,7 @@ func ExamplePrepareTransfers() { // create an address for the remainder. // in this case we will have 20 iotas as the remainder, since we spend 100 from our input // address and only send 80 to the recipient. - remainderAddress, err := address.GenerateAddress(seed, 1, consts.SecurityLevelMedium) + remainderAddress, err := address.GenerateAddress(seed, 1, consts.SecurityLevelMedium, true) if err != nil { // handle error return diff --git a/api/integration/find_transaction_objects_test.go b/api/integration/find_transaction_objects_test.go index 44e09e598..b7ca5dab8 100644 --- a/api/integration/find_transaction_objects_test.go +++ b/api/integration/find_transaction_objects_test.go @@ -4,6 +4,7 @@ import ( . "github.com/iotaledger/iota.go/api" _ "github.com/iotaledger/iota.go/api/integration/gocks" . "github.com/iotaledger/iota.go/api/integration/samples" + "github.com/iotaledger/iota.go/checksum" . "github.com/iotaledger/iota.go/consts" . "github.com/iotaledger/iota.go/trinary" . "github.com/onsi/ginkgo" @@ -19,8 +20,10 @@ var _ = Describe("FindTransactionObjects()", func() { Context("call", func() { It("resolves to correct response", func() { + addrWithChecksum, err := checksum.AddChecksum(Bundle[0].Address, true, AddressChecksumTrytesSize) + Expect(err).ToNot(HaveOccurred()) txs, err := api.FindTransactionObjects(FindTransactionsQuery{ - Addresses: Hashes{Bundle[0].Address}, + Addresses: Hashes{addrWithChecksum}, }) Expect(err).ToNot(HaveOccurred()) Expect(txs[0]).To(Equal(Bundle[0])) diff --git a/api/integration/find_transactions_test.go b/api/integration/find_transactions_test.go index e3fc90f4c..161a1e8e3 100644 --- a/api/integration/find_transactions_test.go +++ b/api/integration/find_transactions_test.go @@ -24,7 +24,7 @@ var _ = Describe("FindTransactions()", func() { Context("address query", func() { It("resolves to correct response", func() { - hashes, err := api.FindTransactions(FindTransactionsQuery{Addresses: FindTransactionsByAddresses}) + hashes, err := api.FindTransactions(FindTransactionsQuery{Addresses: FindTransactionsByAddressesQuery}) Expect(err).ToNot(HaveOccurred()) Expect(hashes).To(Equal(expect)) }) diff --git a/api/integration/get_account_data_test.go b/api/integration/get_account_data_test.go index d5722ba25..12808a428 100644 --- a/api/integration/get_account_data_test.go +++ b/api/integration/get_account_data_test.go @@ -17,17 +17,17 @@ var _ = Describe("GetAccountData()", func() { } accountData := AccountData{ - Addresses: SampleAddresses, + Addresses: SampleAddressesWithChecksum, Transfers: Transfers, Inputs: []Input{ { - Address: SampleAddresses[2], + Address: SampleAddressesWithChecksum[2], Balance: 1, KeyIndex: 2, Security: SecurityLevelMedium, }, }, - LatestAddress: SampleAddresses[2], + LatestAddress: SampleAddressesWithChecksum[2], Transactions: nil, // txs addresses (which are 9s) never matched the seed's addresses Balance: 1, } diff --git a/api/integration/get_balances_test.go b/api/integration/get_balances_test.go index 8a62b8728..fd792daad 100644 --- a/api/integration/get_balances_test.go +++ b/api/integration/get_balances_test.go @@ -22,22 +22,7 @@ var _ = Describe("GetBalances()", func() { Context("call", func() { It("resolves to correct response", func() { - balances, err := api.GetBalances(SampleAddresses, 100) - Expect(err).ToNot(HaveOccurred()) - Expect(*balances).To(Equal(Balances{ - Balances: []uint64{99, 0, 1}, - Milestone: strings.Repeat("M", 81), - MilestoneIndex: 1, - })) - }) - - It("removes the checksum from the addresses", func() { - withChecksums := make(Hashes, len(SampleAddresses)) - for i := range withChecksums { - withChecksums[i] = SampleAddresses[i] + strings.Repeat("9", 9) - } - - balances, err := api.GetBalances(withChecksums, 100) + balances, err := api.GetBalances(SampleAddressesWithChecksum, 100) Expect(err).ToNot(HaveOccurred()) Expect(*balances).To(Equal(Balances{ Balances: []uint64{99, 0, 1}, @@ -54,7 +39,7 @@ var _ = Describe("GetBalances()", func() { }) It("returns an error for invalid threshold", func() { - _, err := api.GetBalances(Hashes{SampleAddresses[0]}, 101) + _, err := api.GetBalances(Hashes{SampleAddressesWithChecksum[0]}, 101) Expect(errors.Cause(err)).To(Equal(ErrInvalidThreshold)) }) }) diff --git a/api/integration/get_bundles_from_addresses_test.go b/api/integration/get_bundles_from_addresses_test.go index e0d18f0c2..0d22fed6d 100644 --- a/api/integration/get_bundles_from_addresses_test.go +++ b/api/integration/get_bundles_from_addresses_test.go @@ -18,8 +18,8 @@ var _ = Describe("GetBundlesFromAddresses()", func() { Context("call", func() { It("resolves to correct response", func() { - addresses := make(Hashes, len(SampleAddresses)) - copy(addresses, SampleAddresses) + addresses := make(Hashes, len(SampleAddressesWithChecksum)) + copy(addresses, SampleAddressesWithChecksum) bndls, err := api.GetBundlesFromAddresses(addresses, true) Expect(err).ToNot(HaveOccurred()) Expect(len(bndls)).To(Equal(2)) diff --git a/api/integration/get_inputs_test.go b/api/integration/get_inputs_test.go index 4eaf21e77..c153318f3 100644 --- a/api/integration/get_inputs_test.go +++ b/api/integration/get_inputs_test.go @@ -19,13 +19,13 @@ var _ = Describe("GetInputs()", func() { var inputs = Inputs{ Inputs: []Input{ { - Address: SampleAddresses[0], + Address: SampleAddressesWithChecksum[0], Balance: 99, KeyIndex: 0, Security: SecurityLevelMedium, }, { - Address: SampleAddresses[2], + Address: SampleAddressesWithChecksum[2], Balance: 1, KeyIndex: 2, Security: SecurityLevelMedium, diff --git a/api/integration/get_new_address_test.go b/api/integration/get_new_address_test.go index c87057f23..b81e45a22 100644 --- a/api/integration/get_new_address_test.go +++ b/api/integration/get_new_address_test.go @@ -21,15 +21,15 @@ var _ = Describe("GetNewAddress()", func() { addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 0}) Expect(err).ToNot(HaveOccurred()) // third address because previous ones are spent or have transactions - Expect(addresses[0]).To(Equal(SampleAddresses[2])) + Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[2])) }) It("resolves to correct addresses with total option", func() { var total uint64 = 2 addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 0, Total: &total}) Expect(err).ToNot(HaveOccurred()) - Expect(addresses[0]).To(Equal(SampleAddresses[0])) - Expect(addresses[1]).To(Equal(SampleAddresses[1])) + Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[0])) + Expect(addresses[1]).To(Equal(SampleAddressesWithChecksum[1])) }) It("resolves to correct addresses with return all option", func() { @@ -37,25 +37,12 @@ var _ = Describe("GetNewAddress()", func() { Expect(err).ToNot(HaveOccurred()) // index 1 has transactions, 2 is new Expect(len(addresses)).To(Equal(2)) - Expect(addresses[0]).To(Equal(SampleAddresses[1])) - Expect(addresses[1]).To(Equal(SampleAddresses[2])) - }) - - It("resolves to correct addresses with return checksum option", func() { - addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 0, Checksum: true}) - Expect(err).ToNot(HaveOccurred()) - Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[2])) - }) - - It("resolves to correct addresses with return checksum and total option", func() { - addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 0, ReturnAll: true, Checksum: true}) - Expect(err).ToNot(HaveOccurred()) - Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[0])) - Expect(addresses[1]).To(Equal(SampleAddressesWithChecksum[1])) + Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[1])) + Expect(addresses[1]).To(Equal(SampleAddressesWithChecksum[2])) }) - It("resolves to correct addresses with return checksum and total option from different index", func() { - addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 1, ReturnAll: true, Checksum: true}) + It("resolves to correct addresses with total option from different index", func() { + addresses, err := api.GetNewAddress(Seed, GetNewAddressOptions{Index: 1, ReturnAll: true}) Expect(err).ToNot(HaveOccurred()) Expect(addresses[0]).To(Equal(SampleAddressesWithChecksum[1])) }) diff --git a/api/integration/gocks/g_find_transactions.go b/api/integration/gocks/g_find_transactions.go index f480d91e3..ab17efa30 100644 --- a/api/integration/gocks/g_find_transactions.go +++ b/api/integration/gocks/g_find_transactions.go @@ -8,6 +8,7 @@ import ( "strings" ) +var FindTransactionsByAddressesQuery = Hashes{"VIWGTBNSFOZDBZYRUMSFGHUJYURQHNYQMYVWGQOBNONDZRFJG9VQTAHPBMTWEEMRYIMQFRAC9VYBOLJVDBPTIELAWD"} var FindTransactionsByAddresses = Hashes{"VIWGTBNSFOZDBZYRUMSFGHUJYURQHNYQMYVWGQOBNONDZRFJG9VQTAHPBMTWEEMRYIMQFRAC9VYBOLJVD"} var FindTransactionsByBundles = DefaultHashes() var FindTransactionsByTags = []Trytes{strings.Repeat("A", 27), strings.Repeat("B", 27)} diff --git a/api/integration/is_address_used_test.go b/api/integration/is_address_used_test.go index e67129464..967c2b993 100644 --- a/api/integration/is_address_used_test.go +++ b/api/integration/is_address_used_test.go @@ -15,19 +15,19 @@ var _ = Describe("IsAddressUsed()", func() { } It("returns true for spent address", func() { - used, err := api.IsAddressUsed(SampleAddresses[0]) + used, err := api.IsAddressUsed(SampleAddressesWithChecksum[0]) Expect(err).ToNot(HaveOccurred()) Expect(used).To(BeTrue()) }) It("returns true for address with transactions", func() { - used, err := api.IsAddressUsed(SampleAddresses[1]) + used, err := api.IsAddressUsed(SampleAddressesWithChecksum[1]) Expect(err).ToNot(HaveOccurred()) Expect(used).To(BeTrue()) }) It("returns false for unused address", func() { - used, err := api.IsAddressUsed(SampleAddresses[2]) + used, err := api.IsAddressUsed(SampleAddressesWithChecksum[2]) Expect(err).ToNot(HaveOccurred()) Expect(used).To(BeFalse()) }) diff --git a/api/integration/prepare_transfers_test.go b/api/integration/prepare_transfers_test.go index 695a9b176..416d76a79 100644 --- a/api/integration/prepare_transfers_test.go +++ b/api/integration/prepare_transfers_test.go @@ -22,13 +22,13 @@ var _ = Describe("PrepareTransfers()", func() { inputs := []Input{ { - Address: "PERXVBEYBJFPNEVPJNTCLWTDVOTEFWVGKVHTGKEOYRTZWYTPXGJJGZZZ9MQMHUNYDKDNUIBWINWB9JQLD", + Address: SampleAddressesWithChecksum[0], KeyIndex: 0, Security: 2, Balance: 3, }, { - Address: "VIWGTBNSFOZDBZYRUMSFGHUJYURQHNYQMYVWGQOBNONDZRFJG9VQTAHPBMTWEEMRYIMQFRAC9VYBOLJVD", + Address: SampleAddressesWithChecksum[1], KeyIndex: 1, Security: 2, Balance: 4, @@ -58,9 +58,13 @@ var _ = Describe("PrepareTransfers()", func() { }, } + targetAddr, err := checksum.AddChecksum("OHXRRYM9XAOOXBLWIFWSMMDUYSRVRK9RWHPMNRFDTKUYZWENMPGHPHKBECU9HRJMAYSQM9JRAS9CTGWBN", true, AddressChecksumTrytesSize) + if err != nil { + panic(err) + } zeroValueTransfer := bundle.Transfers{ { - Address: "OHXRRYM9XAOOXBLWIFWSMMDUYSRVRK9RWHPMNRFDTKUYZWENMPGHPHKBECU9HRJMAYSQM9JRAS9CTGWBN", + Address: targetAddr, Value: 0, Tag: "DJBETBPXOIKY", Message: "K9X", @@ -71,7 +75,7 @@ var _ = Describe("PrepareTransfers()", func() { "K9X999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999OHXRRYM9XAOOXBLWIFWSMMDUYSRVRK9RWHPMNRFDTKUYZWENMPGHPHKBECU9HRJMAYSQM9JRAS9CTGWBN999999999999999999999999999NNCETBPXOIKY999999999999999T9XRIZD99999999999999999999VRBLQHKIUGAFFPBTZROLCDHHCOVPXCJNFBRSNZIOJCCHVZNBSKD99LUOMV9AKLWIKCGBY9UZWCBNPLWYC999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999DJBETBPXOIKY999999999999999999999999999999999999999999999999999999999999999999999", } - remainderAddress := SampleAddresses[2] + remainderAddress := SampleAddressesWithChecksum[2] // necessary so that the output is going to be the same no matter when it is run timestamp := uint64(1522219924) diff --git a/api/integration/were_addresses_spent_from_test.go b/api/integration/were_addresses_spent_from_test.go index 2a39a6033..8e4348878 100644 --- a/api/integration/were_addresses_spent_from_test.go +++ b/api/integration/were_addresses_spent_from_test.go @@ -18,14 +18,6 @@ var _ = Describe("WereAddressesSpentFrom()", func() { Context("call", func() { It("resolves to correct response", func() { - spent, err := api.WereAddressesSpentFrom(SampleAddresses...) - Expect(err).ToNot(HaveOccurred()) - Expect(spent[0]).To(BeTrue()) - Expect(spent[1]).To(BeFalse()) - Expect(spent[2]).To(BeFalse()) - }) - - It("removes checksum from addresses", func() { spent, err := api.WereAddressesSpentFrom(SampleAddressesWithChecksum...) Expect(err).ToNot(HaveOccurred()) Expect(spent[0]).To(BeTrue()) diff --git a/api/iricalls.go b/api/iricalls.go index 4aa5bbd47..8572f5389 100644 --- a/api/iricalls.go +++ b/api/iricalls.go @@ -104,7 +104,7 @@ func (api *API) CheckConsistency(hashes ...Hash) (bool, string, error) { func validateFindTransactions(query *FindTransactionsQuery) error { if err := Validate( - ValidateHashes(query.Addresses...), + ValidateAddresses(false, query.Addresses...), ValidateHashes(query.Bundles...), ValidateTransactionHashes(query.Approvees...), ValidateTags(query.Tags...), @@ -160,7 +160,7 @@ func (api *API) FindTransactions(query FindTransactionsQuery) (Hashes, error) { // GetBalances fetches confirmed balances of the given addresses at the latest solid milestone. func (api *API) GetBalances(addresses Hashes, threshold uint64, tips ...Hash) (*Balances, error) { - if err := Validate(ValidateHashes(addresses...)); err != nil { + if err := Validate(ValidateAddresses(false, addresses...)); err != nil { return nil, err } @@ -337,8 +337,7 @@ func (api *API) StoreTransactions(trytes ...Trytes) ([]Trytes, error) { // WereAddressesSpentFrom checks whether the given addresses were already spent. func (api *API) WereAddressesSpentFrom(addresses ...Hash) ([]bool, error) { if err := Validate( - ValidateNonEmptyStrings(ErrInvalidHash, addresses...), - ValidateHashes(addresses...), + ValidateAddresses(false, addresses...), ); err != nil { return nil, err } diff --git a/api/types.go b/api/types.go index d4226485d..9715dd8c6 100644 --- a/api/types.go +++ b/api/types.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/iotaledger/iota.go/bundle" . "github.com/iotaledger/iota.go/consts" - "github.com/iotaledger/iota.go/transaction" . "github.com/iotaledger/iota.go/trinary" "time" ) @@ -91,8 +90,6 @@ type GetNewAddressOptions struct { Index uint64 // The security level used for generating new addresses. Security SecurityLevel - // Whether to append the checksum to the generated addresses. - Checksum bool // The total amount of addresses to generate. Total *uint64 // Whether to return all generated addresses and not just the new address. @@ -199,18 +196,6 @@ type SendTransfersOptions struct { Reference *Hash } -type prepareTransferProps struct { - Transactions transaction.Transactions - Trytes []Trytes - Transfers bundle.Transfers - Seed Trytes - Security SecurityLevel - Inputs []Input - Timestamp uint64 - RemainderAddress *Trytes - HMACKey *Trytes -} - func getPrepareTransfersDefaultOptions(options PrepareTransfersOptions) PrepareTransfersOptions { if options.Security == 0 { options.Security = SecurityLevelMedium diff --git a/api/wrappers.go b/api/wrappers.go index 7694c26e2..6ba449429 100644 --- a/api/wrappers.go +++ b/api/wrappers.go @@ -9,7 +9,6 @@ import ( "github.com/iotaledger/iota.go/signing" "github.com/iotaledger/iota.go/transaction" . "github.com/iotaledger/iota.go/trinary" - "math" "sort" "sync" "time" @@ -263,15 +262,11 @@ func (api *API) GetNewAddress(seed Trytes, options GetNewAddressOptions) (Hashes return nil, ErrInvalidTotalOption } total := *options.Total - addresses, err = address.GenerateAddresses(seed, index, total, securityLvl) + addresses, err = address.GenerateAddresses(seed, index, total, securityLvl, true) } else { addresses, err = getUntilFirstUnusedAddress(api.IsAddressUsed, seed, index, securityLvl, options.ReturnAll) } - if options.Checksum { - addresses, err = checksum.AddChecksums(addresses, true, AddressChecksumTrytesSize) - } - return addresses, err } @@ -313,7 +308,7 @@ func getUntilFirstUnusedAddress( addresses := Hashes{} for ; ; index++ { - nextAddress, err := address.GenerateAddress(seed, index, security) + nextAddress, err := address.GenerateAddress(seed, index, security, true) if err != nil { return nil, err } @@ -487,56 +482,53 @@ func isAboveMaxDepth(attachmentTimestamp int64) bool { // PrepareTransfers prepares the transaction trytes by generating a bundle, filling in transfers and inputs, // adding remainder and signing all input transactions. -func (api *API) PrepareTransfers(seed Trytes, transfers bundle.Transfers, options PrepareTransfersOptions) ([]Trytes, error) { - options = getPrepareTransfersDefaultOptions(options) +func (api *API) PrepareTransfers(seed Trytes, transfers bundle.Transfers, opts PrepareTransfersOptions) ([]Trytes, error) { + opts = getPrepareTransfersDefaultOptions(opts) - if err := Validate(ValidateSeed(seed), ValidateSecurityLevel(options.Security)); err != nil { + if err := Validate(ValidateSeed(seed), ValidateSecurityLevel(opts.Security)); err != nil { return nil, err } - if options.RemainderAddress != nil { - if err := Validate(ValidateHashes(*options.RemainderAddress)); err != nil { - return nil, ErrInvalidRemainderAddress + for i := range transfers { + if err := Validate(ValidateAddresses(transfers[i].Value != 0, transfers[i].Address)); err != nil { + return nil, err } } - props := prepareTransferProps{ - Seed: seed, Security: options.Security, Inputs: options.Inputs, - Transfers: transfers, Transactions: transaction.Transactions{}, - Trytes: []Trytes{}, RemainderAddress: options.RemainderAddress, - } + var timestamp uint64 + txs := transaction.Transactions{} - if options.Timestamp != nil { - props.Timestamp = *options.Timestamp + if opts.Timestamp != nil { + timestamp = *opts.Timestamp } else { - props.Timestamp = uint64(time.Now().UnixNano() / int64(time.Second)) + timestamp = uint64(time.Now().UnixNano() / int64(time.Second)) } - var totalTransferValue uint64 + var totalOutput uint64 for i := range transfers { - totalTransferValue += transfers[i].Value + totalOutput += transfers[i].Value } // add transfers - outEntries, err := bundle.TransfersToBundleEntries(props.Timestamp, props.Transfers...) + outEntries, err := bundle.TransfersToBundleEntries(timestamp, transfers...) if err != nil { return nil, err } for i := range outEntries { - props.Transactions = bundle.AddEntry(props.Transactions, outEntries[i]) + txs = bundle.AddEntry(txs, outEntries[i]) } - // gather inputs if we have api value transfer but no inputs were specified. + // gather inputs if we have api value transfer but no inputs were given. // this would error out if the gathered inputs don't fulfill the threshold value - if totalTransferValue != 0 && len(props.Inputs) == 0 { - inputs, err := api.GetInputs(seed, GetInputsOptions{Security: props.Security, Threshold: &totalTransferValue}) + if totalOutput != 0 && len(opts.Inputs) == 0 { + inputs, err := api.GetInputs(seed, GetInputsOptions{Security: opts.Security, Threshold: &totalOutput}) if err != nil { return nil, err } // filter out inputs which are already spent - inputAddresses := make(Hashes, len(props.Inputs)) - for i := range props.Inputs { + inputAddresses := make(Hashes, len(opts.Inputs)) + for i := range opts.Inputs { inputAddresses[i] = inputs.Inputs[i].Address } @@ -550,95 +542,88 @@ func (api *API) PrepareTransfers(seed Trytes, transfers bundle.Transfers, option } } - props.Inputs = inputs.Inputs + opts.Inputs = inputs.Inputs } // add input transactions - var inputsTotal uint64 - for i := range props.Inputs { - inputsTotal += props.Inputs[i].Balance - input := &props.Inputs[i] - addr, err := checksum.RemoveChecksum(input.Address) - if err != nil { + var totalInput uint64 + for i := range opts.Inputs { + if err := Validate(ValidateAddresses(opts.Inputs[i].Balance != 0, opts.Inputs[i].Address)); err != nil { return nil, err } + totalInput += opts.Inputs[i].Balance + input := &opts.Inputs[i] bndlEntry := bundle.BundleEntry{ - Address: addr, + Address: input.Address[:HashTrytesSize], Value: -int64(input.Balance), Length: uint64(input.Security), - Timestamp: props.Timestamp, + Timestamp: timestamp, } - props.Transactions = bundle.AddEntry(props.Transactions, bndlEntry) + txs = bundle.AddEntry(txs, bndlEntry) } // verify whether provided inputs fulfill threshold value - if inputsTotal < totalTransferValue { + if totalInput < totalOutput { return nil, ErrInsufficientBalance } // compute remainder - var remainder int64 - for i := range props.Transactions { - remainder += props.Transactions[i].Value - } + remainder := totalInput - totalOutput + // add remainder transaction if there's a remainder if remainder > 0 { - return nil, ErrInsufficientBalance - } - - // add remainder transaction if there's api remainder - if remainder != 0 { // compute new remainder address if non supplied - if totalTransferValue > 0 && props.RemainderAddress == nil { - remainderAddressKeyIndex := props.Inputs[0].KeyIndex - for i := range props.Inputs { - keyIndex := props.Inputs[i].KeyIndex + if opts.RemainderAddress == nil { + remainderAddressKeyIndex := opts.Inputs[0].KeyIndex + for i := range opts.Inputs { + keyIndex := opts.Inputs[i].KeyIndex if keyIndex > remainderAddressKeyIndex { remainderAddressKeyIndex = keyIndex } } remainderAddressKeyIndex++ - addrs, err := api.GetNewAddress(seed, GetNewAddressOptions{Security: props.Security, Index: remainderAddressKeyIndex}) + addrs, err := api.GetNewAddress(seed, GetNewAddressOptions{Security: opts.Security, Index: remainderAddressKeyIndex}) if err != nil { return nil, err } - props.RemainderAddress = &addrs[0] + opts.RemainderAddress = &addrs[0] } else { + if err := Validate(ValidateAddresses(true, *opts.RemainderAddress)); err != nil { + return nil, ErrInvalidRemainderAddress + } // make sure to remove checksum from remainder address - cleanedAddr, err := checksum.RemoveChecksum(*props.RemainderAddress) + cleanedAddr, err := checksum.RemoveChecksum(*opts.RemainderAddress) if err != nil { return nil, err } - props.RemainderAddress = &cleanedAddr + opts.RemainderAddress = &cleanedAddr } // add remainder transaction - if totalTransferValue > 0 { - props.Transactions = bundle.AddEntry(props.Transactions, bundle.BundleEntry{ - Address: *props.RemainderAddress, - Length: 1, Timestamp: props.Timestamp, - Value: int64(math.Abs(float64(remainder))), - }) - } + txs = bundle.AddEntry(txs, bundle.BundleEntry{ + Address: (*opts.RemainderAddress)[:HashTrytesSize], + Length: 1, Timestamp: timestamp, + Value: int64(remainder), + }) } // verify that input txs don't send to the same address - for i := range props.Transactions { - tx := &props.Transactions[i] + for i := range txs { + tx := &txs[i] // only check output txs if tx.Value <= 0 { continue } // check whether any input uses the same address as the output tx - for j := range props.Inputs { - if props.Inputs[j].Address == tx.Address { + for j := range opts.Inputs { + if opts.Inputs[j].Address == tx.Address { return nil, ErrSendingBackToInputs } } } // finalize bundle by adding the bundle hash - finalizedBundle, err := bundle.Finalize(props.Transactions) + finalizedBundle, err := bundle.Finalize(txs) if err != nil { return nil, err } @@ -647,8 +632,8 @@ func (api *API) PrepareTransfers(seed Trytes, transfers bundle.Transfers, option normalizedBundleHash := signing.NormalizedBundleHash(finalizedBundle[0].Bundle) signedFrags := []Trytes{} - for i := range props.Inputs { - input := &props.Inputs[i] + for i := range opts.Inputs { + input := &opts.Inputs[i] subseed, err := signing.Subseed(seed, input.KeyIndex) if err != nil { return nil, err @@ -682,17 +667,17 @@ func (api *API) PrepareTransfers(seed Trytes, transfers bundle.Transfers, option // add signed fragments to txs var indexFirstInputTx int - for i := range props.Transactions { - if props.Transactions[i].Value < 0 { + for i := range txs { + if txs[i].Value < 0 { indexFirstInputTx = i break } } - props.Transactions = bundle.AddTrytes(props.Transactions, signedFrags, indexFirstInputTx) + txs = bundle.AddTrytes(txs, signedFrags, indexFirstInputTx) // finally return built up txs as raw trytes - return transaction.MustFinalTransactionTrytes(props.Transactions), nil + return transaction.MustFinalTransactionTrytes(txs), nil } // SendTransfer calls PrepareTransfers and then sends off the bundle via SendTrytes. diff --git a/guards/.examples/guards_examples_test.go b/guards/.examples/guards_examples_test.go index 5210f4e0b..c4bad5972 100644 --- a/guards/.examples/guards_examples_test.go +++ b/guards/.examples/guards_examples_test.go @@ -37,10 +37,17 @@ func ExampleIsEmptyTrytes() { // i req: trytes, The Trytes to check. // o: bool, Whether it passes the check. func ExampleIsHash() { - hash := "ZFPPXWSTIYJCPPMVCCBZR9TISFJALXEXVYMADGTERQLTHAZJMHGWWFIXVCVPJRBUYLKMTLLKMTWMA9999 " + hash := "ZFPPXWSTIYJCPPMVCCBZR9TISFJALXEXVYMADGTERQLTHAZJMHGWWFIXVCVPJRBUYLKMTLLKMTWMA9999" fmt.Println(guards.IsHash(hash)) // output: true } +// i req: addr, The Trytes to check. +// o: bool, Whether it passes the check. +func ExampleIsAddressWithChecksum() { + addr := "ORQMMP9NVIHMPAOGRLTGIYJEKPNWOKQQKRTBFZ9HVYK9YFCGETUMEAPDSWDVADPYAVZAUIWYMQMUQABIDERPEHEHOZ" + fmt.Println(guards.IsAddressWithChecksum(addr)) // output: true +} + // i req: trytes, The Trytes to check. // o: bool, Whether it passes the check. func ExampleIsTransactionHash() { diff --git a/guards/guards.go b/guards/guards.go index 0126f6260..4470db8da 100644 --- a/guards/guards.go +++ b/guards/guards.go @@ -48,6 +48,11 @@ func IsHash(trytes Trytes) bool { return IsTrytesOfExactLength(trytes, HashTrytesSize) || IsTrytesOfExactLength(trytes, AddressWithChecksumTrytesSize) } +// IsAddressWithChecksum checks if the given address is exactly 90 trytes long. +func IsAddressWithChecksum(addr Trytes) bool { + return IsTrytesOfExactLength(addr, AddressWithChecksumTrytesSize) +} + // IsTransactionHash checks whether the given trytes can be a transaction hash. func IsTransactionHash(trytes Trytes) bool { return IsTrytesOfExactLength(trytes, HashTrytesSize) diff --git a/guards/validators/validator.go b/guards/validators/validator.go index b8c142242..98add15a3 100644 --- a/guards/validators/validator.go +++ b/guards/validators/validator.go @@ -57,6 +57,24 @@ func ValidateHashes(hashes ...Hash) Validatable { } } +// ValidateAddresses validates the given addresses which must include the checksum. +func ValidateAddresses(checkLastTrit bool, addrs ...Hash) Validatable { + return func() error { + for i := range addrs { + if !IsAddressWithChecksum(addrs[i]) { + return errors.Wrapf(ErrInvalidHash, "%s at index %d (not length of 90 trytes)", addrs[i], i) + } + if checkLastTrit { + lastTrits := MustTrytesToTrits(string(addrs[i][80])) + if lastTrits[2] != 0 { + return errors.Wrapf(ErrInvalidHash, "%s at index %d (last trit non 0)", addrs[i], i) + } + } + } + return nil + } +} + // ValidateTransactionTrytes validates the given transaction trytes. func ValidateTransactionTrytes(trytes ...Trytes) Validatable { return func() error {