From 4ac4a61e2032ba5b6104e90952710e76933f5f6a Mon Sep 17 00:00:00 2001 From: Adam Babik Date: Mon, 9 Dec 2019 11:06:04 +0100 Subject: [PATCH] Move whisper to status-go monorepo (#1720) --- eth-node/bridge/geth/envelope.go | 2 +- eth-node/bridge/geth/envelope_error.go | 2 +- eth-node/bridge/geth/envelope_event.go | 2 +- eth-node/bridge/geth/filter.go | 2 +- eth-node/bridge/geth/mailserver_response.go | 2 +- eth-node/bridge/geth/node.go | 2 +- eth-node/bridge/geth/public_whisper_api.go | 2 +- eth-node/bridge/geth/syncevent_response.go | 2 +- eth-node/bridge/geth/syncmailrequest.go | 2 +- eth-node/bridge/geth/whisper.go | 2 +- eth-node/go.mod | 4 +- eth-node/go.sum | 3 + go.mod | 4 +- go.sum | 16 +- mailserver/cleaner_test.go | 2 +- mailserver/mailserver.go | 2 +- mailserver/mailserver_db.go | 3 +- mailserver/mailserver_db_leveldb.go | 2 +- mailserver/mailserver_db_postgres.go | 2 +- mailserver/mailserver_test.go | 2 +- node/node.go | 2 +- node/node_api_test.go | 2 +- node/status_node.go | 2 +- node/status_node_test.go | 2 +- params/config.go | 2 +- peers/peerpool_test.go | 2 +- peers/topicpool_test.go | 4 +- protocol/go.mod | 4 +- protocol/go.sum | 2 + protocol/message_processor_test.go | 2 +- protocol/messenger_test.go | 2 +- protocol/transport/whisper/filter_test.go | 2 +- services/shhext/api.go | 2 +- services/shhext/service_test.go | 4 +- t/benchmarks/mailserver_test.go | 2 +- t/benchmarks/messages_test.go | 2 +- t/benchmarks/utils_test.go | 2 +- t/e2e/node/manager_test.go | 2 +- t/e2e/suites.go | 2 +- t/e2e/whisper/whisper_mailbox_test.go | 2 +- t/e2e/whisper/whisper_test.go | 2 +- vendor/github.com/karalabe/usb/.travis.yml | 4 +- vendor/github.com/karalabe/usb/appveyor.yml | 4 +- .../eth-node/bridge/geth/envelope.go | 2 +- .../eth-node/bridge/geth/envelope_error.go | 2 +- .../eth-node/bridge/geth/envelope_event.go | 2 +- .../status-go/eth-node/bridge/geth/filter.go | 2 +- .../bridge/geth/mailserver_response.go | 2 +- .../status-go/eth-node/bridge/geth/node.go | 2 +- .../bridge/geth/public_whisper_api.go | 2 +- .../bridge/geth/syncevent_response.go | 2 +- .../eth-node/bridge/geth/syncmailrequest.go | 2 +- .../status-go/eth-node/bridge/geth/whisper.go | 2 +- .../status-im/status-go/protocol/go.mod | 4 +- .../status-im/status-go/protocol/go.sum | 2 + .../whisperv6 => status-go/whisper}/api.go | 2 +- .../whisperv6 => status-go/whisper}/config.go | 2 +- .../whisperv6 => status-go/whisper}/doc.go | 2 +- .../whisper}/envelope.go | 2 +- .../whisperv6 => status-go/whisper}/events.go | 3 +- .../whisperv6 => status-go/whisper}/filter.go | 2 +- .../whisperv6 => status-go/whisper}/fuzz.go | 2 +- .../whisper}/gen_criteria_json.go | 2 +- .../whisper}/gen_message_json.go | 2 +- .../whisper}/gen_newmessage_json.go | 2 +- .../status-im/status-go/whisper/go.mod | 17 + .../status-im/status-go/whisper/go.sum | 134 ++ .../whisper}/mailserver_response.go | 2 +- .../whisper}/message.go | 2 +- .../status-im/status-go/whisper/metrics.go | 21 + .../whisperv6 => status-go/whisper}/peer.go | 2 +- .../whisper}/rate_limiter.go | 30 +- .../whisperv6 => status-go/whisper}/topic.go | 2 +- .../whisper}/whisper.go | 36 +- .../status-im/whisper/whisperv6/metrics.go | 50 - vendor/golang.org/x/crypto/blowfish/block.go | 159 -- vendor/golang.org/x/crypto/blowfish/cipher.go | 99 - vendor/golang.org/x/crypto/blowfish/const.go | 199 -- vendor/modules.txt | 4 +- whisper/api.go | 604 ++++++ whisper/api_test.go | 63 + whisper/benchmarks_test.go | 209 +++ whisper/config.go | 32 + whisper/doc.go | 261 +++ whisper/doc_test.go | 56 + whisper/envelope.go | 280 +++ whisper/envelope_test.go | 92 + whisper/events.go | 52 + whisper/filter.go | 294 +++ whisper/filter_test.go | 831 +++++++++ whisper/fuzz.go | 14 + whisper/gen_criteria_json.go | 66 + whisper/gen_message_json.go | 84 + whisper/gen_newmessage_json.go | 90 + whisper/go.mod | 17 + whisper/go.sum | 134 ++ whisper/mailserver_response.go | 140 ++ whisper/mailserver_response_test.go | 79 + whisper/message.go | 361 ++++ whisper/message_test.go | 495 +++++ whisper/metrics.go | 21 + whisper/peer.go | 295 +++ whisper/peer_test.go | 568 ++++++ whisper/rate_limiter.go | 174 ++ whisper/rate_limiter_test.go | 157 ++ whisper/topic.go | 57 + whisper/topic_test.go | 134 ++ whisper/whisper.go | 1587 ++++++++++++++++ whisper/whisper_test.go | 1646 +++++++++++++++++ 109 files changed, 9177 insertions(+), 636 deletions(-) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/api.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/config.go (98%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/doc.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/envelope.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/events.go (98%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/filter.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/fuzz.go (89%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/gen_criteria_json.go (98%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/gen_message_json.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/gen_newmessage_json.go (99%) create mode 100644 vendor/github.com/status-im/status-go/whisper/go.mod create mode 100644 vendor/github.com/status-im/status-go/whisper/go.sum rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/mailserver_response.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/message.go (99%) create mode 100644 vendor/github.com/status-im/status-go/whisper/metrics.go rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/peer.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/rate_limiter.go (86%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/topic.go (99%) rename vendor/github.com/status-im/{whisper/whisperv6 => status-go/whisper}/whisper.go (97%) delete mode 100644 vendor/github.com/status-im/whisper/whisperv6/metrics.go delete mode 100644 vendor/golang.org/x/crypto/blowfish/block.go delete mode 100644 vendor/golang.org/x/crypto/blowfish/cipher.go delete mode 100644 vendor/golang.org/x/crypto/blowfish/const.go create mode 100644 whisper/api.go create mode 100644 whisper/api_test.go create mode 100644 whisper/benchmarks_test.go create mode 100644 whisper/config.go create mode 100644 whisper/doc.go create mode 100644 whisper/doc_test.go create mode 100644 whisper/envelope.go create mode 100644 whisper/envelope_test.go create mode 100644 whisper/events.go create mode 100644 whisper/filter.go create mode 100644 whisper/filter_test.go create mode 100644 whisper/fuzz.go create mode 100644 whisper/gen_criteria_json.go create mode 100644 whisper/gen_message_json.go create mode 100644 whisper/gen_newmessage_json.go create mode 100644 whisper/go.mod create mode 100644 whisper/go.sum create mode 100644 whisper/mailserver_response.go create mode 100644 whisper/mailserver_response_test.go create mode 100644 whisper/message.go create mode 100644 whisper/message_test.go create mode 100644 whisper/metrics.go create mode 100644 whisper/peer.go create mode 100644 whisper/peer_test.go create mode 100644 whisper/rate_limiter.go create mode 100644 whisper/rate_limiter_test.go create mode 100644 whisper/topic.go create mode 100644 whisper/topic_test.go create mode 100644 whisper/whisper.go create mode 100644 whisper/whisper_test.go diff --git a/eth-node/bridge/geth/envelope.go b/eth-node/bridge/geth/envelope.go index 524de1074..9c7e738ea 100644 --- a/eth-node/bridge/geth/envelope.go +++ b/eth-node/bridge/geth/envelope.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethEnvelopeWrapper struct { diff --git a/eth-node/bridge/geth/envelope_error.go b/eth-node/bridge/geth/envelope_error.go index c3156d45d..a6364617e 100644 --- a/eth-node/bridge/geth/envelope_error.go +++ b/eth-node/bridge/geth/envelope_error.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError diff --git a/eth-node/bridge/geth/envelope_event.go b/eth-node/bridge/geth/envelope_event.go index 202f6275d..dc4b9b82d 100644 --- a/eth-node/bridge/geth/envelope_event.go +++ b/eth-node/bridge/geth/envelope_event.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent diff --git a/eth-node/bridge/geth/filter.go b/eth-node/bridge/geth/filter.go index 9033c4952..865c320c3 100644 --- a/eth-node/bridge/geth/filter.go +++ b/eth-node/bridge/geth/filter.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethFilterWrapper struct { diff --git a/eth-node/bridge/geth/mailserver_response.go b/eth-node/bridge/geth/mailserver_response.go index bb27ddeaf..4a4fd2ce2 100644 --- a/eth-node/bridge/geth/mailserver_response.go +++ b/eth-node/bridge/geth/mailserver_response.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse diff --git a/eth-node/bridge/geth/node.go b/eth-node/bridge/geth/node.go index 039869fa0..020f3c85d 100644 --- a/eth-node/bridge/geth/node.go +++ b/eth-node/bridge/geth/node.go @@ -5,7 +5,7 @@ import ( gethens "github.com/status-im/status-go/eth-node/bridge/geth/ens" "github.com/status-im/status-go/eth-node/types" enstypes "github.com/status-im/status-go/eth-node/types/ens" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "go.uber.org/zap" ) diff --git a/eth-node/bridge/geth/public_whisper_api.go b/eth-node/bridge/geth/public_whisper_api.go index b2d1edac9..99bc3689b 100644 --- a/eth-node/bridge/geth/public_whisper_api.go +++ b/eth-node/bridge/geth/public_whisper_api.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethPublicWhisperAPIWrapper struct { diff --git a/eth-node/bridge/geth/syncevent_response.go b/eth-node/bridge/geth/syncevent_response.go index 74edd592f..09eea583c 100644 --- a/eth-node/bridge/geth/syncevent_response.go +++ b/eth-node/bridge/geth/syncevent_response.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethSyncEventResponseWrapper returns a types.SyncEventResponse object that mimics Geth's SyncEventResponse diff --git a/eth-node/bridge/geth/syncmailrequest.go b/eth-node/bridge/geth/syncmailrequest.go index 6d1d6d01e..a8ad76b0b 100644 --- a/eth-node/bridge/geth/syncmailrequest.go +++ b/eth-node/bridge/geth/syncmailrequest.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // GetGethSyncMailRequestFrom converts a whisper SyncMailRequest struct from a SyncMailRequest struct diff --git a/eth-node/bridge/geth/whisper.go b/eth-node/bridge/geth/whisper.go index bafcd5ae5..009884f54 100644 --- a/eth-node/bridge/geth/whisper.go +++ b/eth-node/bridge/geth/whisper.go @@ -5,7 +5,7 @@ import ( "time" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethWhisperWrapper struct { diff --git a/eth-node/go.mod b/eth-node/go.mod index ba1ebd2e6..82d77f515 100644 --- a/eth-node/go.mod +++ b/eth-node/go.mod @@ -6,12 +6,14 @@ replace github.com/ethereum/go-ethereum v1.9.5 => github.com/status-im/go-ethere replace github.com/status-im/status-go/extkeys => ../extkeys +replace github.com/status-im/status-go/whisper => ../whisper + require ( github.com/ethereum/go-ethereum v1.9.5 github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f github.com/status-im/doubleratchet v3.0.0+incompatible github.com/status-im/status-go/extkeys v1.0.0 // indirect - github.com/status-im/whisper v1.5.2 + github.com/status-im/status-go/whisper v1.0.0 // indirect github.com/stretchr/testify v1.4.0 github.com/wealdtech/go-ens/v3 v3.0.9 go.uber.org/zap v1.13.0 diff --git a/eth-node/go.sum b/eth-node/go.sum index 839a22444..b6b32c70c 100644 --- a/eth-node/go.sum +++ b/eth-node/go.sum @@ -208,6 +208,7 @@ github.com/status-im/go-ethereum v1.9.5-status.6 h1:ytuTO1yBIAuTVRtRQoc2mrdyngtP github.com/status-im/go-ethereum v1.9.5-status.6/go.mod h1:08JvQWE+IOnAFSe4UD4ACLNe2fDd9XmWMCq5Yzy9mk0= github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 h1:ju5UTwk5Odtm4trrY+4Ca4RMj5OyXbmVeDAVad2T0Jw= github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/status-im/status-go v0.36.1 h1:nb9eTq0UQJ57YyTZSl5U05emFT+R4AW8/Bga6ocgOks= github.com/status-im/whisper v1.5.2 h1:26NgiKusmPic38eQdtXnaY+iaQ/LuQ3Dh0kCGYT/Uxs= github.com/status-im/whisper v1.5.2/go.mod h1:emrOxzJme0k66QtbbQ2bdd3P8RCdLZ8sTD7SkwH1s2s= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= @@ -217,6 +218,7 @@ github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUW github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -227,6 +229,7 @@ github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2z github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9/go.mod h1:EcGP24b8DY+bWHnpfJDP7fM+o8Nmz4fYH0l2xTtNr3I= github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/uber/jaeger-client-go v0.0.0-20180607151842-f7e0d4744fa6/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= diff --git a/go.mod b/go.mod index 9668ebadb..435899796 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,8 @@ replace github.com/status-im/status-go/extkeys => ./extkeys replace github.com/status-im/status-go/eth-node => ./eth-node +replace github.com/status-im/status-go/whisper => ./whisper + require ( github.com/beevik/ntp v0.2.0 github.com/elastic/gosigar v0.10.5 // indirect @@ -44,8 +46,8 @@ require ( github.com/status-im/status-go/eth-node v0.0.0-20191126161717-86bc127b3d0a github.com/status-im/status-go/extkeys v1.0.0 github.com/status-im/status-go/protocol v0.5.2 + github.com/status-im/status-go/whisper v1.0.0 github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 - github.com/status-im/whisper v1.6.2 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.0 go.uber.org/zap v1.13.0 diff --git a/go.sum b/go.sum index c7c7cc45e..474f26f07 100644 --- a/go.sum +++ b/go.sum @@ -28,14 +28,11 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v0.0.0-20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/allegro/bigcache v1.1.0 h1:MLuIKTjdxDc+qsG2rhjsYjsHQC5LUGjIWzutg7M+W68= -github.com/allegro/bigcache v1.1.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM= github.com/allegro/bigcache v1.2.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apilayer/freegeoip v3.5.0+incompatible/go.mod h1:CUfFqErhFhXneJendyQ/rRcuA8kH8JxHvYnbOozmlCU= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= -github.com/aristanetworks/goarista v0.0.0-20181002214814-33151c4543a7/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aristanetworks/goarista v0.0.0-20190219163901-728bce664cf5/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aristanetworks/goarista v0.0.0-20190502180301-283422fc1708 h1:tS7jSmwRqSxTnonTRlDD1oHo6Q9YOK4xHS9/v4L56eg= github.com/aristanetworks/goarista v0.0.0-20190502180301-283422fc1708/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= @@ -49,7 +46,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190418232430-6867ff32788a/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= @@ -506,13 +502,11 @@ github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8u github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= @@ -619,9 +613,6 @@ github.com/status-im/rendezvous v1.3.0 h1:7RK/MXXW+tlm0asKm1u7Qp7Yni6AO29a7j8+E4 github.com/status-im/rendezvous v1.3.0/go.mod h1:+hzjuP+j/XzLPeF6E50b88pWOTLdTcwjvNYt+Gh1W1s= github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 h1:oa0KU5jJRNtXaM/P465MhvSFo/HM2O8qi2DDuPcd7ro= github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501/go.mod h1:RYo/itke1oU5k/6sj9DNM3QAwtE5rZSgg5JnkOv83hk= -github.com/status-im/whisper v1.5.2/go.mod h1:emrOxzJme0k66QtbbQ2bdd3P8RCdLZ8sTD7SkwH1s2s= -github.com/status-im/whisper v1.6.2 h1:68WS0R2PzfM1VFKq/LW/hxijYHrqStfSlEoYOH8KZE4= -github.com/status-im/whisper v1.6.2/go.mod h1:uacoZIYbpf7iVk+YgFIuym6R0Qv2asjn6GoZEZ3dBLI= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= @@ -630,6 +621,8 @@ github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9C github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -696,7 +689,6 @@ golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90te golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/mailserver/cleaner_test.go b/mailserver/cleaner_test.go index cfcf09ad7..ed1169169 100644 --- a/mailserver/cleaner_test.go +++ b/mailserver/cleaner_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/require" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/storage" diff --git a/mailserver/mailserver.go b/mailserver/mailserver.go index 5e9d5bd9a..84db57140 100644 --- a/mailserver/mailserver.go +++ b/mailserver/mailserver.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" prom "github.com/prometheus/client_golang/prometheus" ) diff --git a/mailserver/mailserver_db.go b/mailserver/mailserver_db.go index 4d688c970..8107be1b6 100644 --- a/mailserver/mailserver_db.go +++ b/mailserver/mailserver_db.go @@ -1,8 +1,9 @@ package mailserver import ( - whisper "github.com/status-im/whisper/whisperv6" "time" + + "github.com/status-im/status-go/whisper" ) // DB is an interface to abstract interactions with the db so that the mailserver diff --git a/mailserver/mailserver_db_leveldb.go b/mailserver/mailserver_db_leveldb.go index 5bfd61712..da2eef1e5 100644 --- a/mailserver/mailserver_db_leveldb.go +++ b/mailserver/mailserver_db_leveldb.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/iterator" diff --git a/mailserver/mailserver_db_postgres.go b/mailserver/mailserver_db_postgres.go index 06c051958..e153c4821 100644 --- a/mailserver/mailserver_db_postgres.go +++ b/mailserver/mailserver_db_postgres.go @@ -16,7 +16,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) func NewPostgresDB(config *params.WhisperConfig) (*PostgresDB, error) { diff --git a/mailserver/mailserver_test.go b/mailserver/mailserver_test.go index 6ecbb41fb..fd6a3527e 100644 --- a/mailserver/mailserver_test.go +++ b/mailserver/mailserver_test.go @@ -34,7 +34,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" ) diff --git a/node/node.go b/node/node.go index f7d090ac2..9ab21912e 100644 --- a/node/node.go +++ b/node/node.go @@ -34,7 +34,7 @@ import ( "github.com/status-im/status-go/services/status" "github.com/status-im/status-go/static" "github.com/status-im/status-go/timesource" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/syndtr/goleveldb/leveldb" ) diff --git a/node/node_api_test.go b/node/node_api_test.go index 5a762ec79..7c0444b57 100644 --- a/node/node_api_test.go +++ b/node/node_api_test.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/status-im/status-go/params" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/require" ) diff --git a/node/status_node.go b/node/status_node.go index 280acc286..a6f2241b5 100644 --- a/node/status_node.go +++ b/node/status_node.go @@ -20,7 +20,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" ma "github.com/multiformats/go-multiaddr" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/syndtr/goleveldb/leveldb" "github.com/status-im/status-go/db" diff --git a/node/status_node_test.go b/node/status_node_test.go index 55ca5b64f..896a72a78 100644 --- a/node/status_node_test.go +++ b/node/status_node_test.go @@ -17,7 +17,7 @@ import ( gethnode "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/status-im/status-go/discovery" "github.com/status-im/status-go/params" diff --git a/params/config.go b/params/config.go index e277ef9e5..8248f3611 100644 --- a/params/config.go +++ b/params/config.go @@ -17,7 +17,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/params" "github.com/status-im/status-go/static" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" validator "gopkg.in/go-playground/validator.v9" ) diff --git a/peers/peerpool_test.go b/peers/peerpool_test.go index 3125f88c9..bf67954c4 100644 --- a/peers/peerpool_test.go +++ b/peers/peerpool_test.go @@ -25,7 +25,7 @@ import ( "github.com/status-im/status-go/discovery" "github.com/status-im/status-go/params" "github.com/status-im/status-go/signal" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type PeerPoolSimulationSuite struct { diff --git a/peers/topicpool_test.go b/peers/topicpool_test.go index 73b9ffa3b..c73a28688 100644 --- a/peers/topicpool_test.go +++ b/peers/topicpool_test.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/status-im/status-go/params" "github.com/status-im/status-go/t/helpers" - "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -389,7 +389,7 @@ func TestServerIgnoresInboundPeer(t *testing.T) { topicPool.running = 1 topicPool.maxCachedPeers = 0 - whisper := whisperv6.New(nil) + whisper := whisper.New(nil) srvkey, err := crypto.GenerateKey() require.NoError(t, err) server := &p2p.Server{ diff --git a/protocol/go.mod b/protocol/go.mod index 18a1b1461..ba29bd3fe 100644 --- a/protocol/go.mod +++ b/protocol/go.mod @@ -8,6 +8,8 @@ replace github.com/gomarkdown/markdown => github.com/status-im/markdown v0.0.0-2 replace github.com/status-im/status-go/eth-node => ../eth-node +replace github.com/status-im/status-go/whisper => ../whisper + require ( github.com/cenkalti/backoff/v3 v3.0.0 github.com/ethereum/go-ethereum v1.9.5 @@ -23,7 +25,7 @@ require ( github.com/status-im/doubleratchet v3.0.0+incompatible github.com/status-im/migrate/v4 v4.6.2-status.2 github.com/status-im/status-go/eth-node v0.0.0-20191120100713-5053b0b6835b - github.com/status-im/whisper v1.5.2 + github.com/status-im/status-go/whisper v1.0.0 github.com/stretchr/testify v1.4.0 github.com/vacp2p/mvds v0.0.23 go.uber.org/zap v1.13.0 diff --git a/protocol/go.sum b/protocol/go.sum index 5a57fb513..733f5000e 100644 --- a/protocol/go.sum +++ b/protocol/go.sum @@ -487,6 +487,7 @@ github.com/status-im/markdown v0.0.0-20191113114344-af599402d015/go.mod h1:tmG2b github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T60IS9z4/rYCCbI8= github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8= github.com/status-im/status-go v0.36.0 h1:91qDMJjHv+T3Li9FwxsWQ2JBVcYtvVDT0nGFSMnmM+8= +github.com/status-im/status-go v0.36.1 h1:nb9eTq0UQJ57YyTZSl5U05emFT+R4AW8/Bga6ocgOks= github.com/status-im/status-go/extkeys v1.0.0 h1:Qyirsoi5Ye5UFfisgPtCjPb/RkBxyK+UsSiEcr2PVlM= github.com/status-im/status-go/extkeys v1.0.0/go.mod h1:GdqJbrcpkNm5ZsSCpp+PdMxnXx+OcRBdm3PI0rs1FpU= github.com/status-im/whisper v1.5.2 h1:26NgiKusmPic38eQdtXnaY+iaQ/LuQ3Dh0kCGYT/Uxs= @@ -501,6 +502,7 @@ github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9C github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/protocol/message_processor_test.go b/protocol/message_processor_test.go index 11cb4082d..87c224406 100644 --- a/protocol/message_processor_test.go +++ b/protocol/message_processor_test.go @@ -21,7 +21,7 @@ import ( "github.com/status-im/status-go/protocol/sqlite" transport "github.com/status-im/status-go/protocol/transport/whisper" v1protocol "github.com/status-im/status-go/protocol/v1" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" datasyncproto "github.com/vacp2p/mvds/protobuf" ) diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index 212d9f494..7a137b886 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -21,7 +21,7 @@ import ( "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/sqlite" "github.com/status-im/status-go/protocol/tt" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" "go.uber.org/zap" ) diff --git a/protocol/transport/whisper/filter_test.go b/protocol/transport/whisper/filter_test.go index e617e24b7..8644449e1 100644 --- a/protocol/transport/whisper/filter_test.go +++ b/protocol/transport/whisper/filter_test.go @@ -14,7 +14,7 @@ import ( _ "github.com/mutecomm/go-sqlcipher" "github.com/status-im/status-go/eth-node/crypto" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" "go.uber.org/zap" ) diff --git a/services/shhext/api.go b/services/shhext/api.go index e3e6c38f1..93ec75d5c 100644 --- a/services/shhext/api.go +++ b/services/shhext/api.go @@ -18,7 +18,7 @@ import ( "github.com/status-im/status-go/mailserver" "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/shhext/mailservers" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" "github.com/status-im/status-go/eth-node/crypto" diff --git a/services/shhext/service_test.go b/services/shhext/service_test.go index 5ea39c39b..7a185e5fb 100644 --- a/services/shhext/service_test.go +++ b/services/shhext/service_test.go @@ -26,7 +26,7 @@ import ( "github.com/status-im/status-go/sqlite" "github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/utils" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/storage" @@ -469,7 +469,7 @@ func (s *RequestMessagesSyncSuite) TestExpired() { } func (s *RequestMessagesSyncSuite) testCompletedFromAttempt(target int) { - const cursorSize = 36 // taken from mailserver_response.go from whisperv6 package + const cursorSize = 36 // taken from mailserver_response.go from whisper package cursor := [cursorSize]byte{} cursor[0] = 0x01 diff --git a/t/benchmarks/mailserver_test.go b/t/benchmarks/mailserver_test.go index f2c0e86de..1a871a4f2 100644 --- a/t/benchmarks/mailserver_test.go +++ b/t/benchmarks/mailserver_test.go @@ -13,7 +13,7 @@ import ( "github.com/status-im/status-go/params" "github.com/status-im/status-go/services/nodebridge" "github.com/status-im/status-go/services/shhext" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/require" ) diff --git a/t/benchmarks/messages_test.go b/t/benchmarks/messages_test.go index cbe84084c..d1a5b27e0 100644 --- a/t/benchmarks/messages_test.go +++ b/t/benchmarks/messages_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/node" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/require" ) diff --git a/t/benchmarks/utils_test.go b/t/benchmarks/utils_test.go index 414374450..c740c8d09 100644 --- a/t/benchmarks/utils_test.go +++ b/t/benchmarks/utils_test.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) var ( diff --git a/t/e2e/node/manager_test.go b/t/e2e/node/manager_test.go index 9ee0a55cf..817d63b98 100644 --- a/t/e2e/node/manager_test.go +++ b/t/e2e/node/manager_test.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/status-im/status-go/node" "github.com/status-im/status-go/params" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/status-im/status-go/t/e2e" . "github.com/status-im/status-go/t/utils" diff --git a/t/e2e/suites.go b/t/e2e/suites.go index 930427c4e..cf08d9723 100644 --- a/t/e2e/suites.go +++ b/t/e2e/suites.go @@ -3,7 +3,7 @@ package e2e import ( "github.com/ethereum/go-ethereum/log" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/status-im/status-go/api" "github.com/status-im/status-go/multiaccounts" diff --git a/t/e2e/whisper/whisper_mailbox_test.go b/t/e2e/whisper/whisper_mailbox_test.go index c4d11ff90..c79a6ad8a 100644 --- a/t/e2e/whisper/whisper_mailbox_test.go +++ b/t/e2e/whisper/whisper_mailbox_test.go @@ -24,7 +24,7 @@ import ( "github.com/status-im/status-go/services/shhext" "github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/utils" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" "golang.org/x/crypto/sha3" ) diff --git a/t/e2e/whisper/whisper_test.go b/t/e2e/whisper/whisper_test.go index 1eae4e176..9dc1c8309 100644 --- a/t/e2e/whisper/whisper_test.go +++ b/t/e2e/whisper/whisper_test.go @@ -11,7 +11,7 @@ import ( "github.com/status-im/status-go/account" e2e "github.com/status-im/status-go/t/e2e" . "github.com/status-im/status-go/t/utils" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "github.com/stretchr/testify/suite" ) diff --git a/vendor/github.com/karalabe/usb/.travis.yml b/vendor/github.com/karalabe/usb/.travis.yml index de0337b2b..7f925fbc0 100644 --- a/vendor/github.com/karalabe/usb/.travis.yml +++ b/vendor/github.com/karalabe/usb/.travis.yml @@ -32,8 +32,8 @@ matrix: - os: osx go: 1.12.x - os: linux - dist: xenial - go: 1.12.x + dist: bionic + go: 1.13.x services: - docker env: diff --git a/vendor/github.com/karalabe/usb/appveyor.yml b/vendor/github.com/karalabe/usb/appveyor.yml index 73a9664ae..595fd34ad 100644 --- a/vendor/github.com/karalabe/usb/appveyor.yml +++ b/vendor/github.com/karalabe/usb/appveyor.yml @@ -22,8 +22,8 @@ environment: install: - rmdir C:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go1.12.9.windows-%GOARCH%.zip - - 7z x go1.12.9.windows-%GOARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://storage.googleapis.com/golang/go1.13.4.windows-%GOARCH%.zip + - 7z x go1.13.4.windows-%GOARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope.go index 524de1074..9c7e738ea 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethEnvelopeWrapper struct { diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_error.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_error.go index c3156d45d..a6364617e 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_error.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_error.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethEnvelopeErrorWrapper returns a types.EnvelopeError object that mimics Geth's EnvelopeError diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_event.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_event.go index 202f6275d..dc4b9b82d 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_event.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/envelope_event.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethEnvelopeEventWrapper returns a types.EnvelopeEvent object that mimics Geth's EnvelopeEvent diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/filter.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/filter.go index 9033c4952..865c320c3 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/filter.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/filter.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethFilterWrapper struct { diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/mailserver_response.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/mailserver_response.go index bb27ddeaf..4a4fd2ce2 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/mailserver_response.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/mailserver_response.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethMailServerResponseWrapper returns a types.MailServerResponse object that mimics Geth's MailServerResponse diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go index 039869fa0..020f3c85d 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go @@ -5,7 +5,7 @@ import ( gethens "github.com/status-im/status-go/eth-node/bridge/geth/ens" "github.com/status-im/status-go/eth-node/types" enstypes "github.com/status-im/status-go/eth-node/types/ens" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" "go.uber.org/zap" ) diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/public_whisper_api.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/public_whisper_api.go index b2d1edac9..99bc3689b 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/public_whisper_api.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/public_whisper_api.go @@ -5,7 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethPublicWhisperAPIWrapper struct { diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncevent_response.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncevent_response.go index 74edd592f..09eea583c 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncevent_response.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncevent_response.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // NewGethSyncEventResponseWrapper returns a types.SyncEventResponse object that mimics Geth's SyncEventResponse diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncmailrequest.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncmailrequest.go index 6d1d6d01e..a8ad76b0b 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncmailrequest.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/syncmailrequest.go @@ -2,7 +2,7 @@ package gethbridge import ( "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) // GetGethSyncMailRequestFrom converts a whisper SyncMailRequest struct from a SyncMailRequest struct diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go index bafcd5ae5..009884f54 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go @@ -5,7 +5,7 @@ import ( "time" "github.com/status-im/status-go/eth-node/types" - whisper "github.com/status-im/whisper/whisperv6" + "github.com/status-im/status-go/whisper" ) type gethWhisperWrapper struct { diff --git a/vendor/github.com/status-im/status-go/protocol/go.mod b/vendor/github.com/status-im/status-go/protocol/go.mod index 18a1b1461..ba29bd3fe 100644 --- a/vendor/github.com/status-im/status-go/protocol/go.mod +++ b/vendor/github.com/status-im/status-go/protocol/go.mod @@ -8,6 +8,8 @@ replace github.com/gomarkdown/markdown => github.com/status-im/markdown v0.0.0-2 replace github.com/status-im/status-go/eth-node => ../eth-node +replace github.com/status-im/status-go/whisper => ../whisper + require ( github.com/cenkalti/backoff/v3 v3.0.0 github.com/ethereum/go-ethereum v1.9.5 @@ -23,7 +25,7 @@ require ( github.com/status-im/doubleratchet v3.0.0+incompatible github.com/status-im/migrate/v4 v4.6.2-status.2 github.com/status-im/status-go/eth-node v0.0.0-20191120100713-5053b0b6835b - github.com/status-im/whisper v1.5.2 + github.com/status-im/status-go/whisper v1.0.0 github.com/stretchr/testify v1.4.0 github.com/vacp2p/mvds v0.0.23 go.uber.org/zap v1.13.0 diff --git a/vendor/github.com/status-im/status-go/protocol/go.sum b/vendor/github.com/status-im/status-go/protocol/go.sum index 5a57fb513..733f5000e 100644 --- a/vendor/github.com/status-im/status-go/protocol/go.sum +++ b/vendor/github.com/status-im/status-go/protocol/go.sum @@ -487,6 +487,7 @@ github.com/status-im/markdown v0.0.0-20191113114344-af599402d015/go.mod h1:tmG2b github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T60IS9z4/rYCCbI8= github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8= github.com/status-im/status-go v0.36.0 h1:91qDMJjHv+T3Li9FwxsWQ2JBVcYtvVDT0nGFSMnmM+8= +github.com/status-im/status-go v0.36.1 h1:nb9eTq0UQJ57YyTZSl5U05emFT+R4AW8/Bga6ocgOks= github.com/status-im/status-go/extkeys v1.0.0 h1:Qyirsoi5Ye5UFfisgPtCjPb/RkBxyK+UsSiEcr2PVlM= github.com/status-im/status-go/extkeys v1.0.0/go.mod h1:GdqJbrcpkNm5ZsSCpp+PdMxnXx+OcRBdm3PI0rs1FpU= github.com/status-im/whisper v1.5.2 h1:26NgiKusmPic38eQdtXnaY+iaQ/LuQ3Dh0kCGYT/Uxs= @@ -501,6 +502,7 @@ github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9C github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/github.com/status-im/whisper/whisperv6/api.go b/vendor/github.com/status-im/status-go/whisper/api.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/api.go rename to vendor/github.com/status-im/status-go/whisper/api.go index 0aa85b0ff..e87f51a50 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/api.go +++ b/vendor/github.com/status-im/status-go/whisper/api.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package whisperv6 +package whisper import ( "context" diff --git a/vendor/github.com/status-im/whisper/whisperv6/config.go b/vendor/github.com/status-im/status-go/whisper/config.go similarity index 98% rename from vendor/github.com/status-im/whisper/whisperv6/config.go rename to vendor/github.com/status-im/status-go/whisper/config.go index 36bf412ac..1b5adf7b2 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/config.go +++ b/vendor/github.com/status-im/status-go/whisper/config.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package whisperv6 +package whisper // Config represents the configuration state of a whisper node. type Config struct { diff --git a/vendor/github.com/status-im/whisper/whisperv6/doc.go b/vendor/github.com/status-im/status-go/whisper/doc.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/doc.go rename to vendor/github.com/status-im/status-go/whisper/doc.go index d7a1a082b..15879ffa9 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/doc.go +++ b/vendor/github.com/status-im/status-go/whisper/doc.go @@ -30,7 +30,7 @@ particularly the notion of singular endpoints. // Contains the Whisper protocol constant definitions -package whisperv6 +package whisper import ( "errors" diff --git a/vendor/github.com/status-im/whisper/whisperv6/envelope.go b/vendor/github.com/status-im/status-go/whisper/envelope.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/envelope.go rename to vendor/github.com/status-im/status-go/whisper/envelope.go index 38626e1be..3bef71209 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/envelope.go +++ b/vendor/github.com/status-im/status-go/whisper/envelope.go @@ -16,7 +16,7 @@ // Contains the Whisper protocol Envelope element. -package whisperv6 +package whisper import ( "crypto/ecdsa" diff --git a/vendor/github.com/status-im/whisper/whisperv6/events.go b/vendor/github.com/status-im/status-go/whisper/events.go similarity index 98% rename from vendor/github.com/status-im/whisper/whisperv6/events.go rename to vendor/github.com/status-im/status-go/whisper/events.go index 195435f66..9de7b846e 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/events.go +++ b/vendor/github.com/status-im/status-go/whisper/events.go @@ -1,4 +1,4 @@ -package whisperv6 +package whisper import ( "github.com/ethereum/go-ethereum/common" @@ -38,7 +38,6 @@ const ( // EnvelopeEvent used for envelopes events. type EnvelopeEvent struct { Event EventType - Topic TopicType Hash common.Hash Batch common.Hash Peer enode.ID diff --git a/vendor/github.com/status-im/whisper/whisperv6/filter.go b/vendor/github.com/status-im/status-go/whisper/filter.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/filter.go rename to vendor/github.com/status-im/status-go/whisper/filter.go index b75f7b3c8..9bdc7742c 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/filter.go +++ b/vendor/github.com/status-im/status-go/whisper/filter.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package whisperv6 +package whisper import ( "crypto/ecdsa" diff --git a/vendor/github.com/status-im/whisper/whisperv6/fuzz.go b/vendor/github.com/status-im/status-go/whisper/fuzz.go similarity index 89% rename from vendor/github.com/status-im/whisper/whisperv6/fuzz.go rename to vendor/github.com/status-im/status-go/whisper/fuzz.go index 7f92bdb0a..46488b178 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/fuzz.go +++ b/vendor/github.com/status-im/status-go/whisper/fuzz.go @@ -1,6 +1,6 @@ // +build gofuzz -package whisperv6 +package whisper func Fuzz(data []byte) int { if len(data) < 2 { diff --git a/vendor/github.com/status-im/whisper/whisperv6/gen_criteria_json.go b/vendor/github.com/status-im/status-go/whisper/gen_criteria_json.go similarity index 98% rename from vendor/github.com/status-im/whisper/whisperv6/gen_criteria_json.go rename to vendor/github.com/status-im/status-go/whisper/gen_criteria_json.go index 1a428d6df..dc7f13894 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/gen_criteria_json.go +++ b/vendor/github.com/status-im/status-go/whisper/gen_criteria_json.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package whisperv6 +package whisper import ( "encoding/json" diff --git a/vendor/github.com/status-im/whisper/whisperv6/gen_message_json.go b/vendor/github.com/status-im/status-go/whisper/gen_message_json.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/gen_message_json.go rename to vendor/github.com/status-im/status-go/whisper/gen_message_json.go index 6218f5df6..d573b7ae4 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/gen_message_json.go +++ b/vendor/github.com/status-im/status-go/whisper/gen_message_json.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package whisperv6 +package whisper import ( "encoding/json" diff --git a/vendor/github.com/status-im/whisper/whisperv6/gen_newmessage_json.go b/vendor/github.com/status-im/status-go/whisper/gen_newmessage_json.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/gen_newmessage_json.go rename to vendor/github.com/status-im/status-go/whisper/gen_newmessage_json.go index 75a1279ae..d6a14c203 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/gen_newmessage_json.go +++ b/vendor/github.com/status-im/status-go/whisper/gen_newmessage_json.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package whisperv6 +package whisper import ( "encoding/json" diff --git a/vendor/github.com/status-im/status-go/whisper/go.mod b/vendor/github.com/status-im/status-go/whisper/go.mod new file mode 100644 index 000000000..a66c5b439 --- /dev/null +++ b/vendor/github.com/status-im/status-go/whisper/go.mod @@ -0,0 +1,17 @@ +module github.com/status-im/status-go/whisper + +go 1.13 + +replace github.com/ethereum/go-ethereum v1.9.5 => github.com/status-im/go-ethereum v1.9.5-status.6 + +require ( + github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea + github.com/ethereum/go-ethereum v1.9.5 + github.com/gorilla/websocket v1.4.1 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.3.0 + github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 + github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9 + golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f +) diff --git a/vendor/github.com/status-im/status-go/whisper/go.sum b/vendor/github.com/status-im/status-go/whisper/go.sum new file mode 100644 index 000000000..b864e2768 --- /dev/null +++ b/vendor/github.com/status-im/status-go/whisper/go.sum @@ -0,0 +1,134 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +github.com/Azure/azure-pipeline-go v0.0.0-20180607212504-7571e8eb0876/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-storage-blob-go v0.0.0-20180712005634-eaae161d9d5e/go.mod h1:x2mtS6O3mnMEZOJp7d7oldh8IvatBrMfReiyQ+cKgKY= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/allegro/bigcache v0.0.0-20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/btcsuite/btcd v0.20.0-beta h1:DnZGUjFbRkpytojHWwy6nfUSA7vFrzWXDLpFNzt74ZA= +github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgrijalva/jwt-go v0.0.0-20170201225849-2268707a8f08/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/docker v0.0.0-20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa h1:o8OuEkracbk3qH6GvlI6XpEN1HTSxkzOG42xZpfDv/s= +github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gizak/termui v0.0.0-20170117222342-991cd3d38091/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-stack/stack v1.5.4 h1:ACUuwAbOuCKT3mK+Az9UrqaSheA8lDWOfm0+ZT62NHY= +github.com/go-stack/stack v1.5.4/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 h1:DqD8eigqlUm0+znmx7zhL0xvTW3+e1jCekJMfBUADWI= +github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= +github.com/influxdata/influxdb v0.0.0-20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jackpal/go-nat-pmp v0.0.0-20160603034137-1fa385a6f458 h1:LPECOO5LcZx5tvkxraIptrg6AiAUf+28rFV9+noSZFA= +github.com/jackpal/go-nat-pmp v0.0.0-20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v0.0.0-20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karalabe/hid v0.0.0-20181128192157-d815e0c1a2e2/go.mod h1:YvbcH+3Wo6XPs9nkgTY3u19KXLauXW+J5nB7hEHuX0A= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/naoina/toml v0.0.0-20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/olekukonko/tablewriter v0.0.0-20170128050532-febf2d34b54a/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v0.0.0-20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterh/liner v0.0.0-20170902204657-a37ad3984311/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pkg/errors v0.0.0-20171216070316-e881fd58d78e h1:+RHxT/gm0O3UF7nLJbdNzAmULvCFt4XfXHWzh3XI/zs= +github.com/pkg/errors v0.0.0-20171216070316-e881fd58d78e/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/prometheus v0.0.0-20170814170113-3101606756c5/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/status-im/go-ethereum v1.9.5-status.6 h1:ytuTO1yBIAuTVRtRQoc2mrdyngtP+XOQ9IHIibbz7/I= +github.com/status-im/go-ethereum v1.9.5-status.6/go.mod h1:08JvQWE+IOnAFSe4UD4ACLNe2fDd9XmWMCq5Yzy9mk0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4 h1:InXsxTNd7R4kIHKuA052litAUzokFLqjgbmhpUQTAs8= +github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2zDhJJABEofdb51uMRNb8eqIVtdducs= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9 h1:kjbwitOGH46vD01f2s3leBfrMnePQa3NSAIlW35MvY8= +github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9/go.mod h1:EcGP24b8DY+bWHnpfJDP7fM+o8Nmz4fYH0l2xTtNr3I= +github.com/uber/jaeger-client-go v0.0.0-20180607151842-f7e0d4744fa6/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v0.0.0-20180615202729-a51202d6f4a7/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20180302121509-abf0ba0be5d5/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/status-im/whisper/whisperv6/mailserver_response.go b/vendor/github.com/status-im/status-go/whisper/mailserver_response.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/mailserver_response.go rename to vendor/github.com/status-im/status-go/whisper/mailserver_response.go index ba1d5901a..f529c2f2f 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/mailserver_response.go +++ b/vendor/github.com/status-im/status-go/whisper/mailserver_response.go @@ -1,4 +1,4 @@ -package whisperv6 +package whisper import ( "bytes" diff --git a/vendor/github.com/status-im/whisper/whisperv6/message.go b/vendor/github.com/status-im/status-go/whisper/message.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/message.go rename to vendor/github.com/status-im/status-go/whisper/message.go index 8b6cdb74d..f085b8ebc 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/message.go +++ b/vendor/github.com/status-im/status-go/whisper/message.go @@ -16,7 +16,7 @@ // Contains the Whisper protocol Message element. -package whisperv6 +package whisper import ( "crypto/aes" diff --git a/vendor/github.com/status-im/status-go/whisper/metrics.go b/vendor/github.com/status-im/status-go/whisper/metrics.go new file mode 100644 index 000000000..090682fbd --- /dev/null +++ b/vendor/github.com/status-im/status-go/whisper/metrics.go @@ -0,0 +1,21 @@ +package whisper + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + envelopeAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeAdded", nil) + envelopeNewAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeNewAdded", nil) + envelopeClearedCounter = metrics.NewRegisteredCounter("whisper/envelopeCleared", nil) + envelopeErrFromFutureCounter = metrics.NewRegisteredCounter("whisper/envelopeErrFromFuture", nil) + envelopeErrVeryOldCounter = metrics.NewRegisteredCounter("whisper/envelopeErrVeryOld", nil) + envelopeErrExpiredCounter = metrics.NewRegisteredCounter("whisper/envelopeErrExpired", nil) + envelopeErrOversizedCounter = metrics.NewRegisteredCounter("whisper/envelopeErrOversized", nil) + envelopeErrLowPowCounter = metrics.NewRegisteredCounter("whisper/envelopeErrLowPow", nil) + envelopeErrNoBloomMatchCounter = metrics.NewRegisteredCounter("whisper/envelopeErrNoBloomMatch", nil) + envelopeSizeMeter = metrics.NewRegisteredMeter("whisper/envelopeSize", nil) + + // rate limiter metrics + rateLimiterProcessed = metrics.NewRegisteredCounter("whisper/rateLimiterProcessed", nil) + rateLimiterIPExceeded = metrics.NewRegisteredCounter("whisper/rateLimiterIPExceeded", nil) + rateLimiterPeerExceeded = metrics.NewRegisteredCounter("whisper/rateLimiterPeerExceeded", nil) +) diff --git a/vendor/github.com/status-im/whisper/whisperv6/peer.go b/vendor/github.com/status-im/status-go/whisper/peer.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/peer.go rename to vendor/github.com/status-im/status-go/whisper/peer.go index 1a1de9bd5..d2a2d0600 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/peer.go +++ b/vendor/github.com/status-im/status-go/whisper/peer.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package whisperv6 +package whisper import ( "bytes" diff --git a/vendor/github.com/status-im/whisper/whisperv6/rate_limiter.go b/vendor/github.com/status-im/status-go/whisper/rate_limiter.go similarity index 86% rename from vendor/github.com/status-im/whisper/whisperv6/rate_limiter.go rename to vendor/github.com/status-im/status-go/whisper/rate_limiter.go index 93efe4baf..43166210a 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/rate_limiter.go +++ b/vendor/github.com/status-im/status-go/whisper/rate_limiter.go @@ -1,4 +1,4 @@ -package whisperv6 +package whisper import ( "bytes" @@ -14,24 +14,14 @@ import ( type runLoop func(p *Peer, rw p2p.MsgReadWriter) error type RateLimiterHandler interface { - IncProcessed() - IncExceedPeerLimit() - IncExceedIPLimit() + ExceedPeerLimit() + ExceedIPLimit() } type MetricsRateLimiterHandler struct{} -func (MetricsRateLimiterHandler) IncProcessed() { - rateLimitsProcessed.Inc() -} - -func (MetricsRateLimiterHandler) IncExceedPeerLimit() { - rateLimitsExceeded.WithLabelValues("max_peers").Inc() -} - -func (MetricsRateLimiterHandler) IncExceedIPLimit() { - rateLimitsExceeded.WithLabelValues("max_ips").Inc() -} +func (MetricsRateLimiterHandler) ExceedPeerLimit() { rateLimiterPeerExceeded.Inc(1) } +func (MetricsRateLimiterHandler) ExceedIPLimit() { rateLimiterIPExceeded.Inc(1) } type PeerRateLimiterConfig struct { LimitPerSecIP int64 @@ -40,7 +30,7 @@ type PeerRateLimiterConfig struct { WhitelistedPeerIDs []enode.ID } -var peerRateLimiterDefaults = PeerRateLimiterConfig{ +var defaultPeerRateLimiterConfig = PeerRateLimiterConfig{ LimitPerSecIP: 10, LimitPerSecPeerID: 5, WhitelistedIPs: nil, @@ -62,7 +52,7 @@ type PeerRateLimiter struct { func NewPeerRateLimiter(handler RateLimiterHandler, cfg *PeerRateLimiterConfig) *PeerRateLimiter { if cfg == nil { - copy := peerRateLimiterDefaults + copy := defaultPeerRateLimiterConfig cfg = © } @@ -92,14 +82,14 @@ func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoo return } - r.handler.IncProcessed() + rateLimiterProcessed.Inc(1) var ip string if p != nil && p.peer != nil { ip = p.peer.Node().IP().String() } if halted := r.throttleIP(ip); halted { - r.handler.IncExceedIPLimit() + r.handler.ExceedIPLimit() } var peerID []byte @@ -107,7 +97,7 @@ func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoo peerID = p.ID() } if halted := r.throttlePeer(peerID); halted { - r.handler.IncExceedPeerLimit() + r.handler.ExceedPeerLimit() } if err := in.WriteMsg(packet); err != nil { diff --git a/vendor/github.com/status-im/whisper/whisperv6/topic.go b/vendor/github.com/status-im/status-go/whisper/topic.go similarity index 99% rename from vendor/github.com/status-im/whisper/whisperv6/topic.go rename to vendor/github.com/status-im/status-go/whisper/topic.go index 4dd8f283c..9b6763688 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/topic.go +++ b/vendor/github.com/status-im/status-go/whisper/topic.go @@ -16,7 +16,7 @@ // Contains the Whisper protocol Topic element. -package whisperv6 +package whisper import ( "github.com/ethereum/go-ethereum/common" diff --git a/vendor/github.com/status-im/whisper/whisperv6/whisper.go b/vendor/github.com/status-im/status-go/whisper/whisper.go similarity index 97% rename from vendor/github.com/status-im/whisper/whisperv6/whisper.go rename to vendor/github.com/status-im/status-go/whisper/whisper.go index a8587a8b4..d333d7c90 100644 --- a/vendor/github.com/status-im/whisper/whisperv6/whisper.go +++ b/vendor/github.com/status-im/status-go/whisper/whisper.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package whisperv6 +package whisper import ( "bytes" @@ -441,7 +441,6 @@ func (whisper *Whisper) RequestHistoricMessagesWithTimeout(peerID []byte, envelo } whisper.envelopeFeed.Send(EnvelopeEvent{ Peer: p.peer.ID(), - Topic: envelope.Topic, Hash: envelope.Hash(), Event: EventMailServerRequestSent, }) @@ -934,14 +933,12 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // decode the contained envelopes data, err := ioutil.ReadAll(packet.Payload) if err != nil { - envelopesRejectedCounter.WithLabelValues("failed_read").Inc() log.Warn("failed to read envelopes data", "peer", p.peer.ID(), "error", err) return errors.New("invalid enveloopes") } var envelopes []*Envelope if err := rlp.DecodeBytes(data, &envelopes); err != nil { - envelopesRejectedCounter.WithLabelValues("invalid_data").Inc() log.Warn("failed to decode envelopes, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelopes") } @@ -960,11 +957,9 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { whisper.envelopeFeed.Send(EnvelopeEvent{ Event: EventEnvelopeReceived, - Topic: env.Topic, Hash: env.Hash(), Peer: p.peer.ID(), }) - envelopesValidatedCounter.Inc() if cached { p.mark(env) } @@ -979,14 +974,12 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { case messageResponseCode: var multiResponse MultiVersionResponse if err := packet.Decode(&multiResponse); err != nil { - envelopesRejectedCounter.WithLabelValues("failed_read").Inc() log.Error("failed to decode messages response", "peer", p.peer.ID(), "error", err) return errors.New("invalid response message") } if multiResponse.Version == 1 { response, err := multiResponse.DecodeResponse1() if err != nil { - envelopesRejectedCounter.WithLabelValues("invalid_data").Inc() log.Error("failed to decode messages response into first version of response", "peer", p.peer.ID(), "error", err) } whisper.envelopeFeed.Send(EnvelopeEvent{ @@ -1013,13 +1006,11 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { s := rlp.NewStream(packet.Payload, uint64(packet.Size)) i, err := s.Uint() if err != nil { - envelopesRejectedCounter.WithLabelValues("invalid_pow_req").Inc() log.Warn("failed to decode powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid powRequirementCode message") } f := math.Float64frombits(i) if math.IsInf(f, 0) || math.IsNaN(f) || f < 0.0 { - envelopesRejectedCounter.WithLabelValues("invalid_pow_req").Inc() log.Warn("invalid value in powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid value in powRequirementCode message") } @@ -1033,7 +1024,6 @@ func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { if err != nil { log.Warn("failed to decode bloom filter exchange message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - envelopesRejectedCounter.WithLabelValues("invalid_bloom").Inc() return errors.New("invalid bloom filter exchange message") } p.setBloomFilter(bloom) @@ -1214,10 +1204,10 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { now := uint32(whisper.timeSource().Unix()) sent := envelope.Expiry - envelope.TTL - envelopesReceivedCounter.Inc() + envelopeAddedCounter.Inc(1) if sent > now { if sent-DefaultSyncAllowance > now { - envelopesCacheFailedCounter.WithLabelValues("in_future").Inc() + envelopeErrFromFutureCounter.Inc(1) log.Warn("envelope created in the future", "hash", envelope.Hash()) return false, TimeSyncError(errors.New("envelope from future")) } @@ -1227,17 +1217,17 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { if envelope.Expiry < now { if envelope.Expiry+DefaultSyncAllowance*2 < now { - envelopesCacheFailedCounter.WithLabelValues("very_old").Inc() + envelopeErrVeryOldCounter.Inc(1) log.Warn("very old envelope", "hash", envelope.Hash()) return false, TimeSyncError(errors.New("very old envelope")) } log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) - envelopesCacheFailedCounter.WithLabelValues("expired").Inc() + envelopeErrExpiredCounter.Inc(1) return false, nil // drop envelope without error } if uint32(envelope.size()) > whisper.MaxMessageSize() { - envelopesCacheFailedCounter.WithLabelValues("oversized").Inc() + envelopeErrOversizedCounter.Inc(1) return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -1246,7 +1236,7 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { // in this case the previous value is retrieved by MinPowTolerance() // for a short period of peer synchronization. if envelope.PoW() < whisper.MinPowTolerance() { - envelopesCacheFailedCounter.WithLabelValues("low_pow").Inc() + envelopeErrLowPowCounter.Inc(1) return false, fmt.Errorf("envelope with low PoW received: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex()) } } @@ -1256,7 +1246,7 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { // in this case the previous value is retrieved by BloomFilterTolerance() // for a short period of peer synchronization. if !BloomFilterMatch(whisper.BloomFilterTolerance(), envelope.Bloom()) { - envelopesCacheFailedCounter.WithLabelValues("no_bloom_match").Inc() + envelopeErrNoBloomMatchCounter.Inc(1) return false, fmt.Errorf("envelope does not match bloom filter, hash=[%v], bloom: \n%x \n%x \n%x", envelope.Hash().Hex(), whisper.BloomFilter(), envelope.Bloom(), envelope.Topic) } @@ -1279,11 +1269,10 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { if alreadyCached { log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) - envelopesCachedCounter.WithLabelValues("hit").Inc() } else { log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) - envelopesCachedCounter.WithLabelValues("miss").Inc() - envelopesSizeMeter.Observe(float64(envelope.size())) + envelopeNewAddedCounter.Inc(1) + envelopeSizeMeter.Mark(int64(envelope.size())) whisper.statsMu.Lock() whisper.stats.memoryUsed += envelope.size() whisper.statsMu.Unlock() @@ -1291,7 +1280,6 @@ func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { if whisper.mailServer != nil { whisper.mailServer.Archive(envelope) whisper.envelopeFeed.Send(EnvelopeEvent{ - Topic: envelope.Topic, Hash: envelope.Hash(), Event: EventMailServerEnvelopeArchived, }) @@ -1341,7 +1329,6 @@ func (whisper *Whisper) processQueue() { case e := <-whisper.messageQueue: whisper.filters.NotifyWatchers(e, false) whisper.envelopeFeed.Send(EnvelopeEvent{ - Topic: e.Topic, Hash: e.Hash(), Event: EventEnvelopeAvailable, }) @@ -1359,7 +1346,6 @@ func (whisper *Whisper) processP2P() { case *Envelope: whisper.filters.NotifyWatchers(event, true) whisper.envelopeFeed.Send(EnvelopeEvent{ - Topic: event.Topic, Hash: event.Hash(), Event: EventEnvelopeAvailable, }) @@ -1404,7 +1390,7 @@ func (whisper *Whisper) expire() { hashSet.Each(func(v interface{}) bool { sz := whisper.envelopes[v.(common.Hash)].size() delete(whisper.envelopes, v.(common.Hash)) - envelopesCachedCounter.WithLabelValues("clear").Inc() + envelopeClearedCounter.Inc(1) whisper.envelopeFeed.Send(EnvelopeEvent{ Hash: v.(common.Hash), Event: EventEnvelopeExpired, diff --git a/vendor/github.com/status-im/whisper/whisperv6/metrics.go b/vendor/github.com/status-im/whisper/whisperv6/metrics.go deleted file mode 100644 index 1d2064bba..000000000 --- a/vendor/github.com/status-im/whisper/whisperv6/metrics.go +++ /dev/null @@ -1,50 +0,0 @@ -package whisperv6 - -import prom "github.com/prometheus/client_golang/prometheus" - -var ( - envelopesReceivedCounter = prom.NewCounter(prom.CounterOpts{ - Name: "whisper_envelopes_received_total", - Help: "Number of envelopes received.", - }) - envelopesValidatedCounter = prom.NewCounter(prom.CounterOpts{ - Name: "whisper_envelopes_validated_total", - Help: "Number of envelopes processed successfully.", - }) - envelopesRejectedCounter = prom.NewCounterVec(prom.CounterOpts{ - Name: "whisper_envelopes_rejected_total", - Help: "Number of envelopes rejected.", - }, []string{"reason"}) - envelopesCacheFailedCounter = prom.NewCounterVec(prom.CounterOpts{ - Name: "whisper_envelopes_cache_failures_total", - Help: "Number of envelopes which failed to be cached.", - }, []string{"type"}) - envelopesCachedCounter = prom.NewCounterVec(prom.CounterOpts{ - Name: "whisper_envelopes_cached_total", - Help: "Number of envelopes cached.", - }, []string{"cache"}) - envelopesSizeMeter = prom.NewHistogram(prom.HistogramOpts{ - Name: "whisper_envelopes_size_bytes", - Help: "Size of processed Whisper envelopes in bytes.", - Buckets: prom.ExponentialBuckets(256, 4, 10), - }) - // rate limiter metrics - rateLimitsProcessed = prom.NewCounter(prom.CounterOpts{ - Name: "whisper_rate_limits_processed_total", - Help: "Number of packets whisper rate limiter processed.", - }) - rateLimitsExceeded = prom.NewCounterVec(prom.CounterOpts{ - Name: "whisper_rate_limits_exceeded_total", - Help: "Number of times the whisper rate limits were exceeded", - }, []string{"type"}) -) - -func init() { - prom.MustRegister(envelopesReceivedCounter) - prom.MustRegister(envelopesRejectedCounter) - prom.MustRegister(envelopesCacheFailedCounter) - prom.MustRegister(envelopesCachedCounter) - prom.MustRegister(envelopesSizeMeter) - prom.MustRegister(rateLimitsProcessed) - prom.MustRegister(rateLimitsExceeded) -} diff --git a/vendor/golang.org/x/crypto/blowfish/block.go b/vendor/golang.org/x/crypto/blowfish/block.go deleted file mode 100644 index 9d80f1952..000000000 --- a/vendor/golang.org/x/crypto/blowfish/block.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package blowfish - -// getNextWord returns the next big-endian uint32 value from the byte slice -// at the given position in a circular manner, updating the position. -func getNextWord(b []byte, pos *int) uint32 { - var w uint32 - j := *pos - for i := 0; i < 4; i++ { - w = w<<8 | uint32(b[j]) - j++ - if j >= len(b) { - j = 0 - } - } - *pos = j - return w -} - -// ExpandKey performs a key expansion on the given *Cipher. Specifically, it -// performs the Blowfish algorithm's key schedule which sets up the *Cipher's -// pi and substitution tables for calls to Encrypt. This is used, primarily, -// by the bcrypt package to reuse the Blowfish key schedule during its -// set up. It's unlikely that you need to use this directly. -func ExpandKey(key []byte, c *Cipher) { - j := 0 - for i := 0; i < 18; i++ { - // Using inlined getNextWord for performance. - var d uint32 - for k := 0; k < 4; k++ { - d = d<<8 | uint32(key[j]) - j++ - if j >= len(key) { - j = 0 - } - } - c.p[i] ^= d - } - - var l, r uint32 - for i := 0; i < 18; i += 2 { - l, r = encryptBlock(l, r, c) - c.p[i], c.p[i+1] = l, r - } - - for i := 0; i < 256; i += 2 { - l, r = encryptBlock(l, r, c) - c.s0[i], c.s0[i+1] = l, r - } - for i := 0; i < 256; i += 2 { - l, r = encryptBlock(l, r, c) - c.s1[i], c.s1[i+1] = l, r - } - for i := 0; i < 256; i += 2 { - l, r = encryptBlock(l, r, c) - c.s2[i], c.s2[i+1] = l, r - } - for i := 0; i < 256; i += 2 { - l, r = encryptBlock(l, r, c) - c.s3[i], c.s3[i+1] = l, r - } -} - -// This is similar to ExpandKey, but folds the salt during the key -// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero -// salt passed in, reusing ExpandKey turns out to be a place of inefficiency -// and specializing it here is useful. -func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { - j := 0 - for i := 0; i < 18; i++ { - c.p[i] ^= getNextWord(key, &j) - } - - j = 0 - var l, r uint32 - for i := 0; i < 18; i += 2 { - l ^= getNextWord(salt, &j) - r ^= getNextWord(salt, &j) - l, r = encryptBlock(l, r, c) - c.p[i], c.p[i+1] = l, r - } - - for i := 0; i < 256; i += 2 { - l ^= getNextWord(salt, &j) - r ^= getNextWord(salt, &j) - l, r = encryptBlock(l, r, c) - c.s0[i], c.s0[i+1] = l, r - } - - for i := 0; i < 256; i += 2 { - l ^= getNextWord(salt, &j) - r ^= getNextWord(salt, &j) - l, r = encryptBlock(l, r, c) - c.s1[i], c.s1[i+1] = l, r - } - - for i := 0; i < 256; i += 2 { - l ^= getNextWord(salt, &j) - r ^= getNextWord(salt, &j) - l, r = encryptBlock(l, r, c) - c.s2[i], c.s2[i+1] = l, r - } - - for i := 0; i < 256; i += 2 { - l ^= getNextWord(salt, &j) - r ^= getNextWord(salt, &j) - l, r = encryptBlock(l, r, c) - c.s3[i], c.s3[i+1] = l, r - } -} - -func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { - xl, xr := l, r - xl ^= c.p[0] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] - xr ^= c.p[17] - return xr, xl -} - -func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { - xl, xr := l, r - xl ^= c.p[17] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] - xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] - xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] - xr ^= c.p[0] - return xr, xl -} diff --git a/vendor/golang.org/x/crypto/blowfish/cipher.go b/vendor/golang.org/x/crypto/blowfish/cipher.go deleted file mode 100644 index 213bf204a..000000000 --- a/vendor/golang.org/x/crypto/blowfish/cipher.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. -// -// Blowfish is a legacy cipher and its short block size makes it vulnerable to -// birthday bound attacks (see https://sweet32.info). It should only be used -// where compatibility with legacy systems, not security, is the goal. -// -// Deprecated: any new system should use AES (from crypto/aes, if necessary in -// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from -// golang.org/x/crypto/chacha20poly1305). -package blowfish // import "golang.org/x/crypto/blowfish" - -// The code is a port of Bruce Schneier's C implementation. -// See https://www.schneier.com/blowfish.html. - -import "strconv" - -// The Blowfish block size in bytes. -const BlockSize = 8 - -// A Cipher is an instance of Blowfish encryption using a particular key. -type Cipher struct { - p [18]uint32 - s0, s1, s2, s3 [256]uint32 -} - -type KeySizeError int - -func (k KeySizeError) Error() string { - return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) -} - -// NewCipher creates and returns a Cipher. -// The key argument should be the Blowfish key, from 1 to 56 bytes. -func NewCipher(key []byte) (*Cipher, error) { - var result Cipher - if k := len(key); k < 1 || k > 56 { - return nil, KeySizeError(k) - } - initCipher(&result) - ExpandKey(key, &result) - return &result, nil -} - -// NewSaltedCipher creates a returns a Cipher that folds a salt into its key -// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is -// sufficient and desirable. For bcrypt compatibility, the key can be over 56 -// bytes. -func NewSaltedCipher(key, salt []byte) (*Cipher, error) { - if len(salt) == 0 { - return NewCipher(key) - } - var result Cipher - if k := len(key); k < 1 { - return nil, KeySizeError(k) - } - initCipher(&result) - expandKeyWithSalt(key, salt, &result) - return &result, nil -} - -// BlockSize returns the Blowfish block size, 8 bytes. -// It is necessary to satisfy the Block interface in the -// package "crypto/cipher". -func (c *Cipher) BlockSize() int { return BlockSize } - -// Encrypt encrypts the 8-byte buffer src using the key k -// and stores the result in dst. -// Note that for amounts of data larger than a block, -// it is not safe to just call Encrypt on successive blocks; -// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). -func (c *Cipher) Encrypt(dst, src []byte) { - l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) - r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) - l, r = encryptBlock(l, r, c) - dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) - dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) -} - -// Decrypt decrypts the 8-byte buffer src using the key k -// and stores the result in dst. -func (c *Cipher) Decrypt(dst, src []byte) { - l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) - r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) - l, r = decryptBlock(l, r, c) - dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) - dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) -} - -func initCipher(c *Cipher) { - copy(c.p[0:], p[0:]) - copy(c.s0[0:], s0[0:]) - copy(c.s1[0:], s1[0:]) - copy(c.s2[0:], s2[0:]) - copy(c.s3[0:], s3[0:]) -} diff --git a/vendor/golang.org/x/crypto/blowfish/const.go b/vendor/golang.org/x/crypto/blowfish/const.go deleted file mode 100644 index d04077595..000000000 --- a/vendor/golang.org/x/crypto/blowfish/const.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// The startup permutation array and substitution boxes. -// They are the hexadecimal digits of PI; see: -// https://www.schneier.com/code/constants.txt. - -package blowfish - -var s0 = [256]uint32{ - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, - 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, - 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, - 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, - 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, - 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, - 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, - 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, - 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, - 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, - 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, - 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, - 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, - 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, - 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, - 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, - 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, - 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, - 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, - 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, - 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, - 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, -} - -var s1 = [256]uint32{ - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, - 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, - 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, - 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, - 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, - 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, - 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, - 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, - 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, - 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, - 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, - 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, - 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, - 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, - 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, - 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, - 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, - 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, - 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, - 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, - 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, - 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, -} - -var s2 = [256]uint32{ - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, - 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, - 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, - 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, - 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, - 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, - 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, - 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, - 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, - 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, - 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, - 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, - 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, - 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, - 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, - 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, - 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, - 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, - 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, - 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, - 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, - 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, -} - -var s3 = [256]uint32{ - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, - 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, - 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, - 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, - 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, - 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, - 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, - 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, - 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, - 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, - 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, - 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, - 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, - 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, - 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, - 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, - 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, - 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, - 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, - 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, - 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, - 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, -} - -var p = [18]uint32{ - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, - 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, -} diff --git a/vendor/modules.txt b/vendor/modules.txt index d6a090b04..7b399f71f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -396,10 +396,10 @@ github.com/status-im/status-go/protocol/transport/whisper github.com/status-im/status-go/protocol/transport/whisper/migrations github.com/status-im/status-go/protocol/v1 github.com/status-im/status-go/protocol/zaputil +# github.com/status-im/status-go/whisper v1.0.0 => ./whisper +github.com/status-im/status-go/whisper # github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 github.com/status-im/tcp-shaker -# github.com/status-im/whisper v1.6.2 -github.com/status-im/whisper/whisperv6 # github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/bloomfilter # github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 diff --git a/whisper/api.go b/whisper/api.go new file mode 100644 index 000000000..e87f51a50 --- /dev/null +++ b/whisper/api.go @@ -0,0 +1,604 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rpc" +) + +// List of errors +var ( + ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") + ErrInvalidSymmetricKey = errors.New("invalid symmetric key") + ErrInvalidPublicKey = errors.New("invalid public key") + ErrInvalidSigningPubKey = errors.New("invalid signing public key") + ErrTooLowPoW = errors.New("message rejected, PoW too low") + ErrNoTopics = errors.New("missing topic(s)") +) + +// PublicWhisperAPI provides the whisper RPC service that can be +// use publicly without security implications. +type PublicWhisperAPI struct { + w *Whisper + + mu sync.Mutex + lastUsed map[string]time.Time // keeps track when a filter was polled for the last time. +} + +// NewPublicWhisperAPI create a new RPC whisper service. +func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI { + api := &PublicWhisperAPI{ + w: w, + lastUsed: make(map[string]time.Time), + } + return api +} + +// Version returns the Whisper sub-protocol version. +func (api *PublicWhisperAPI) Version(ctx context.Context) string { + return ProtocolVersionStr +} + +// Info contains diagnostic information. +type Info struct { + Memory int `json:"memory"` // Memory size of the floating messages in bytes. + Messages int `json:"messages"` // Number of floating messages. + MinPow float64 `json:"minPow"` // Minimal accepted PoW + MaxMessageSize uint32 `json:"maxMessageSize"` // Maximum accepted message size +} + +// Info returns diagnostic information about the whisper node. +func (api *PublicWhisperAPI) Info(ctx context.Context) Info { + stats := api.w.Stats() + return Info{ + Memory: stats.memoryUsed, + Messages: len(api.w.messageQueue) + len(api.w.p2pMsgQueue), + MinPow: api.w.MinPow(), + MaxMessageSize: api.w.MaxMessageSize(), + } +} + +// SetMaxMessageSize sets the maximum message size that is accepted. +// Upper limit is defined by MaxMessageSize. +func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) { + return true, api.w.SetMaxMessageSize(size) +} + +// SetMinPoW sets the minimum PoW, and notifies the peers. +func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { + return true, api.w.SetMinimumPoW(pow) +} + +// SetBloomFilter sets the new value of bloom filter, and notifies the peers. +func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) { + return true, api.w.SetBloomFilter(bloom) +} + +// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages. +// Note: This function is not adding new nodes, the node needs to exists as a peer. +func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, url string) (bool, error) { + n, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + return false, err + } + return true, api.w.AllowP2PMessagesFromPeer(n.ID().Bytes()) +} + +// NewKeyPair generates a new public and private key pair for message decryption and encryption. +// It returns an ID that can be used to refer to the keypair. +func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) { + return api.w.NewKeyPair() +} + +// AddPrivateKey imports the given private key. +func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) { + key, err := crypto.ToECDSA(privateKey) + if err != nil { + return "", err + } + return api.w.AddKeyPair(key) +} + +// DeleteKeyPair removes the key with the given key if it exists. +func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) { + if ok := api.w.DeleteKeyPair(key); ok { + return true, nil + } + return false, fmt.Errorf("key pair %s not found", key) +} + +// HasKeyPair returns an indication if the node has a key pair that is associated with the given id. +func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool { + return api.w.HasKeyPair(id) +} + +// GetPublicKey returns the public key associated with the given key. The key is the hex +// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. +func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) { + key, err := api.w.GetPrivateKey(id) + if err != nil { + return hexutil.Bytes{}, err + } + return crypto.FromECDSAPub(&key.PublicKey), nil +} + +// GetPrivateKey returns the private key associated with the given key. The key is the hex +// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. +func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { + key, err := api.w.GetPrivateKey(id) + if err != nil { + return hexutil.Bytes{}, err + } + return crypto.FromECDSA(key), nil +} + +// NewSymKey generate a random symmetric key. +// It returns an ID that can be used to refer to the key. +// Can be used encrypting and decrypting messages where the key is known to both parties. +func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) { + return api.w.GenerateSymKey() +} + +// AddSymKey import a symmetric key. +// It returns an ID that can be used to refer to the key. +// Can be used encrypting and decrypting messages where the key is known to both parties. +func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) { + return api.w.AddSymKeyDirect([]byte(key)) +} + +// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID. +func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) { + return api.w.AddSymKeyFromPassword(passwd) +} + +// HasSymKey returns an indication if the node has a symmetric key associated with the given key. +func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool { + return api.w.HasSymKey(id) +} + +// GetSymKey returns the symmetric key associated with the given id. +func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) { + return api.w.GetSymKey(id) +} + +// DeleteSymKey deletes the symmetric key that is associated with the given id. +func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool { + return api.w.DeleteSymKey(id) +} + +// MakeLightClient turns the node into light client, which does not forward +// any incoming messages, and sends only messages originated in this node. +func (api *PublicWhisperAPI) MakeLightClient(ctx context.Context) bool { + api.w.SetLightClientMode(true) + return api.w.LightClientMode() +} + +// CancelLightClient cancels light client mode. +func (api *PublicWhisperAPI) CancelLightClient(ctx context.Context) bool { + api.w.SetLightClientMode(false) + return !api.w.LightClientMode() +} + +//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go + +// NewMessage represents a new whisper message that is posted through the RPC. +type NewMessage struct { + SymKeyID string `json:"symKeyID"` + PublicKey []byte `json:"pubKey"` + Sig string `json:"sig"` + TTL uint32 `json:"ttl"` + Topic TopicType `json:"topic"` + Payload []byte `json:"payload"` + Padding []byte `json:"padding"` + PowTime uint32 `json:"powTime"` + PowTarget float64 `json:"powTarget"` + TargetPeer string `json:"targetPeer"` +} + +type newMessageOverride struct { + PublicKey hexutil.Bytes + Payload hexutil.Bytes + Padding hexutil.Bytes +} + +// Post posts a message on the Whisper network. +// returns the hash of the message in case of success. +func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) { + var ( + symKeyGiven = len(req.SymKeyID) > 0 + pubKeyGiven = len(req.PublicKey) > 0 + err error + ) + + // user must specify either a symmetric or an asymmetric key + if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { + return nil, ErrSymAsym + } + + params := &MessageParams{ + TTL: req.TTL, + Payload: req.Payload, + Padding: req.Padding, + WorkTime: req.PowTime, + PoW: req.PowTarget, + Topic: req.Topic, + } + + // Set key that is used to sign the message + if len(req.Sig) > 0 { + if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { + return nil, err + } + } + + // Set symmetric key that is used to encrypt the message + if symKeyGiven { + if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption + return nil, ErrNoTopics + } + if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { + return nil, err + } + if !validateDataIntegrity(params.KeySym, aesKeyLength) { + return nil, ErrInvalidSymmetricKey + } + } + + // Set asymmetric key that is used to encrypt the message + if pubKeyGiven { + if params.Dst, err = crypto.UnmarshalPubkey(req.PublicKey); err != nil { + return nil, ErrInvalidPublicKey + } + } + + // encrypt and sent message + whisperMsg, err := NewSentMessage(params) + if err != nil { + return nil, err + } + + var result []byte + env, err := whisperMsg.Wrap(params, api.w.GetCurrentTime()) + if err != nil { + return nil, err + } + + // send to specific node (skip PoW check) + if len(req.TargetPeer) > 0 { + n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer) + if err != nil { + return nil, fmt.Errorf("failed to parse target peer: %s", err) + } + err = api.w.SendP2PMessage(n.ID().Bytes(), env) + if err == nil { + hash := env.Hash() + result = hash[:] + } + return result, err + } + + // ensure that the message PoW meets the node's minimum accepted PoW + if req.PowTarget < api.w.MinPow() { + return nil, ErrTooLowPoW + } + + err = api.w.Send(env) + if err == nil { + hash := env.Hash() + result = hash[:] + } + return result, err +} + +// UninstallFilter is alias for Unsubscribe +func (api *PublicWhisperAPI) UninstallFilter(id string) { + api.w.Unsubscribe(id) +} + +// Unsubscribe disables and removes an existing filter. +func (api *PublicWhisperAPI) Unsubscribe(id string) { + api.w.Unsubscribe(id) +} + +//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go + +// Criteria holds various filter options for inbound messages. +type Criteria struct { + SymKeyID string `json:"symKeyID"` + PrivateKeyID string `json:"privateKeyID"` + Sig []byte `json:"sig"` + MinPow float64 `json:"minPow"` + Topics []TopicType `json:"topics"` + AllowP2P bool `json:"allowP2P"` +} + +type criteriaOverride struct { + Sig hexutil.Bytes +} + +// Messages set up a subscription that fires events when messages arrive that match +// the given set of criteria. +func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) { + var ( + symKeyGiven = len(crit.SymKeyID) > 0 + pubKeyGiven = len(crit.PrivateKeyID) > 0 + err error + ) + + // ensure that the RPC connection supports subscriptions + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return nil, rpc.ErrNotificationsUnsupported + } + + // user must specify either a symmetric or an asymmetric key + if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { + return nil, ErrSymAsym + } + + filter := Filter{ + PoW: crit.MinPow, + Messages: api.w.NewMessageStore(), + AllowP2P: crit.AllowP2P, + } + + if len(crit.Sig) > 0 { + if filter.Src, err = crypto.UnmarshalPubkey(crit.Sig); err != nil { + return nil, ErrInvalidSigningPubKey + } + } + + for i, bt := range crit.Topics { + if len(bt) == 0 || len(bt) > 4 { + return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt)) + } + filter.Topics = append(filter.Topics, bt[:]) + } + + // listen for message that are encrypted with the given symmetric key + if symKeyGiven { + if len(filter.Topics) == 0 { + return nil, ErrNoTopics + } + key, err := api.w.GetSymKey(crit.SymKeyID) + if err != nil { + return nil, err + } + if !validateDataIntegrity(key, aesKeyLength) { + return nil, ErrInvalidSymmetricKey + } + filter.KeySym = key + filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) + } + + // listen for messages that are encrypted with the given public key + if pubKeyGiven { + filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID) + if err != nil || filter.KeyAsym == nil { + return nil, ErrInvalidPublicKey + } + } + + id, err := api.w.Subscribe(&filter) + if err != nil { + return nil, err + } + + // create subscription and start waiting for message events + rpcSub := notifier.CreateSubscription() + go func() { + // for now poll internally, refactor whisper internal for channel support + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if filter := api.w.GetFilter(id); filter != nil { + for _, rpcMessage := range toMessage(filter.Retrieve()) { + if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil { + log.Error("Failed to send notification", "err", err) + } + } + } + case <-rpcSub.Err(): + api.w.Unsubscribe(id) + return + case <-notifier.Closed(): + api.w.Unsubscribe(id) + return + } + } + }() + + return rpcSub, nil +} + +//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go + +// Message is the RPC representation of a whisper message. +type Message struct { + Sig []byte `json:"sig,omitempty"` + TTL uint32 `json:"ttl"` + Timestamp uint32 `json:"timestamp"` + Topic TopicType `json:"topic"` + Payload []byte `json:"payload"` + Padding []byte `json:"padding"` + PoW float64 `json:"pow"` + Hash []byte `json:"hash"` + Dst []byte `json:"recipientPublicKey,omitempty"` + P2P bool `json:"bool,omitempty"` +} + +type messageOverride struct { + Sig hexutil.Bytes + Payload hexutil.Bytes + Padding hexutil.Bytes + Hash hexutil.Bytes + Dst hexutil.Bytes +} + +// ToWhisperMessage converts an internal message into an API version. +func ToWhisperMessage(message *ReceivedMessage) *Message { + msg := Message{ + Payload: message.Payload, + Padding: message.Padding, + Timestamp: message.Sent, + TTL: message.TTL, + PoW: message.PoW, + Hash: message.EnvelopeHash.Bytes(), + Topic: message.Topic, + P2P: message.P2P, + } + + if message.Dst != nil { + b := crypto.FromECDSAPub(message.Dst) + if b != nil { + msg.Dst = b + } + } + + if isMessageSigned(message.Raw[0]) { + b := crypto.FromECDSAPub(message.SigToPubKey()) + if b != nil { + msg.Sig = b + } + } + + return &msg +} + +// toMessage converts a set of messages to its RPC representation. +func toMessage(messages []*ReceivedMessage) []*Message { + msgs := make([]*Message, len(messages)) + for i, msg := range messages { + msgs[i] = ToWhisperMessage(msg) + } + return msgs +} + +// GetFilterMessages returns the messages that match the filter criteria and +// are received between the last poll and now. +func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) { + api.mu.Lock() + f := api.w.GetFilter(id) + if f == nil { + api.mu.Unlock() + return nil, fmt.Errorf("filter not found") + } + api.lastUsed[id] = time.Now() + api.mu.Unlock() + + receivedMessages := f.Retrieve() + messages := make([]*Message, 0, len(receivedMessages)) + for _, msg := range receivedMessages { + messages = append(messages, ToWhisperMessage(msg)) + } + + return messages, nil +} + +// DeleteMessageFilter deletes a filter. +func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) { + api.mu.Lock() + defer api.mu.Unlock() + + delete(api.lastUsed, id) + return true, api.w.Unsubscribe(id) +} + +// NewMessageFilter creates a new filter that can be used to poll for +// (new) messages that satisfy the given criteria. +func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) { + var ( + src *ecdsa.PublicKey + keySym []byte + keyAsym *ecdsa.PrivateKey + topics [][]byte + + symKeyGiven = len(req.SymKeyID) > 0 + asymKeyGiven = len(req.PrivateKeyID) > 0 + + err error + ) + + // user must specify either a symmetric or an asymmetric key + if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) { + return "", ErrSymAsym + } + + if len(req.Sig) > 0 { + if src, err = crypto.UnmarshalPubkey(req.Sig); err != nil { + return "", ErrInvalidSigningPubKey + } + } + + if symKeyGiven { + if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { + return "", err + } + if !validateDataIntegrity(keySym, aesKeyLength) { + return "", ErrInvalidSymmetricKey + } + } + + if asymKeyGiven { + if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil { + return "", err + } + } + + if len(req.Topics) > 0 { + topics = make([][]byte, len(req.Topics)) + for i, topic := range req.Topics { + topics[i] = make([]byte, TopicLength) + copy(topics[i], topic[:]) + } + } + + f := &Filter{ + Src: src, + KeySym: keySym, + KeyAsym: keyAsym, + PoW: req.MinPow, + AllowP2P: req.AllowP2P, + Topics: topics, + Messages: api.w.NewMessageStore(), + } + + id, err := api.w.Subscribe(f) + if err != nil { + return "", err + } + + api.mu.Lock() + api.lastUsed[id] = time.Now() + api.mu.Unlock() + + return id, nil +} diff --git a/whisper/api_test.go b/whisper/api_test.go new file mode 100644 index 000000000..6c974e5a2 --- /dev/null +++ b/whisper/api_test.go @@ -0,0 +1,63 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "testing" + "time" +) + +func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { + w := New(nil) + + keyID, err := w.GenerateSymKey() + if err != nil { + t.Fatalf("Error generating symmetric key: %v", err) + } + api := PublicWhisperAPI{ + w: w, + lastUsed: make(map[string]time.Time), + } + + t1 := [4]byte{0xde, 0xea, 0xbe, 0xef} + t2 := [4]byte{0xca, 0xfe, 0xde, 0xca} + + crit := Criteria{ + SymKeyID: keyID, + Topics: []TopicType{TopicType(t1), TopicType(t2)}, + } + + _, err = api.NewMessageFilter(crit) + if err != nil { + t.Fatalf("Error creating the filter: %v", err) + } + + found := false + candidates := w.filters.getWatchersByTopic(TopicType(t1)) + for _, f := range candidates { + if len(f.Topics) == 2 { + if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) { + found = true + } + } + } + + if !found { + t.Fatalf("Could not find filter with both topics") + } +} diff --git a/whisper/benchmarks_test.go b/whisper/benchmarks_test.go new file mode 100644 index 000000000..58129214d --- /dev/null +++ b/whisper/benchmarks_test.go @@ -0,0 +1,209 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "crypto/sha256" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/crypto/pbkdf2" +) + +func BenchmarkDeriveKeyMaterial(b *testing.B) { + for i := 0; i < b.N; i++ { + pbkdf2.Key([]byte("test"), nil, 65356, aesKeyLength, sha256.New) + } +} + +func BenchmarkEncryptionSym(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + for i := 0; i < b.N; i++ { + msg, _ := NewSentMessage(params) + _, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Errorf("failed Wrap with seed %d: %s.", seed, err) + b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload)) + return + } + } +} + +func BenchmarkEncryptionAsym(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + key, err := crypto.GenerateKey() + if err != nil { + b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + params.KeySym = nil + params.Dst = &key.PublicKey + + for i := 0; i < b.N; i++ { + msg, _ := NewSentMessage(params) + _, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + } +} + +func BenchmarkDecryptionSymValid(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, _ := NewSentMessage(params) + env, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + f := Filter{KeySym: params.KeySym} + + for i := 0; i < b.N; i++ { + msg := env.Open(&f) + if msg == nil { + b.Fatalf("failed to open with seed %d.", seed) + } + } +} + +func BenchmarkDecryptionSymInvalid(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, _ := NewSentMessage(params) + env, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + f := Filter{KeySym: []byte("arbitrary stuff here")} + + for i := 0; i < b.N; i++ { + msg := env.Open(&f) + if msg != nil { + b.Fatalf("opened envelope with invalid key, seed: %d.", seed) + } + } +} + +func BenchmarkDecryptionAsymValid(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + key, err := crypto.GenerateKey() + if err != nil { + b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + f := Filter{KeyAsym: key} + params.KeySym = nil + params.Dst = &key.PublicKey + msg, _ := NewSentMessage(params) + env, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + for i := 0; i < b.N; i++ { + msg := env.Open(&f) + if msg == nil { + b.Fatalf("fail to open, seed: %d.", seed) + } + } +} + +func BenchmarkDecryptionAsymInvalid(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + key, err := crypto.GenerateKey() + if err != nil { + b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + params.KeySym = nil + params.Dst = &key.PublicKey + msg, _ := NewSentMessage(params) + env, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + key, err = crypto.GenerateKey() + if err != nil { + b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + f := Filter{KeyAsym: key} + + for i := 0; i < b.N; i++ { + msg := env.Open(&f) + if msg != nil { + b.Fatalf("opened envelope with invalid key, seed: %d.", seed) + } + } +} + +func increment(x []byte) { + for i := 0; i < len(x); i++ { + x[i]++ + if x[i] != 0 { + break + } + } +} + +func BenchmarkPoW(b *testing.B) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params.Payload = make([]byte, 32) + params.PoW = 10.0 + params.TTL = 1 + + for i := 0; i < b.N; i++ { + increment(params.Payload) + msg, _ := NewSentMessage(params) + _, err := msg.Wrap(params, time.Now()) + if err != nil { + b.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + } +} diff --git a/whisper/config.go b/whisper/config.go new file mode 100644 index 000000000..1b5adf7b2 --- /dev/null +++ b/whisper/config.go @@ -0,0 +1,32 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +// Config represents the configuration state of a whisper node. +type Config struct { + MaxMessageSize uint32 `toml:",omitempty"` + MinimumAcceptedPOW float64 `toml:",omitempty"` + RestrictConnectionBetweenLightClients bool `toml:",omitempty"` + DisableConfirmations bool `toml:",omitempty"` +} + +// DefaultConfig represents (shocker!) the default configuration. +var DefaultConfig = Config{ + MaxMessageSize: DefaultMaxMessageSize, + MinimumAcceptedPOW: DefaultMinimumPoW, + RestrictConnectionBetweenLightClients: true, +} diff --git a/whisper/doc.go b/whisper/doc.go new file mode 100644 index 000000000..15879ffa9 --- /dev/null +++ b/whisper/doc.go @@ -0,0 +1,261 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* +Package whisper implements the Whisper protocol (version 6). + +Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP). +As such it may be likened and compared to both, not dissimilar to the +matter/energy duality (apologies to physicists for the blatant abuse of a +fundamental and beautiful natural principle). + +Whisper is a pure identity-based messaging system. Whisper provides a low-level +(non-application-specific) but easily-accessible API without being based upon +or prejudiced by the low-level hardware attributes and characteristics, +particularly the notion of singular endpoints. +*/ + +// Contains the Whisper protocol constant definitions + +package whisper + +import ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +// Whisper protocol parameters +const ( + ProtocolVersion = uint64(6) // Protocol version number + ProtocolVersionStr = "6.0" // The same, as a string + ProtocolName = "shh" // Nickname of the protocol in geth + + // whisper protocol message codes, according to EIP-627 + statusCode = 0 // used by whisper protocol + messagesCode = 1 // normal whisper message + powRequirementCode = 2 // PoW requirement + bloomFilterExCode = 3 // bloom filter exchange + batchAcknowledgedCode = 11 // confirmation that batch of envelopes was received + messageResponseCode = 12 // includes confirmation for delivery and information about errors + p2pSyncRequestCode = 123 // used to sync envelopes between two mail servers + p2pSyncResponseCode = 124 // used to sync envelopes between two mail servers + p2pRequestCompleteCode = 125 // peer-to-peer message, used by Dapp protocol + p2pRequestCode = 126 // peer-to-peer message, used by Dapp protocol + p2pMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further) + NumberOfMessageCodes = 128 + + SizeMask = byte(3) // mask used to extract the size of payload size field from the flags + signatureFlag = byte(4) + + TopicLength = 4 // in bytes + signatureLength = crypto.SignatureLength // in bytes + aesKeyLength = 32 // in bytes + aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize() + keyIDSize = 32 // in bytes + BloomFilterSize = 64 // in bytes + flagsLength = 1 + + EnvelopeHeaderLength = 20 + + MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message. + DefaultMaxMessageSize = uint32(1024 * 1024) + DefaultMinimumPoW = 0.2 + + padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol + messageQueueLimit = 1024 + + expirationCycle = time.Second + transmissionCycle = 300 * time.Millisecond + + DefaultTTL = 50 // seconds + DefaultSyncAllowance = 10 // seconds + + MaxLimitInSyncMailRequest = 1000 + + EnvelopeTimeNotSynced uint = iota + 1 + EnvelopeOtherError + + MaxLimitInMessagesRequest = 1000 +) + +// MailServer represents a mail server, capable of +// archiving the old messages for subsequent delivery +// to the peers. Any implementation must ensure that both +// functions are thread-safe. Also, they must return ASAP. +// DeliverMail should use directMessagesCode for delivery, +// in order to bypass the expiry checks. +type MailServer interface { + Archive(env *Envelope) + // DEPRECATED + DeliverMail(whisperPeer *Peer, request *Envelope) + Deliver(whisperPeer *Peer, request MessagesRequest) + SyncMail(*Peer, SyncMailRequest) error +} + +// MessagesRequest contains details of a request of historic messages. +type MessagesRequest struct { + // ID of the request. The current implementation requires ID to be 32-byte array, + // however, it's not enforced for future implementation. + ID []byte `json:"id"` + + // From is a lower bound of time range. + From uint32 `json:"from"` + + // To is a upper bound of time range. + To uint32 `json:"to"` + + // Limit determines the number of messages sent by the mail server + // for the current paginated request. + Limit uint32 `json:"limit"` + + // Cursor is used as starting point for paginated requests. + Cursor []byte `json:"cursor"` + + // Bloom is a filter to match requested messages. + Bloom []byte `json:"bloom"` +} + +func (r MessagesRequest) Validate() error { + if len(r.ID) != common.HashLength { + return errors.New("invalid 'ID', expected a 32-byte slice") + } + + if r.From > r.To { + return errors.New("invalid 'From' value which is greater than To") + } + + if r.Limit > MaxLimitInMessagesRequest { + return fmt.Errorf("invalid 'Limit' value, expected value lower than %d", MaxLimitInMessagesRequest) + } + + if len(r.Bloom) == 0 { + return errors.New("invalid 'Bloom' provided") + } + + return nil +} + +// SyncMailRequest contains details which envelopes should be synced +// between Mail Servers. +type SyncMailRequest struct { + // Lower is a lower bound of time range for which messages are requested. + Lower uint32 + // Upper is a lower bound of time range for which messages are requested. + Upper uint32 + // Bloom is a bloom filter to filter envelopes. + Bloom []byte + // Limit is the max number of envelopes to return. + Limit uint32 + // Cursor is used for pagination of the results. + Cursor []byte +} + +// Validate checks request's fields if they are valid. +func (r SyncMailRequest) Validate() error { + if r.Limit == 0 { + return errors.New("invalid 'Limit' value, expected value greater than 0") + } + + if r.Limit > MaxLimitInSyncMailRequest { + return fmt.Errorf("invalid 'Limit' value, expected value lower than %d", MaxLimitInSyncMailRequest) + } + + if r.Lower > r.Upper { + return errors.New("invalid 'Lower' value, can't be greater than 'Upper'") + } + + return nil +} + +// SyncResponse is a struct representing a response sent to the peer +// asking for syncing archived envelopes. +type SyncResponse struct { + Envelopes []*Envelope + Cursor []byte + Final bool // if true it means all envelopes were processed + Error string +} + +// RawSyncResponse is a struct representing a response sent to the peer +// asking for syncing archived envelopes. +type RawSyncResponse struct { + Envelopes []rlp.RawValue + Cursor []byte + Final bool // if true it means all envelopes were processed + Error string +} + +// MessagesResponse sent as a response after processing batch of envelopes. +type MessagesResponse struct { + // Hash is a hash of all envelopes sent in the single batch. + Hash common.Hash + // Per envelope error. + Errors []EnvelopeError +} + +// EnvelopeError code and optional description of the error. +type EnvelopeError struct { + Hash common.Hash + Code uint + Description string +} + +// MultiVersionResponse allows to decode response into chosen version. +type MultiVersionResponse struct { + Version uint + Response rlp.RawValue +} + +// DecodeResponse1 decodes response into first version of the messages response. +func (m MultiVersionResponse) DecodeResponse1() (resp MessagesResponse, err error) { + return resp, rlp.DecodeBytes(m.Response, &resp) +} + +// Version1MessageResponse first version of the message response. +type Version1MessageResponse struct { + Version uint + Response MessagesResponse +} + +// NewMessagesResponse returns instane of the version messages response. +func NewMessagesResponse(batch common.Hash, errors []EnvelopeError) Version1MessageResponse { + return Version1MessageResponse{ + Version: 1, + Response: MessagesResponse{ + Hash: batch, + Errors: errors, + }, + } +} + +// ErrorToEnvelopeError converts common golang error into EnvelopeError with a code. +func ErrorToEnvelopeError(hash common.Hash, err error) EnvelopeError { + code := EnvelopeOtherError + switch err.(type) { + case TimeSyncError: + code = EnvelopeTimeNotSynced + } + return EnvelopeError{ + Hash: hash, + Code: code, + Description: err.Error(), + } +} diff --git a/whisper/doc_test.go b/whisper/doc_test.go new file mode 100644 index 000000000..1322d5e0c --- /dev/null +++ b/whisper/doc_test.go @@ -0,0 +1,56 @@ +package whisper + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSyncMailRequestValidate(t *testing.T) { + testCases := []struct { + Name string + Req SyncMailRequest + Error string + }{ + { + Name: "invalid zero Limit", + Req: SyncMailRequest{}, + Error: "invalid 'Limit' value, expected value greater than 0", + }, + { + Name: "invalid large Limit", + Req: SyncMailRequest{Limit: 1e6}, + Error: "invalid 'Limit' value, expected value lower than 1000", + }, + { + Name: "invalid Lower", + Req: SyncMailRequest{Limit: 10, Lower: 10, Upper: 5}, + Error: "invalid 'Lower' value, can't be greater than 'Upper'", + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + err := tc.Req.Validate() + if tc.Error != "" { + assert.EqualError(t, err, tc.Error) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestEncodeDecodeVersionedResponse(t *testing.T) { + response := NewMessagesResponse(common.Hash{1}, []EnvelopeError{{Code: 1}}) + bytes, err := rlp.EncodeToBytes(response) + require.NoError(t, err) + var mresponse MultiVersionResponse + require.NoError(t, rlp.DecodeBytes(bytes, &mresponse)) + v1resp, err := mresponse.DecodeResponse1() + require.NoError(t, err) + require.Equal(t, response.Response.Hash, v1resp.Hash) +} diff --git a/whisper/envelope.go b/whisper/envelope.go new file mode 100644 index 000000000..3bef71209 --- /dev/null +++ b/whisper/envelope.go @@ -0,0 +1,280 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the Whisper protocol Envelope element. + +package whisper + +import ( + "crypto/ecdsa" + "encoding/binary" + "fmt" + gmath "math" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ethereum/go-ethereum/rlp" +) + +// Envelope represents a clear-text data packet to transmit through the Whisper +// network. Its contents may or may not be encrypted and signed. +type Envelope struct { + Expiry uint32 + TTL uint32 + Topic TopicType + Data []byte + Nonce uint64 + + pow float64 // Message-specific PoW as described in the Whisper specification. + + // the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom() + hash common.Hash // Cached hash of the envelope to avoid rehashing every time. + bloom []byte +} + +// size returns the size of envelope as it is sent (i.e. public fields only) +func (e *Envelope) size() int { + return EnvelopeHeaderLength + len(e.Data) +} + +// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce. +func (e *Envelope) rlpWithoutNonce() []byte { + res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data}) + return res +} + +// NewEnvelope wraps a Whisper message with expiration and destination data +// included into an envelope for network forwarding. +func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage, now time.Time) *Envelope { + env := Envelope{ + Expiry: uint32(now.Add(time.Second * time.Duration(ttl)).Unix()), + TTL: ttl, + Topic: topic, + Data: msg.Raw, + Nonce: 0, + } + + return &env +} + +// Seal closes the envelope by spending the requested amount of time as a proof +// of work on hashing the data. +func (e *Envelope) Seal(options *MessageParams) error { + if options.PoW == 0 { + // PoW is not required + return nil + } + + var target, bestLeadingZeros int + if options.PoW < 0 { + // target is not set - the function should run for a period + // of time specified in WorkTime param. Since we can predict + // the execution time, we can also adjust Expiry. + e.Expiry += options.WorkTime + } else { + target = e.powToFirstBit(options.PoW) + } + + rlp := e.rlpWithoutNonce() + buf := make([]byte, len(rlp)+8) + copy(buf, rlp) + asAnInt := new(big.Int) + + finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano() + for nonce := uint64(0); time.Now().UnixNano() < finish; { + for i := 0; i < 1024; i++ { + binary.BigEndian.PutUint64(buf[len(rlp):], nonce) + h := crypto.Keccak256(buf) + asAnInt.SetBytes(h) + leadingZeros := 256 - asAnInt.BitLen() + if leadingZeros > bestLeadingZeros { + e.Nonce, bestLeadingZeros = nonce, leadingZeros + if target > 0 && bestLeadingZeros >= target { + return nil + } + } + nonce++ + } + } + + if target > 0 && bestLeadingZeros < target { + return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime) + } + + return nil +} + +// PoW computes (if necessary) and returns the proof of work target +// of the envelope. +func (e *Envelope) PoW() float64 { + if e.pow == 0 { + e.calculatePoW(0) + } + return e.pow +} + +func (e *Envelope) calculatePoW(diff uint32) { + rlp := e.rlpWithoutNonce() + buf := make([]byte, len(rlp)+8) + copy(buf, rlp) + binary.BigEndian.PutUint64(buf[len(rlp):], e.Nonce) + powHash := new(big.Int).SetBytes(crypto.Keccak256(buf)) + leadingZeroes := 256 - powHash.BitLen() + x := gmath.Pow(2, float64(leadingZeroes)) + x /= float64(len(rlp)) + x /= float64(e.TTL + diff) + e.pow = x +} + +func (e *Envelope) powToFirstBit(pow float64) int { + x := pow + x *= float64(e.size()) + x *= float64(e.TTL) + bits := gmath.Log2(x) + bits = gmath.Ceil(bits) + res := int(bits) + if res < 1 { + res = 1 + } + return res +} + +// Hash returns the SHA3 hash of the envelope, calculating it if not yet done. +func (e *Envelope) Hash() common.Hash { + if (e.hash == common.Hash{}) { + encoded, _ := rlp.EncodeToBytes(e) + e.hash = crypto.Keccak256Hash(encoded) + } + return e.hash +} + +// DecodeRLP decodes an Envelope from an RLP data stream. +func (e *Envelope) DecodeRLP(s *rlp.Stream) error { + raw, err := s.Raw() + if err != nil { + return err + } + // The decoding of Envelope uses the struct fields but also needs + // to compute the hash of the whole RLP-encoded envelope. This + // type has the same structure as Envelope but is not an + // rlp.Decoder (does not implement DecodeRLP function). + // Only public members will be encoded. + type rlpenv Envelope + if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil { + return err + } + e.hash = crypto.Keccak256Hash(raw) + return nil +} + +// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key. +func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) { + message := &ReceivedMessage{Raw: e.Data} + err := message.decryptAsymmetric(key) + switch err { + case nil: + return message, nil + case ecies.ErrInvalidPublicKey: // addressed to somebody else + return nil, err + default: + return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err) + } +} + +// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key. +func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { + msg = &ReceivedMessage{Raw: e.Data} + err = msg.decryptSymmetric(key) + if err != nil { + msg = nil + } + return msg, err +} + +// Open tries to decrypt an envelope, and populates the message fields in case of success. +func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) { + if watcher == nil { + return nil + } + + // The API interface forbids filters doing both symmetric and asymmetric encryption. + if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() { + return nil + } + + if watcher.expectsAsymmetricEncryption() { + msg, _ = e.OpenAsymmetric(watcher.KeyAsym) + if msg != nil { + msg.Dst = &watcher.KeyAsym.PublicKey + } + } else if watcher.expectsSymmetricEncryption() { + msg, _ = e.OpenSymmetric(watcher.KeySym) + if msg != nil { + msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) + } + } + + if msg != nil { + ok := msg.ValidateAndParse() + if !ok { + return nil + } + msg.Topic = e.Topic + msg.PoW = e.PoW() + msg.TTL = e.TTL + msg.Sent = e.Expiry - e.TTL + msg.EnvelopeHash = e.Hash() + } + return msg +} + +// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most). +func (e *Envelope) Bloom() []byte { + if e.bloom == nil { + e.bloom = TopicToBloom(e.Topic) + } + return e.bloom +} + +// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes) +func TopicToBloom(topic TopicType) []byte { + b := make([]byte, BloomFilterSize) + var index [3]int + for j := 0; j < 3; j++ { + index[j] = int(topic[j]) + if (topic[3] & (1 << uint(j))) != 0 { + index[j] += 256 + } + } + + for j := 0; j < 3; j++ { + byteIndex := index[j] / 8 + bitIndex := index[j] % 8 + b[byteIndex] = (1 << uint(bitIndex)) + } + return b +} + +// GetEnvelope retrieves an envelope from the message queue by its hash. +// It returns nil if the envelope can not be found. +func (w *Whisper) GetEnvelope(hash common.Hash) *Envelope { + w.poolMu.RLock() + defer w.poolMu.RUnlock() + return w.envelopes[hash] +} diff --git a/whisper/envelope_test.go b/whisper/envelope_test.go new file mode 100644 index 000000000..db7339ede --- /dev/null +++ b/whisper/envelope_test.go @@ -0,0 +1,92 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the tests associated with the Whisper protocol Envelope object. + +package whisper + +import ( + mrand "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestPoWCalculationsWithNoLeadingZeros(t *testing.T) { + e := Envelope{ + TTL: 1, + Data: []byte{0xde, 0xad, 0xbe, 0xef}, + Nonce: 100000, + } + + e.calculatePoW(0) + + if e.pow != 0.07692307692307693 { + t.Fatalf("invalid PoW calculation. Expected 0.07692307692307693, got %v", e.pow) + } +} + +func TestPoWCalculationsWith8LeadingZeros(t *testing.T) { + e := Envelope{ + TTL: 1, + Data: []byte{0xde, 0xad, 0xbe, 0xef}, + Nonce: 276, + } + e.calculatePoW(0) + + if e.pow != 19.692307692307693 { + t.Fatalf("invalid PoW calculation. Expected 19.692307692307693, got %v", e.pow) + } +} + +func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) { + symKey := make([]byte, aesKeyLength) + mrand.Read(symKey) + + asymKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + + params := MessageParams{ + PoW: 0.01, + WorkTime: 1, + TTL: uint32(mrand.Intn(1024)), + Payload: make([]byte, 50), + KeySym: symKey, + Dst: nil, + } + + mrand.Read(params.Payload) + + msg, err := NewSentMessage(¶ms) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + + e, err := msg.Wrap(¶ms, time.Now()) + if err != nil { + t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err) + } + + f := Filter{KeySym: symKey, KeyAsym: asymKey} + + decrypted := e.Open(&f) + if decrypted != nil { + t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed) + } +} diff --git a/whisper/events.go b/whisper/events.go new file mode 100644 index 000000000..9de7b846e --- /dev/null +++ b/whisper/events.go @@ -0,0 +1,52 @@ +package whisper + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// EventType used to define known envelope events. +type EventType string + +const ( + // EventEnvelopeSent fires when envelope was sent to a peer. + EventEnvelopeSent EventType = "envelope.sent" + // EventEnvelopeExpired fires when envelop expired + EventEnvelopeExpired EventType = "envelope.expired" + // EventEnvelopeReceived is sent once envelope was received from a peer. + // EventEnvelopeReceived must be sent to the feed even if envelope was previously in the cache. + // And event, ideally, should contain information about peer that sent envelope to us. + EventEnvelopeReceived EventType = "envelope.received" + // EventBatchAcknowledged is sent when batch of envelopes was acknowleged by a peer. + EventBatchAcknowledged EventType = "batch.acknowleged" + // EventEnvelopeAvailable fires when envelop is available for filters + EventEnvelopeAvailable EventType = "envelope.available" + // EventMailServerRequestSent fires when such request is sent. + EventMailServerRequestSent EventType = "mailserver.request.sent" + // EventMailServerRequestCompleted fires after mailserver sends all the requested messages + EventMailServerRequestCompleted EventType = "mailserver.request.completed" + // EventMailServerRequestExpired fires after mailserver the request TTL ends. + // This event is independent and concurrent to EventMailServerRequestCompleted. + // Request should be considered as expired only if expiry event was received first. + EventMailServerRequestExpired EventType = "mailserver.request.expired" + // EventMailServerEnvelopeArchived fires after an envelope has been archived + EventMailServerEnvelopeArchived EventType = "mailserver.envelope.archived" + // EventMailServerSyncFinished fires when the sync of messages is finished. + EventMailServerSyncFinished EventType = "mailserver.sync.finished" +) + +// EnvelopeEvent used for envelopes events. +type EnvelopeEvent struct { + Event EventType + Hash common.Hash + Batch common.Hash + Peer enode.ID + Data interface{} +} + +// SyncEventResponse is a response from the Mail Server +// form which the peer received envelopes. +type SyncEventResponse struct { + Cursor []byte + Error string +} diff --git a/whisper/filter.go b/whisper/filter.go new file mode 100644 index 000000000..9bdc7742c --- /dev/null +++ b/whisper/filter.go @@ -0,0 +1,294 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "crypto/ecdsa" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +// MessageStore defines interface for temporary message store. +type MessageStore interface { + Add(*ReceivedMessage) error + Pop() ([]*ReceivedMessage, error) +} + +// NewMemoryMessageStore returns pointer to an instance of the MemoryMessageStore. +func NewMemoryMessageStore() *MemoryMessageStore { + return &MemoryMessageStore{ + messages: map[common.Hash]*ReceivedMessage{}, + } +} + +// MemoryMessageStore stores massages in memory hash table. +type MemoryMessageStore struct { + mu sync.Mutex + messages map[common.Hash]*ReceivedMessage +} + +// Add adds message to store. +func (store *MemoryMessageStore) Add(msg *ReceivedMessage) error { + store.mu.Lock() + defer store.mu.Unlock() + if _, exist := store.messages[msg.EnvelopeHash]; !exist { + store.messages[msg.EnvelopeHash] = msg + } + return nil +} + +// Pop returns all avaiable messages and cleans the store. +func (store *MemoryMessageStore) Pop() ([]*ReceivedMessage, error) { + store.mu.Lock() + defer store.mu.Unlock() + all := make([]*ReceivedMessage, 0, len(store.messages)) + for hash, msg := range store.messages { + delete(store.messages, hash) + all = append(all, msg) + } + return all, nil +} + +// Filter represents a Whisper message filter +type Filter struct { + Src *ecdsa.PublicKey // Sender of the message + KeyAsym *ecdsa.PrivateKey // Private Key of recipient + KeySym []byte // Key associated with the Topic + Topics [][]byte // Topics to filter messages with + PoW float64 // Proof of work as described in the Whisper spec + AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages + SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization + id string // unique identifier + + Messages MessageStore + mutex sync.RWMutex +} + +// Filters represents a collection of filters +type Filters struct { + watchers map[string]*Filter + + topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic + allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is + + whisper *Whisper + mutex sync.RWMutex +} + +// NewFilters returns a newly created filter collection +func NewFilters(w *Whisper) *Filters { + return &Filters{ + watchers: make(map[string]*Filter), + topicMatcher: make(map[TopicType]map[*Filter]struct{}), + allTopicsMatcher: make(map[*Filter]struct{}), + whisper: w, + } +} + +// Install will add a new filter to the filter collection +func (fs *Filters) Install(watcher *Filter) (string, error) { + if watcher.KeySym != nil && watcher.KeyAsym != nil { + return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys") + } + + id, err := GenerateRandomID() + if err != nil { + return "", err + } + + fs.mutex.Lock() + defer fs.mutex.Unlock() + + if fs.watchers[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") + } + + if watcher.expectsSymmetricEncryption() { + watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) + } + + watcher.id = id + fs.watchers[id] = watcher + fs.addTopicMatcher(watcher) + return id, err +} + +// Uninstall will remove a filter whose id has been specified from +// the filter collection +func (fs *Filters) Uninstall(id string) bool { + fs.mutex.Lock() + defer fs.mutex.Unlock() + if fs.watchers[id] != nil { + fs.removeFromTopicMatchers(fs.watchers[id]) + delete(fs.watchers, id) + return true + } + return false +} + +// addTopicMatcher adds a filter to the topic matchers. +// If the filter's Topics array is empty, it will be tried on every topic. +// Otherwise, it will be tried on the topics specified. +func (fs *Filters) addTopicMatcher(watcher *Filter) { + if len(watcher.Topics) == 0 { + fs.allTopicsMatcher[watcher] = struct{}{} + } else { + for _, t := range watcher.Topics { + topic := BytesToTopic(t) + if fs.topicMatcher[topic] == nil { + fs.topicMatcher[topic] = make(map[*Filter]struct{}) + } + fs.topicMatcher[topic][watcher] = struct{}{} + } + } +} + +// removeFromTopicMatchers removes a filter from the topic matchers +func (fs *Filters) removeFromTopicMatchers(watcher *Filter) { + delete(fs.allTopicsMatcher, watcher) + for _, topic := range watcher.Topics { + delete(fs.topicMatcher[BytesToTopic(topic)], watcher) + } +} + +// getWatchersByTopic returns a slice containing the filters that +// match a specific topic +func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter { + res := make([]*Filter, 0, len(fs.allTopicsMatcher)) + for watcher := range fs.allTopicsMatcher { + res = append(res, watcher) + } + for watcher := range fs.topicMatcher[topic] { + res = append(res, watcher) + } + return res +} + +// Get returns a filter from the collection with a specific ID +func (fs *Filters) Get(id string) *Filter { + fs.mutex.RLock() + defer fs.mutex.RUnlock() + return fs.watchers[id] +} + +// NotifyWatchers notifies any filter that has declared interest +// for the envelope's topic. +func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { + var msg *ReceivedMessage + + fs.mutex.RLock() + defer fs.mutex.RUnlock() + + candidates := fs.getWatchersByTopic(env.Topic) + for _, watcher := range candidates { + if p2pMessage && !watcher.AllowP2P { + log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id)) + continue + } + + var match bool + if msg != nil { + match = watcher.MatchMessage(msg) + } else { + match = watcher.MatchEnvelope(env) + if match { + msg = env.Open(watcher) + if msg == nil { + log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id) + } + } else { + log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id) + } + } + + if match && msg != nil { + msg.P2P = p2pMessage + log.Trace("processing message: decrypted", "hash", env.Hash().Hex()) + if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) { + watcher.Trigger(msg) + } + } + } +} + +func (f *Filter) expectsAsymmetricEncryption() bool { + return f.KeyAsym != nil +} + +func (f *Filter) expectsSymmetricEncryption() bool { + return f.KeySym != nil +} + +// Trigger adds a yet-unknown message to the filter's list of +// received messages. +func (f *Filter) Trigger(msg *ReceivedMessage) { + err := f.Messages.Add(msg) + if err != nil { + log.Error("failed to add msg into the filters store", "hash", msg.EnvelopeHash, "error", err) + } +} + +// Retrieve will return the list of all received messages associated +// to a filter. +func (f *Filter) Retrieve() []*ReceivedMessage { + msgs, err := f.Messages.Pop() + if err != nil { + log.Error("failed to retrieve messages from filter store", "error", err) + return nil + } + return msgs +} + +// MatchMessage checks if the filter matches an already decrypted +// message (i.e. a Message that has already been handled by +// MatchEnvelope when checked by a previous filter). +// Topics are not checked here, since this is done by topic matchers. +func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { + if f.PoW > 0 && msg.PoW < f.PoW { + return false + } + + if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() { + return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) + } else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() { + return f.SymKeyHash == msg.SymKeyHash + } + return false +} + +// MatchEnvelope checks if it's worth decrypting the message. If +// it returns `true`, client code is expected to attempt decrypting +// the message and subsequently call MatchMessage. +// Topics are not checked here, since this is done by topic matchers. +func (f *Filter) MatchEnvelope(envelope *Envelope) bool { + return f.PoW <= 0 || envelope.pow >= f.PoW +} + +// IsPubKeyEqual checks that two public keys are equal +func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { + if !ValidatePublicKey(a) { + return false + } else if !ValidatePublicKey(b) { + return false + } + // the curve is always the same, just compare the points + return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 +} diff --git a/whisper/filter_test.go b/whisper/filter_test.go new file mode 100644 index 000000000..6d9d21190 --- /dev/null +++ b/whisper/filter_test.go @@ -0,0 +1,831 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "math/big" + mrand "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var seed int64 + +// InitSingleTest should be called in the beginning of every +// test, which uses RNG, in order to make the tests +// reproduciblity independent of their sequence. +func InitSingleTest() { + seed = time.Now().Unix() + mrand.Seed(seed) +} + +func InitDebugTest(i int64) { + seed = i + mrand.Seed(seed) +} + +type FilterTestCase struct { + f *Filter + id string + alive bool + msgCnt int +} + +func generateFilter(t *testing.T, symmetric bool) (*Filter, error) { + var f Filter + f.Messages = NewMemoryMessageStore() + + const topicNum = 8 + f.Topics = make([][]byte, topicNum) + for i := 0; i < topicNum; i++ { + f.Topics[i] = make([]byte, 4) + mrand.Read(f.Topics[i]) + f.Topics[i][0] = 0x01 + } + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("generateFilter 1 failed with seed %d.", seed) + return nil, err + } + f.Src = &key.PublicKey + + if symmetric { + f.KeySym = make([]byte, aesKeyLength) + mrand.Read(f.KeySym) + f.SymKeyHash = crypto.Keccak256Hash(f.KeySym) + } else { + f.KeyAsym, err = crypto.GenerateKey() + if err != nil { + t.Fatalf("generateFilter 2 failed with seed %d.", seed) + return nil, err + } + } + + // AcceptP2P & PoW are not set + return &f, nil +} + +func generateTestCases(t *testing.T, SizeTestFilters int) []FilterTestCase { + cases := make([]FilterTestCase, SizeTestFilters) + for i := 0; i < SizeTestFilters; i++ { + f, _ := generateFilter(t, true) + cases[i].f = f + cases[i].alive = mrand.Int()&int(1) == 0 + } + return cases +} + +func TestInstallFilters(t *testing.T) { + InitSingleTest() + + const SizeTestFilters = 256 + w := New(&Config{}) + filters := NewFilters(w) + tst := generateTestCases(t, SizeTestFilters) + + var err error + var j string + for i := 0; i < SizeTestFilters; i++ { + j, err = filters.Install(tst[i].f) + if err != nil { + t.Fatalf("seed %d: failed to install filter: %s", seed, err) + } + tst[i].id = j + if len(j) != keyIDSize*2 { + t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j)) + } + } + + for _, testCase := range tst { + if !testCase.alive { + filters.Uninstall(testCase.id) + } + } + + for i, testCase := range tst { + fil := filters.Get(testCase.id) + exist := fil != nil + if exist != testCase.alive { + t.Fatalf("seed %d: failed alive: %d, %v, %v", seed, i, exist, testCase.alive) + } + if exist && fil.PoW != testCase.f.PoW { + t.Fatalf("seed %d: failed Get: %d, %v, %v", seed, i, exist, testCase.alive) + } + } +} + +func TestInstallSymKeyGeneratesHash(t *testing.T) { + InitSingleTest() + + w := New(&Config{}) + filters := NewFilters(w) + filter, _ := generateFilter(t, true) + + // save the current SymKeyHash for comparison + initialSymKeyHash := filter.SymKeyHash + + // ensure the SymKeyHash is invalid, for Install to recreate it + var invalid common.Hash + filter.SymKeyHash = invalid + + _, err := filters.Install(filter) + + if err != nil { + t.Fatalf("Error installing the filter: %s", err) + } + + for i, b := range filter.SymKeyHash { + if b != initialSymKeyHash[i] { + t.Fatalf("The filter's symmetric key hash was not properly generated by Install") + } + } +} + +func TestInstallIdenticalFilters(t *testing.T) { + InitSingleTest() + + w := New(&Config{}) + filters := NewFilters(w) + filter1, _ := generateFilter(t, true) + + // Copy the first filter since some of its fields + // are randomly gnerated. + filter2 := &Filter{ + KeySym: filter1.KeySym, + Topics: filter1.Topics, + PoW: filter1.PoW, + AllowP2P: filter1.AllowP2P, + Messages: NewMemoryMessageStore(), + } + + _, err := filters.Install(filter1) + + if err != nil { + t.Fatalf("Error installing the first filter with seed %d: %s", seed, err) + } + + _, err = filters.Install(filter2) + + if err != nil { + t.Fatalf("Error installing the second filter with seed %d: %s", seed, err) + } + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("Error generating message parameters with seed %d: %s", seed, err) + } + + params.KeySym = filter1.KeySym + params.Topic = BytesToTopic(filter1.Topics[0]) + + filter1.Src = ¶ms.Src.PublicKey + filter2.Src = ¶ms.Src.PublicKey + + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := sentMessage.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + msg := env.Open(filter1) + if msg == nil { + t.Fatalf("failed to Open with filter1") + } + + if !filter1.MatchEnvelope(env) { + t.Fatalf("failed matching with the first filter") + } + + if !filter2.MatchEnvelope(env) { + t.Fatalf("failed matching with the first filter") + } + + if !filter1.MatchMessage(msg) { + t.Fatalf("failed matching with the second filter") + } + + if !filter2.MatchMessage(msg) { + t.Fatalf("failed matching with the second filter") + } +} + +func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { + InitSingleTest() + + w := New(&Config{}) + filters := NewFilters(w) + filter1, _ := generateFilter(t, true) + + asymKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("Unable to create asymetric keys: %v", err) + } + + // Copy the first filter since some of its fields + // are randomly gnerated. + filter := &Filter{ + KeySym: filter1.KeySym, + KeyAsym: asymKey, + Topics: filter1.Topics, + PoW: filter1.PoW, + AllowP2P: filter1.AllowP2P, + Messages: NewMemoryMessageStore(), + } + + _, err = filters.Install(filter) + + if err == nil { + t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed) + } +} + +func TestComparePubKey(t *testing.T) { + InitSingleTest() + + key1, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate first key with seed %d: %s.", seed, err) + } + key2, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate second key with seed %d: %s.", seed, err) + } + if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) { + t.Fatalf("public keys are equal, seed %d.", seed) + } + + // generate key3 == key1 + mrand.Seed(seed) + key3, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate third key with seed %d: %s.", seed, err) + } + if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) { + t.Fatalf("key1 == key3, seed %d.", seed) + } +} + +func TestMatchEnvelope(t *testing.T) { + InitSingleTest() + + fsym, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) + } + + fasym, err := generateFilter(t, false) + if err != nil { + t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err) + } + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + params.Topic[0] = 0xFF // topic mismatch + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + // encrypt symmetrically + i := mrand.Int() % 4 + fsym.Topics[i] = params.Topic[:] + fasym.Topics[i] = params.Topic[:] + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err = msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) + } + + // symmetric + matching topic: match + match := fsym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed) + } + + // symmetric + matching topic + insufficient PoW: mismatch + fsym.PoW = env.PoW() + 1.0 + match = fsym.MatchEnvelope(env) + if match { + t.Fatalf("failed MatchEnvelope(symmetric + matching topic + insufficient PoW) asymmetric with seed %d.", seed) + } + + // symmetric + matching topic + sufficient PoW: match + fsym.PoW = env.PoW() / 2 + match = fsym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(symmetric + matching topic + sufficient PoW) with seed %d.", seed) + } + + // symmetric + topics are nil (wildcard): match + prevTopics := fsym.Topics + fsym.Topics = nil + match = fsym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(symmetric + topics are nil) with seed %d.", seed) + } + fsym.Topics = prevTopics + + // encrypt asymmetrically + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + params.KeySym = nil + params.Dst = &key.PublicKey + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err = msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) + } + + // encryption method mismatch + match = fsym.MatchEnvelope(env) + if match { + t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) + } + + // asymmetric + mismatching topic: mismatch + match = fasym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(asymmetric + mismatching topic) with seed %d.", seed) + } + + // asymmetric + matching topic: match + fasym.Topics[i] = fasym.Topics[i+1] + match = fasym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(asymmetric + matching topic) with seed %d.", seed) + } + + // asymmetric + filter without topic (wildcard): match + fasym.Topics = nil + match = fasym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(asymmetric + filter without topic) with seed %d.", seed) + } + + // asymmetric + insufficient PoW: mismatch + fasym.PoW = env.PoW() + 1.0 + match = fasym.MatchEnvelope(env) + if match { + t.Fatalf("failed MatchEnvelope(asymmetric + insufficient PoW) with seed %d.", seed) + } + + // asymmetric + sufficient PoW: match + fasym.PoW = env.PoW() / 2 + match = fasym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(asymmetric + sufficient PoW) with seed %d.", seed) + } + + // filter without topic + envelope without topic: match + env.Topic = TopicType{} + match = fasym.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed) + } + + // filter with topic + envelope without topic: mismatch + fasym.Topics = fsym.Topics + match = fasym.MatchEnvelope(env) + if !match { + // topic mismatch should have no affect, as topics are handled by topic matchers + t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed) + } +} + +func TestMatchMessageSym(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + f, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) + } + + const index = 1 + params.KeySym = f.KeySym + params.Topic = BytesToTopic(f.Topics[index]) + + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := sentMessage.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + msg := env.Open(f) + if msg == nil { + t.Fatalf("failed Open with seed %d.", seed) + } + + // Src: match + *f.Src.X = *params.Src.PublicKey.X + *f.Src.Y = *params.Src.PublicKey.Y + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(src match) with seed %d.", seed) + } + + // insufficient PoW: mismatch + f.PoW = msg.PoW + 1.0 + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed) + } + + // sufficient PoW: match + f.PoW = msg.PoW / 2 + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed) + } + + // topic mismatch + f.Topics[index][0]++ + if !f.MatchMessage(msg) { + // topic mismatch should have no affect, as topics are handled by topic matchers + t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed) + } + f.Topics[index][0]-- + + // key mismatch + f.SymKeyHash[0]++ + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed) + } + f.SymKeyHash[0]-- + + // Src absent: match + f.Src = nil + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed) + } + + // key hash mismatch + h := f.SymKeyHash + f.SymKeyHash = common.Hash{} + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(key hash mismatch) with seed %d.", seed) + } + f.SymKeyHash = h + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(key hash match) with seed %d.", seed) + } + + // encryption method mismatch + f.KeySym = nil + f.KeyAsym, err = crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) + } +} + +func TestMatchMessageAsym(t *testing.T) { + InitSingleTest() + + f, err := generateFilter(t, false) + if err != nil { + t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) + } + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + const index = 1 + params.Topic = BytesToTopic(f.Topics[index]) + params.Dst = &f.KeyAsym.PublicKey + keySymOrig := params.KeySym + params.KeySym = nil + + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := sentMessage.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + msg := env.Open(f) + if msg == nil { + t.Fatalf("failed to open with seed %d.", seed) + } + + // Src: match + *f.Src.X = *params.Src.PublicKey.X + *f.Src.Y = *params.Src.PublicKey.Y + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchMessage(src match) with seed %d.", seed) + } + + // insufficient PoW: mismatch + f.PoW = msg.PoW + 1.0 + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed) + } + + // sufficient PoW: match + f.PoW = msg.PoW / 2 + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed) + } + + // topic mismatch + f.Topics[index][0]++ + if !f.MatchMessage(msg) { + // topic mismatch should have no affect, as topics are handled by topic matchers + t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed) + } + f.Topics[index][0]-- + + // key mismatch + prev := *f.KeyAsym.PublicKey.X + zero := *big.NewInt(0) + *f.KeyAsym.PublicKey.X = zero + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed) + } + *f.KeyAsym.PublicKey.X = prev + + // Src absent: match + f.Src = nil + if !f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed) + } + + // encryption method mismatch + f.KeySym = keySymOrig + f.KeyAsym = nil + if f.MatchMessage(msg) { + t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) + } +} + +func cloneFilter(orig *Filter) *Filter { + var clone Filter + clone.Messages = NewMemoryMessageStore() + clone.Src = orig.Src + clone.KeyAsym = orig.KeyAsym + clone.KeySym = orig.KeySym + clone.Topics = orig.Topics + clone.PoW = orig.PoW + clone.AllowP2P = orig.AllowP2P + clone.SymKeyHash = orig.SymKeyHash + return &clone +} + +func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope { + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + return nil + } + + params.KeySym = f.KeySym + params.Topic = BytesToTopic(f.Topics[2]) + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := sentMessage.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + return nil + } + return env +} + +func TestWatchers(t *testing.T) { + InitSingleTest() + + const NumFilters = 16 + const NumMessages = 256 + var i int + var j uint32 + var e *Envelope + var x, firstID string + var err error + + w := New(&Config{}) + filters := NewFilters(w) + tst := generateTestCases(t, NumFilters) + for i = 0; i < NumFilters; i++ { + tst[i].f.Src = nil + x, err = filters.Install(tst[i].f) + if err != nil { + t.Fatalf("failed to install filter with seed %d: %s.", seed, err) + } + tst[i].id = x + if len(firstID) == 0 { + firstID = x + } + } + + lastID := x + + var envelopes [NumMessages]*Envelope + for i = 0; i < NumMessages; i++ { + j = mrand.Uint32() % NumFilters + e = generateCompatibeEnvelope(t, tst[j].f) + envelopes[i] = e + tst[j].msgCnt++ + } + + for i = 0; i < NumMessages; i++ { + filters.NotifyWatchers(envelopes[i], false) + } + + var total int + var mail []*ReceivedMessage + var count [NumFilters]int + + for i = 0; i < NumFilters; i++ { + mail = tst[i].f.Retrieve() + count[i] = len(mail) + total += len(mail) + } + + if total != NumMessages { + t.Fatalf("failed with seed %d: total = %d, want: %d.", seed, total, NumMessages) + } + + for i = 0; i < NumFilters; i++ { + mail = tst[i].f.Retrieve() + if len(mail) != 0 { + t.Fatalf("failed with seed %d: i = %d.", seed, i) + } + + if tst[i].msgCnt != count[i] { + t.Fatalf("failed with seed %d: count[%d]: get %d, want %d.", seed, i, tst[i].msgCnt, count[i]) + } + } + + // another round with a cloned filter + + clone := cloneFilter(tst[0].f) + filters.Uninstall(lastID) + total = 0 + last := NumFilters - 1 + tst[last].f = clone + filters.Install(clone) + for i = 0; i < NumFilters; i++ { + tst[i].msgCnt = 0 + count[i] = 0 + } + + // make sure that the first watcher receives at least one message + e = generateCompatibeEnvelope(t, tst[0].f) + envelopes[0] = e + tst[0].msgCnt++ + for i = 1; i < NumMessages; i++ { + j = mrand.Uint32() % NumFilters + e = generateCompatibeEnvelope(t, tst[j].f) + envelopes[i] = e + tst[j].msgCnt++ + } + + for i = 0; i < NumMessages; i++ { + filters.NotifyWatchers(envelopes[i], false) + } + + for i = 0; i < NumFilters; i++ { + mail = tst[i].f.Retrieve() + count[i] = len(mail) + total += len(mail) + } + + combined := tst[0].msgCnt + tst[last].msgCnt + if total != NumMessages+count[0] { + t.Fatalf("failed with seed %d: total = %d, count[0] = %d.", seed, total, count[0]) + } + + if combined != count[0] { + t.Fatalf("failed with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0]) + } + + if combined != count[last] { + t.Fatalf("failed with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last]) + } + + for i = 1; i < NumFilters-1; i++ { + mail = tst[i].f.Retrieve() + if len(mail) != 0 { + t.Fatalf("failed with seed %d: i = %d.", seed, i) + } + + if tst[i].msgCnt != count[i] { + t.Fatalf("failed with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i]) + } + } + + // test AcceptP2P + + total = 0 + filters.NotifyWatchers(envelopes[0], true) + + for i = 0; i < NumFilters; i++ { + mail = tst[i].f.Retrieve() + total += len(mail) + } + + if total != 0 { + t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total) + } + + f := filters.Get(firstID) + if f == nil { + t.Fatalf("failed to get the filter with seed %d.", seed) + } + f.AllowP2P = true + total = 0 + filters.NotifyWatchers(envelopes[0], true) + + for i = 0; i < NumFilters; i++ { + mail = tst[i].f.Retrieve() + total += len(mail) + } + + if total != 1 { + t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total) + } +} + +func TestVariableTopics(t *testing.T) { + InitSingleTest() + + const lastTopicByte = 3 + var match bool + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + f, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) + } + + for i := 0; i < 4; i++ { + env.Topic = BytesToTopic(f.Topics[i]) + match = f.MatchEnvelope(env) + if !match { + t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i) + } + + f.Topics[i][lastTopicByte]++ + match = f.MatchEnvelope(env) + if !match { + // topic mismatch should have no affect, as topics are handled by topic matchers + t.Fatalf("MatchEnvelope symmetric with seed %d, step %d.", seed, i) + } + } +} diff --git a/whisper/fuzz.go b/whisper/fuzz.go new file mode 100644 index 000000000..46488b178 --- /dev/null +++ b/whisper/fuzz.go @@ -0,0 +1,14 @@ +// +build gofuzz + +package whisper + +func Fuzz(data []byte) int { + if len(data) < 2 { + return -1 + } + + msg := &ReceivedMessage{Raw: data} + msg.ValidateAndParse() + + return 0 +} diff --git a/whisper/gen_criteria_json.go b/whisper/gen_criteria_json.go new file mode 100644 index 000000000..dc7f13894 --- /dev/null +++ b/whisper/gen_criteria_json.go @@ -0,0 +1,66 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package whisper + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*criteriaOverride)(nil) + +// MarshalJSON marshals type Criteria to a json string +func (c Criteria) MarshalJSON() ([]byte, error) { + type Criteria struct { + SymKeyID string `json:"symKeyID"` + PrivateKeyID string `json:"privateKeyID"` + Sig hexutil.Bytes `json:"sig"` + MinPow float64 `json:"minPow"` + Topics []TopicType `json:"topics"` + AllowP2P bool `json:"allowP2P"` + } + var enc Criteria + enc.SymKeyID = c.SymKeyID + enc.PrivateKeyID = c.PrivateKeyID + enc.Sig = c.Sig + enc.MinPow = c.MinPow + enc.Topics = c.Topics + enc.AllowP2P = c.AllowP2P + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals type Criteria to a json string +func (c *Criteria) UnmarshalJSON(input []byte) error { + type Criteria struct { + SymKeyID *string `json:"symKeyID"` + PrivateKeyID *string `json:"privateKeyID"` + Sig *hexutil.Bytes `json:"sig"` + MinPow *float64 `json:"minPow"` + Topics []TopicType `json:"topics"` + AllowP2P *bool `json:"allowP2P"` + } + var dec Criteria + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.SymKeyID != nil { + c.SymKeyID = *dec.SymKeyID + } + if dec.PrivateKeyID != nil { + c.PrivateKeyID = *dec.PrivateKeyID + } + if dec.Sig != nil { + c.Sig = *dec.Sig + } + if dec.MinPow != nil { + c.MinPow = *dec.MinPow + } + if dec.Topics != nil { + c.Topics = dec.Topics + } + if dec.AllowP2P != nil { + c.AllowP2P = *dec.AllowP2P + } + return nil +} diff --git a/whisper/gen_message_json.go b/whisper/gen_message_json.go new file mode 100644 index 000000000..d573b7ae4 --- /dev/null +++ b/whisper/gen_message_json.go @@ -0,0 +1,84 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package whisper + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*messageOverride)(nil) + +// MarshalJSON marshals type Message to a json string +func (m Message) MarshalJSON() ([]byte, error) { + type Message struct { + Sig hexutil.Bytes `json:"sig,omitempty"` + TTL uint32 `json:"ttl"` + Timestamp uint32 `json:"timestamp"` + Topic TopicType `json:"topic"` + Payload hexutil.Bytes `json:"payload"` + Padding hexutil.Bytes `json:"padding"` + PoW float64 `json:"pow"` + Hash hexutil.Bytes `json:"hash"` + Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"` + } + var enc Message + enc.Sig = m.Sig + enc.TTL = m.TTL + enc.Timestamp = m.Timestamp + enc.Topic = m.Topic + enc.Payload = m.Payload + enc.Padding = m.Padding + enc.PoW = m.PoW + enc.Hash = m.Hash + enc.Dst = m.Dst + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals type Message to a json string +func (m *Message) UnmarshalJSON(input []byte) error { + type Message struct { + Sig *hexutil.Bytes `json:"sig,omitempty"` + TTL *uint32 `json:"ttl"` + Timestamp *uint32 `json:"timestamp"` + Topic *TopicType `json:"topic"` + Payload *hexutil.Bytes `json:"payload"` + Padding *hexutil.Bytes `json:"padding"` + PoW *float64 `json:"pow"` + Hash *hexutil.Bytes `json:"hash"` + Dst *hexutil.Bytes `json:"recipientPublicKey,omitempty"` + } + var dec Message + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Sig != nil { + m.Sig = *dec.Sig + } + if dec.TTL != nil { + m.TTL = *dec.TTL + } + if dec.Timestamp != nil { + m.Timestamp = *dec.Timestamp + } + if dec.Topic != nil { + m.Topic = *dec.Topic + } + if dec.Payload != nil { + m.Payload = *dec.Payload + } + if dec.Padding != nil { + m.Padding = *dec.Padding + } + if dec.PoW != nil { + m.PoW = *dec.PoW + } + if dec.Hash != nil { + m.Hash = *dec.Hash + } + if dec.Dst != nil { + m.Dst = *dec.Dst + } + return nil +} diff --git a/whisper/gen_newmessage_json.go b/whisper/gen_newmessage_json.go new file mode 100644 index 000000000..d6a14c203 --- /dev/null +++ b/whisper/gen_newmessage_json.go @@ -0,0 +1,90 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package whisper + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*newMessageOverride)(nil) + +// MarshalJSON marshals type NewMessage to a json string +func (n NewMessage) MarshalJSON() ([]byte, error) { + type NewMessage struct { + SymKeyID string `json:"symKeyID"` + PublicKey hexutil.Bytes `json:"pubKey"` + Sig string `json:"sig"` + TTL uint32 `json:"ttl"` + Topic TopicType `json:"topic"` + Payload hexutil.Bytes `json:"payload"` + Padding hexutil.Bytes `json:"padding"` + PowTime uint32 `json:"powTime"` + PowTarget float64 `json:"powTarget"` + TargetPeer string `json:"targetPeer"` + } + var enc NewMessage + enc.SymKeyID = n.SymKeyID + enc.PublicKey = n.PublicKey + enc.Sig = n.Sig + enc.TTL = n.TTL + enc.Topic = n.Topic + enc.Payload = n.Payload + enc.Padding = n.Padding + enc.PowTime = n.PowTime + enc.PowTarget = n.PowTarget + enc.TargetPeer = n.TargetPeer + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals type NewMessage to a json string +func (n *NewMessage) UnmarshalJSON(input []byte) error { + type NewMessage struct { + SymKeyID *string `json:"symKeyID"` + PublicKey *hexutil.Bytes `json:"pubKey"` + Sig *string `json:"sig"` + TTL *uint32 `json:"ttl"` + Topic *TopicType `json:"topic"` + Payload *hexutil.Bytes `json:"payload"` + Padding *hexutil.Bytes `json:"padding"` + PowTime *uint32 `json:"powTime"` + PowTarget *float64 `json:"powTarget"` + TargetPeer *string `json:"targetPeer"` + } + var dec NewMessage + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.SymKeyID != nil { + n.SymKeyID = *dec.SymKeyID + } + if dec.PublicKey != nil { + n.PublicKey = *dec.PublicKey + } + if dec.Sig != nil { + n.Sig = *dec.Sig + } + if dec.TTL != nil { + n.TTL = *dec.TTL + } + if dec.Topic != nil { + n.Topic = *dec.Topic + } + if dec.Payload != nil { + n.Payload = *dec.Payload + } + if dec.Padding != nil { + n.Padding = *dec.Padding + } + if dec.PowTime != nil { + n.PowTime = *dec.PowTime + } + if dec.PowTarget != nil { + n.PowTarget = *dec.PowTarget + } + if dec.TargetPeer != nil { + n.TargetPeer = *dec.TargetPeer + } + return nil +} diff --git a/whisper/go.mod b/whisper/go.mod new file mode 100644 index 000000000..a66c5b439 --- /dev/null +++ b/whisper/go.mod @@ -0,0 +1,17 @@ +module github.com/status-im/status-go/whisper + +go 1.13 + +replace github.com/ethereum/go-ethereum v1.9.5 => github.com/status-im/go-ethereum v1.9.5-status.6 + +require ( + github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea + github.com/ethereum/go-ethereum v1.9.5 + github.com/gorilla/websocket v1.4.1 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.3.0 + github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 + github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9 + golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f +) diff --git a/whisper/go.sum b/whisper/go.sum new file mode 100644 index 000000000..b864e2768 --- /dev/null +++ b/whisper/go.sum @@ -0,0 +1,134 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +github.com/Azure/azure-pipeline-go v0.0.0-20180607212504-7571e8eb0876/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg= +github.com/Azure/azure-storage-blob-go v0.0.0-20180712005634-eaae161d9d5e/go.mod h1:x2mtS6O3mnMEZOJp7d7oldh8IvatBrMfReiyQ+cKgKY= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/allegro/bigcache v0.0.0-20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/btcsuite/btcd v0.20.0-beta h1:DnZGUjFbRkpytojHWwy6nfUSA7vFrzWXDLpFNzt74ZA= +github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgrijalva/jwt-go v0.0.0-20170201225849-2268707a8f08/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/docker v0.0.0-20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa h1:o8OuEkracbk3qH6GvlI6XpEN1HTSxkzOG42xZpfDv/s= +github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gizak/termui v0.0.0-20170117222342-991cd3d38091/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-stack/stack v1.5.4 h1:ACUuwAbOuCKT3mK+Az9UrqaSheA8lDWOfm0+ZT62NHY= +github.com/go-stack/stack v1.5.4/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3 h1:DqD8eigqlUm0+znmx7zhL0xvTW3+e1jCekJMfBUADWI= +github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= +github.com/influxdata/influxdb v0.0.0-20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jackpal/go-nat-pmp v0.0.0-20160603034137-1fa385a6f458 h1:LPECOO5LcZx5tvkxraIptrg6AiAUf+28rFV9+noSZFA= +github.com/jackpal/go-nat-pmp v0.0.0-20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v0.0.0-20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karalabe/hid v0.0.0-20181128192157-d815e0c1a2e2/go.mod h1:YvbcH+3Wo6XPs9nkgTY3u19KXLauXW+J5nB7hEHuX0A= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/maruel/panicparse v0.0.0-20160720141634-ad661195ed0e/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/naoina/toml v0.0.0-20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/olekukonko/tablewriter v0.0.0-20170128050532-febf2d34b54a/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v0.0.0-20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterh/liner v0.0.0-20170902204657-a37ad3984311/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pkg/errors v0.0.0-20171216070316-e881fd58d78e h1:+RHxT/gm0O3UF7nLJbdNzAmULvCFt4XfXHWzh3XI/zs= +github.com/pkg/errors v0.0.0-20171216070316-e881fd58d78e/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/prometheus v0.0.0-20170814170113-3101606756c5/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/status-im/go-ethereum v1.9.5-status.6 h1:ytuTO1yBIAuTVRtRQoc2mrdyngtP+XOQ9IHIibbz7/I= +github.com/status-im/go-ethereum v1.9.5-status.6/go.mod h1:08JvQWE+IOnAFSe4UD4ACLNe2fDd9XmWMCq5Yzy9mk0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4 h1:InXsxTNd7R4kIHKuA052litAUzokFLqjgbmhpUQTAs8= +github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2zDhJJABEofdb51uMRNb8eqIVtdducs= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9 h1:kjbwitOGH46vD01f2s3leBfrMnePQa3NSAIlW35MvY8= +github.com/tsenart/tb v0.0.0-20181025101425-0d2499c8b6e9/go.mod h1:EcGP24b8DY+bWHnpfJDP7fM+o8Nmz4fYH0l2xTtNr3I= +github.com/uber/jaeger-client-go v0.0.0-20180607151842-f7e0d4744fa6/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v0.0.0-20180615202729-a51202d6f4a7/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss= +golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ= +gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20180302121509-abf0ba0be5d5/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/whisper/mailserver_response.go b/whisper/mailserver_response.go new file mode 100644 index 000000000..f529c2f2f --- /dev/null +++ b/whisper/mailserver_response.go @@ -0,0 +1,140 @@ +package whisper + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const ( + mailServerFailedPayloadPrefix = "ERROR=" + cursorSize = 36 +) + +func invalidResponseSizeError(size int) error { + return fmt.Errorf("unexpected payload size: %d", size) +} + +// CreateMailServerRequestCompletedPayload creates a payload representing +// a successful request to mailserver +func CreateMailServerRequestCompletedPayload(requestID, lastEnvelopeHash common.Hash, cursor []byte) []byte { + payload := make([]byte, len(requestID)) + copy(payload, requestID[:]) + payload = append(payload, lastEnvelopeHash[:]...) + payload = append(payload, cursor...) + return payload +} + +// CreateMailServerRequestFailedPayload creates a payload representing +// a failed request to a mailserver +func CreateMailServerRequestFailedPayload(requestID common.Hash, err error) []byte { + payload := []byte(mailServerFailedPayloadPrefix) + payload = append(payload, requestID[:]...) + payload = append(payload, []byte(err.Error())...) + return payload +} + +// CreateMailServerEvent returns EnvelopeEvent with correct data +// if payload corresponds to any of the know mailserver events: +// * request completed successfully +// * request failed +// If the payload is unknown/unparseable, it returns `nil` +func CreateMailServerEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { + + if len(payload) < common.HashLength { + return nil, invalidResponseSizeError(len(payload)) + } + + event, err := tryCreateMailServerRequestFailedEvent(nodeID, payload) + + if err != nil || event != nil { + return event, err + } + + return tryCreateMailServerRequestCompletedEvent(nodeID, payload) +} + +func tryCreateMailServerRequestFailedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { + if len(payload) < common.HashLength+len(mailServerFailedPayloadPrefix) { + return nil, nil + } + + prefix, remainder := extractPrefix(payload, len(mailServerFailedPayloadPrefix)) + + if !bytes.Equal(prefix, []byte(mailServerFailedPayloadPrefix)) { + return nil, nil + } + + var ( + requestID common.Hash + errorMsg string + ) + + requestID, remainder = extractHash(remainder) + errorMsg = string(remainder) + + event := EnvelopeEvent{ + Peer: nodeID, + Hash: requestID, + Event: EventMailServerRequestCompleted, + Data: &MailServerResponse{ + Error: errors.New(errorMsg), + }, + } + + return &event, nil + +} + +func tryCreateMailServerRequestCompletedEvent(nodeID enode.ID, payload []byte) (*EnvelopeEvent, error) { + // check if payload is + // - requestID or + // - requestID + lastEnvelopeHash or + // - requestID + lastEnvelopeHash + cursor + // requestID is the hash of the request envelope. + // lastEnvelopeHash is the last envelope sent by the mail server + // cursor is the db key, 36 bytes: 4 for the timestamp + 32 for the envelope hash. + if len(payload) > common.HashLength*2+cursorSize { + return nil, invalidResponseSizeError(len(payload)) + } + + var ( + requestID common.Hash + lastEnvelopeHash common.Hash + cursor []byte + ) + + requestID, remainder := extractHash(payload) + + if len(remainder) >= common.HashLength { + lastEnvelopeHash, remainder = extractHash(remainder) + } + + if len(remainder) >= cursorSize { + cursor = remainder + } + + event := EnvelopeEvent{ + Peer: nodeID, + Hash: requestID, + Event: EventMailServerRequestCompleted, + Data: &MailServerResponse{ + LastEnvelopeHash: lastEnvelopeHash, + Cursor: cursor, + }, + } + + return &event, nil +} + +func extractHash(payload []byte) (common.Hash, []byte) { + prefix, remainder := extractPrefix(payload, common.HashLength) + return common.BytesToHash(prefix), remainder +} + +func extractPrefix(payload []byte, size int) ([]byte, []byte) { + return payload[:size], payload[size:] +} diff --git a/whisper/mailserver_response_test.go b/whisper/mailserver_response_test.go new file mode 100644 index 000000000..b78ce9a00 --- /dev/null +++ b/whisper/mailserver_response_test.go @@ -0,0 +1,79 @@ +package whisper + +import ( + "encoding/binary" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/stretchr/testify/require" + "github.com/syndtr/goleveldb/leveldb/errors" +) + +func checkValidErrorPayload(t *testing.T, id []byte, errorMsg string) { + requestID := common.BytesToHash(id) + errPayload := CreateMailServerRequestFailedPayload(requestID, errors.New(errorMsg)) + nid := enode.ID{1} + event, err := CreateMailServerEvent(nid, errPayload) + + require.NoError(t, err) + require.NotNil(t, event) + require.Equal(t, nid, event.Peer) + require.Equal(t, requestID, event.Hash) + + eventData, ok := event.Data.(*MailServerResponse) + if !ok { + require.FailNow(t, "Unexpected data in event: %v, expected a MailServerResponse", event.Data) + } + require.EqualError(t, eventData.Error, errorMsg) +} + +func checkValidSuccessPayload(t *testing.T, id []byte, lastHash []byte, timestamp uint32, envHash []byte) { + requestID := common.BytesToHash(id) + lastEnvelopeHash := common.BytesToHash(lastHash) + timestampBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(timestampBytes, timestamp) + envelopeHash := common.BytesToHash(envHash) + cursor := append(timestampBytes, envelopeHash[:]...) + successPayload := CreateMailServerRequestCompletedPayload(common.BytesToHash(id), lastEnvelopeHash, cursor) + nid := enode.ID{1} + event, err := CreateMailServerEvent(nid, successPayload) + + require.NoError(t, err) + require.NotNil(t, event) + require.Equal(t, nid, event.Peer) + require.Equal(t, requestID, event.Hash) + + eventData, ok := event.Data.(*MailServerResponse) + if !ok { + require.FailNow(t, "Unexpected data in event: %v, expected a MailServerResponse", event.Data) + } + require.Equal(t, lastEnvelopeHash, eventData.LastEnvelopeHash) + require.Equal(t, cursor, eventData.Cursor) + require.NoError(t, eventData.Error) +} + +func TestCreateMailServerEvent(t *testing.T) { + // valid cases + longErrorMessage := "longMessage|" + for i := 0; i < 5; i++ { + longErrorMessage = longErrorMessage + longErrorMessage + } + checkValidErrorPayload(t, []byte{0x01}, "test error 1") + checkValidErrorPayload(t, []byte{0x02}, "test error 2") + checkValidErrorPayload(t, []byte{0x02}, "") + checkValidErrorPayload(t, []byte{0x00}, "test error 3") + checkValidErrorPayload(t, []byte{}, "test error 4") + + checkValidSuccessPayload(t, []byte{0x01}, []byte{0x02}, 123, []byte{0x03}) + // invalid payloads + + // too small + _, err := CreateMailServerEvent(enode.ID{}, []byte{0x00}) + require.Error(t, err) + + // too big and not error payload + payloadTooBig := make([]byte, common.HashLength*2+cursorSize+100) + _, err = CreateMailServerEvent(enode.ID{}, payloadTooBig) + require.Error(t, err) +} diff --git a/whisper/message.go b/whisper/message.go new file mode 100644 index 000000000..f085b8ebc --- /dev/null +++ b/whisper/message.go @@ -0,0 +1,361 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the Whisper protocol Message element. + +package whisper + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + crand "crypto/rand" + "encoding/binary" + "errors" + mrand "math/rand" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ethereum/go-ethereum/log" +) + +// MessageParams specifies the exact way a message should be wrapped +// into an Envelope. +type MessageParams struct { + TTL uint32 + Src *ecdsa.PrivateKey + Dst *ecdsa.PublicKey + KeySym []byte + Topic TopicType + WorkTime uint32 + PoW float64 + Payload []byte + Padding []byte +} + +// SentMessage represents an end-user data packet to transmit through the +// Whisper protocol. These are wrapped into Envelopes that need not be +// understood by intermediate nodes, just forwarded. +type sentMessage struct { + Raw []byte +} + +// ReceivedMessage represents a data packet to be received through the +// Whisper protocol and successfully decrypted. +type ReceivedMessage struct { + Raw []byte + + Payload []byte + Padding []byte + Signature []byte + Salt []byte + + PoW float64 // Proof of work as described in the Whisper spec + Sent uint32 // Time when the message was posted into the network + TTL uint32 // Maximum time to live allowed for the message + Src *ecdsa.PublicKey // Message recipient (identity used to decode the message) + Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message) + Topic TopicType + + SymKeyHash common.Hash // The Keccak256Hash of the key + EnvelopeHash common.Hash // Message envelope hash to act as a unique id + + P2P bool // is set to true if this message was received from mail server. +} + +func isMessageSigned(flags byte) bool { + return (flags & signatureFlag) != 0 +} + +func (msg *ReceivedMessage) isSymmetricEncryption() bool { + return msg.SymKeyHash != common.Hash{} +} + +func (msg *ReceivedMessage) isAsymmetricEncryption() bool { + return msg.Dst != nil +} + +// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message. +func NewSentMessage(params *MessageParams) (*sentMessage, error) { + const payloadSizeFieldMaxSize = 4 + msg := sentMessage{} + msg.Raw = make([]byte, 1, + flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) + msg.Raw[0] = 0 // set all the flags to zero + msg.addPayloadSizeField(params.Payload) + msg.Raw = append(msg.Raw, params.Payload...) + err := msg.appendPadding(params) + return &msg, err +} + +// addPayloadSizeField appends the auxiliary field containing the size of payload +func (msg *sentMessage) addPayloadSizeField(payload []byte) { + fieldSize := getSizeOfPayloadSizeField(payload) + field := make([]byte, 4) + binary.LittleEndian.PutUint32(field, uint32(len(payload))) + field = field[:fieldSize] + msg.Raw = append(msg.Raw, field...) + msg.Raw[0] |= byte(fieldSize) +} + +// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload +func getSizeOfPayloadSizeField(payload []byte) int { + s := 1 + for i := len(payload); i >= 256; i /= 256 { + s++ + } + return s +} + +// appendPadding appends the padding specified in params. +// If no padding is provided in params, then random padding is generated. +func (msg *sentMessage) appendPadding(params *MessageParams) error { + if len(params.Padding) != 0 { + // padding data was provided by the Dapp, just use it as is + msg.Raw = append(msg.Raw, params.Padding...) + return nil + } + + rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload) + if params.Src != nil { + rawSize += signatureLength + } + odd := rawSize % padSizeLimit + paddingSize := padSizeLimit - odd + pad := make([]byte, paddingSize) + _, err := crand.Read(pad) + if err != nil { + return err + } + if !validateDataIntegrity(pad, paddingSize) { + return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize)) + } + msg.Raw = append(msg.Raw, pad...) + return nil +} + +// sign calculates and sets the cryptographic signature for the message, +// also setting the sign flag. +func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error { + if isMessageSigned(msg.Raw[0]) { + // this should not happen, but no reason to panic + log.Error("failed to sign the message: already signed") + return nil + } + + msg.Raw[0] |= signatureFlag // it is important to set this flag before signing + hash := crypto.Keccak256(msg.Raw) + signature, err := crypto.Sign(hash, key) + if err != nil { + msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag + return err + } + msg.Raw = append(msg.Raw, signature...) + return nil +} + +// encryptAsymmetric encrypts a message with a public key. +func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { + if !ValidatePublicKey(key) { + return errors.New("invalid public key provided for asymmetric encryption") + } + encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil) + if err == nil { + msg.Raw = encrypted + } + return err +} + +// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256. +// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). +func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { + if !validateDataIntegrity(key, aesKeyLength) { + return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key))) + } + block, err := aes.NewCipher(key) + if err != nil { + return err + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return err + } + salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key + if err != nil { + return err + } + encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil) + msg.Raw = append(encrypted, salt...) + return nil +} + +// generateSecureRandomData generates random data where extra security is required. +// The purpose of this function is to prevent some bugs in software or in hardware +// from delivering not-very-random data. This is especially useful for AES nonce, +// where true randomness does not really matter, but it is very important to have +// a unique nonce for every message. +func generateSecureRandomData(length int) ([]byte, error) { + x := make([]byte, length) + y := make([]byte, length) + res := make([]byte, length) + + _, err := crand.Read(x) + if err != nil { + return nil, err + } else if !validateDataIntegrity(x, length) { + return nil, errors.New("crypto/rand failed to generate secure random data") + } + _, err = mrand.Read(y) + if err != nil { + return nil, err + } else if !validateDataIntegrity(y, length) { + return nil, errors.New("math/rand failed to generate secure random data") + } + for i := 0; i < length; i++ { + res[i] = x[i] ^ y[i] + } + if !validateDataIntegrity(res, length) { + return nil, errors.New("failed to generate secure random data") + } + return res, nil +} + +// Wrap bundles the message into an Envelope to transmit over the network. +func (msg *sentMessage) Wrap(options *MessageParams, now time.Time) (envelope *Envelope, err error) { + if options.TTL == 0 { + options.TTL = DefaultTTL + } + if options.Src != nil { + if err = msg.sign(options.Src); err != nil { + return nil, err + } + } + if options.Dst != nil { + err = msg.encryptAsymmetric(options.Dst) + } else if options.KeySym != nil { + err = msg.encryptSymmetric(options.KeySym) + } else { + err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") + } + if err != nil { + return nil, err + } + + envelope = NewEnvelope(options.TTL, options.Topic, msg, now) + if err = envelope.Seal(options); err != nil { + return nil, err + } + return envelope, nil +} + +// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256. +// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). +func (msg *ReceivedMessage) decryptSymmetric(key []byte) error { + // symmetric messages are expected to contain the 12-byte nonce at the end of the payload + if len(msg.Raw) < aesNonceLength { + return errors.New("missing salt or invalid payload in symmetric message") + } + salt := msg.Raw[len(msg.Raw)-aesNonceLength:] + + block, err := aes.NewCipher(key) + if err != nil { + return err + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return err + } + decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil) + if err != nil { + return err + } + msg.Raw = decrypted + msg.Salt = salt + return nil +} + +// decryptAsymmetric decrypts an encrypted payload with a private key. +func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error { + decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil) + if err == nil { + msg.Raw = decrypted + } + return err +} + +// ValidateAndParse checks the message validity and extracts the fields in case of success. +func (msg *ReceivedMessage) ValidateAndParse() bool { + end := len(msg.Raw) + if end < 1 { + return false + } + + if isMessageSigned(msg.Raw[0]) { + end -= signatureLength + if end <= 1 { + return false + } + msg.Signature = msg.Raw[end : end+signatureLength] + msg.Src = msg.SigToPubKey() + if msg.Src == nil { + return false + } + } + + beg := 1 + payloadSize := 0 + sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload + if sizeOfPayloadSizeField != 0 { + if end < beg+sizeOfPayloadSizeField { + return false + } + payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField])) + beg += sizeOfPayloadSizeField + if beg+payloadSize > end { + return false + } + msg.Payload = msg.Raw[beg : beg+payloadSize] + } + + beg += payloadSize + msg.Padding = msg.Raw[beg:end] + return true +} + +// SigToPubKey returns the public key associated to the message's +// signature. +func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { + defer func() { recover() }() // in case of invalid signature + + pub, err := crypto.SigToPub(msg.hash(), msg.Signature) + if err != nil { + log.Error("failed to recover public key from signature", "err", err) + return nil + } + return pub +} + +// hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding. +func (msg *ReceivedMessage) hash() []byte { + if isMessageSigned(msg.Raw[0]) { + sz := len(msg.Raw) - signatureLength + return crypto.Keccak256(msg.Raw[:sz]) + } + return crypto.Keccak256(msg.Raw) +} diff --git a/whisper/message_test.go b/whisper/message_test.go new file mode 100644 index 000000000..aeac27007 --- /dev/null +++ b/whisper/message_test.go @@ -0,0 +1,495 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + mrand "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +func generateMessageParams() (*MessageParams, error) { + // set all the parameters except p.Dst and p.Padding + + buf := make([]byte, 4) + mrand.Read(buf) + sz := mrand.Intn(400) + + var p MessageParams + p.PoW = 0.01 + p.WorkTime = 1 + p.TTL = uint32(mrand.Intn(1024)) + p.Payload = make([]byte, sz) + p.KeySym = make([]byte, aesKeyLength) + mrand.Read(p.Payload) + mrand.Read(p.KeySym) + p.Topic = BytesToTopic(buf) + + var err error + p.Src, err = crypto.GenerateKey() + if err != nil { + return nil, err + } + + return &p, nil +} + +func singleMessageTest(t *testing.T, symmetric bool) { + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + + if !symmetric { + params.KeySym = nil + params.Dst = &key.PublicKey + } + + text := make([]byte, 0, 512) + text = append(text, params.Payload...) + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + var decrypted *ReceivedMessage + if symmetric { + decrypted, err = env.OpenSymmetric(params.KeySym) + } else { + decrypted, err = env.OpenAsymmetric(key) + } + + if err != nil { + t.Fatalf("failed to encrypt with seed %d: %s.", seed, err) + } + + if !decrypted.ValidateAndParse() { + t.Fatalf("failed to validate with seed %d, symmetric = %v.", seed, symmetric) + } + + if !bytes.Equal(text, decrypted.Payload) { + t.Fatalf("failed with seed %d: compare payload.", seed) + } + if !isMessageSigned(decrypted.Raw[0]) { + t.Fatalf("failed with seed %d: unsigned.", seed) + } + if len(decrypted.Signature) != signatureLength { + t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) + } + if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { + t.Fatalf("failed with seed %d: signature mismatch.", seed) + } +} + +func TestMessageEncryption(t *testing.T) { + InitSingleTest() + + var symmetric bool + for i := 0; i < 256; i++ { + singleMessageTest(t, symmetric) + symmetric = !symmetric + } +} + +func TestMessageWrap(t *testing.T) { + seed = int64(1777444222) + mrand.Seed(seed) + target := 128.0 + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.TTL = 1 + params.WorkTime = 12 + params.PoW = target + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + pow := env.PoW() + if pow < target { + t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) + } + + // set PoW target too high, expect error + msg2, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.TTL = 1000000 + params.WorkTime = 1 + params.PoW = 10000000.0 + _, err = msg2.Wrap(params, time.Now()) + if err == nil { + t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed) + } +} + +func TestMessageSeal(t *testing.T) { + // this test depends on deterministic choice of seed (1976726903) + seed = int64(1976726903) + mrand.Seed(seed) + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.TTL = 1 + + env := NewEnvelope(params.TTL, params.Topic, msg, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + env.Expiry = uint32(seed) // make it deterministic + target := 32.0 + params.WorkTime = 4 + params.PoW = target + env.Seal(params) + + env.calculatePoW(0) + pow := env.PoW() + if pow < target { + t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) + } + + params.WorkTime = 1 + params.PoW = 1000000000.0 + env.Seal(params) + env.calculatePoW(0) + pow = env.PoW() + if pow < 2*target { + t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow) + } +} + +func TestEnvelopeOpen(t *testing.T) { + InitSingleTest() + + var symmetric bool + for i := 0; i < 32; i++ { + singleEnvelopeOpenTest(t, symmetric) + symmetric = !symmetric + } +} + +func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) + } + + if !symmetric { + params.KeySym = nil + params.Dst = &key.PublicKey + } + + text := make([]byte, 0, 512) + text = append(text, params.Payload...) + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + var f Filter + if symmetric { + f = Filter{KeySym: params.KeySym} + } else { + f = Filter{KeyAsym: key} + } + decrypted := env.Open(&f) + if decrypted == nil { + t.Fatalf("failed to open with seed %d.", seed) + } + + if !bytes.Equal(text, decrypted.Payload) { + t.Fatalf("failed with seed %d: compare payload.", seed) + } + if !isMessageSigned(decrypted.Raw[0]) { + t.Fatalf("failed with seed %d: unsigned.", seed) + } + if len(decrypted.Signature) != signatureLength { + t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) + } + if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { + t.Fatalf("failed with seed %d: signature mismatch.", seed) + } + if decrypted.isAsymmetricEncryption() == symmetric { + t.Fatalf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric) + } + if decrypted.isSymmetricEncryption() != symmetric { + t.Fatalf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric) + } + if !symmetric { + if decrypted.Dst == nil { + t.Fatalf("failed with seed %d: dst is nil.", seed) + } + if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) { + t.Fatalf("failed with seed %d: Dst.", seed) + } + } +} + +func TestEncryptWithZeroKey(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.KeySym = make([]byte, aesKeyLength) + _, err = msg.Wrap(params, time.Now()) + if err == nil { + t.Fatalf("wrapped with zero key, seed: %d.", seed) + } + + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.KeySym = make([]byte, 0) + _, err = msg.Wrap(params, time.Now()) + if err == nil { + t.Fatalf("wrapped with empty key, seed: %d.", seed) + } + + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + params.KeySym = nil + _, err = msg.Wrap(params, time.Now()) + if err == nil { + t.Fatalf("wrapped with nil key, seed: %d.", seed) + } +} + +func TestRlpEncode(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("wrapped with zero key, seed: %d.", seed) + } + + raw, err := rlp.EncodeToBytes(env) + if err != nil { + t.Fatalf("RLP encode failed: %s.", err) + } + + var decoded Envelope + rlp.DecodeBytes(raw, &decoded) + if err != nil { + t.Fatalf("RLP decode failed: %s.", err) + } + + he := env.Hash() + hd := decoded.Hash() + + if he != hd { + t.Fatalf("Hashes are not equal: %x vs. %x", he, hd) + } +} + +func singlePaddingTest(t *testing.T, padSize int) { + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err) + } + params.Padding = make([]byte, padSize) + params.PoW = 0.0000000001 + pad := make([]byte, padSize) + _, err = mrand.Read(pad) + if err != nil { + t.Fatalf("padding is not generated (seed %d): %s", seed, err) + } + n := copy(params.Padding, pad) + if n != padSize { + t.Fatalf("padding is not copied (seed %d): %s", seed, err) + } + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize) + } + f := Filter{KeySym: params.KeySym} + decrypted := env.Open(&f) + if decrypted == nil { + t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize) + } + if !bytes.Equal(pad, decrypted.Padding) { + t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding) + } +} + +func TestPadding(t *testing.T) { + InitSingleTest() + + for i := 1; i < 260; i++ { + singlePaddingTest(t, i) + } + + lim := 256 * 256 + for i := lim - 5; i < lim+2; i++ { + singlePaddingTest(t, i) + } + + for i := 0; i < 256; i++ { + n := mrand.Intn(256*254) + 256 + singlePaddingTest(t, n) + } + + for i := 0; i < 256; i++ { + n := mrand.Intn(256*1024) + 256*256 + singlePaddingTest(t, n) + } +} + +func TestPaddingAppendedToSymMessagesWithSignature(t *testing.T) { + params := &MessageParams{ + Payload: make([]byte, 246), + KeySym: make([]byte, aesKeyLength), + } + + pSrc, err := crypto.GenerateKey() + + if err != nil { + t.Fatalf("Error creating the signature key %v", err) + return + } + params.Src = pSrc + + // Simulate a message with a payload just under 256 so that + // payload + flag + signature > 256. Check that the result + // is padded on the next 256 boundary. + msg := sentMessage{} + const payloadSizeFieldMinSize = 1 + msg.Raw = make([]byte, flagsLength+payloadSizeFieldMinSize+len(params.Payload)) + + err = msg.appendPadding(params) + + if err != nil { + t.Fatalf("Error appending padding to message %v", err) + return + } + + if len(msg.Raw) != 512-signatureLength { + t.Errorf("Invalid size %d != 512", len(msg.Raw)) + } +} + +func TestAesNonce(t *testing.T) { + key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31") + block, err := aes.NewCipher(key) + if err != nil { + t.Fatalf("NewCipher failed: %s", err) + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + t.Fatalf("NewGCM failed: %s", err) + } + // This is the most important single test in this package. + // If it fails, whisper will not be working. + if aesgcm.NonceSize() != aesNonceLength { + t.Fatalf("Nonce size is wrong. This is a critical error. Apparently AES nonce size have changed in the new version of AES GCM package. Whisper will not be working until this problem is resolved.") + } +} + +func TestValidateAndParseSizeOfPayloadSize(t *testing.T) { + testCases := []struct { + Name string + Raw []byte + }{ + { + Name: "one byte of value 1", + Raw: []byte{1}, + }, + { + Name: "two bytes of values 1 and 1", + Raw: []byte{1, 1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + msg := ReceivedMessage{Raw: tc.Raw} + msg.ValidateAndParse() + }) + } +} diff --git a/whisper/metrics.go b/whisper/metrics.go new file mode 100644 index 000000000..090682fbd --- /dev/null +++ b/whisper/metrics.go @@ -0,0 +1,21 @@ +package whisper + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + envelopeAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeAdded", nil) + envelopeNewAddedCounter = metrics.NewRegisteredCounter("whisper/envelopeNewAdded", nil) + envelopeClearedCounter = metrics.NewRegisteredCounter("whisper/envelopeCleared", nil) + envelopeErrFromFutureCounter = metrics.NewRegisteredCounter("whisper/envelopeErrFromFuture", nil) + envelopeErrVeryOldCounter = metrics.NewRegisteredCounter("whisper/envelopeErrVeryOld", nil) + envelopeErrExpiredCounter = metrics.NewRegisteredCounter("whisper/envelopeErrExpired", nil) + envelopeErrOversizedCounter = metrics.NewRegisteredCounter("whisper/envelopeErrOversized", nil) + envelopeErrLowPowCounter = metrics.NewRegisteredCounter("whisper/envelopeErrLowPow", nil) + envelopeErrNoBloomMatchCounter = metrics.NewRegisteredCounter("whisper/envelopeErrNoBloomMatch", nil) + envelopeSizeMeter = metrics.NewRegisteredMeter("whisper/envelopeSize", nil) + + // rate limiter metrics + rateLimiterProcessed = metrics.NewRegisteredCounter("whisper/rateLimiterProcessed", nil) + rateLimiterIPExceeded = metrics.NewRegisteredCounter("whisper/rateLimiterIPExceeded", nil) + rateLimiterPeerExceeded = metrics.NewRegisteredCounter("whisper/rateLimiterPeerExceeded", nil) +) diff --git a/whisper/peer.go b/whisper/peer.go new file mode 100644 index 000000000..d2a2d0600 --- /dev/null +++ b/whisper/peer.go @@ -0,0 +1,295 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "fmt" + "math" + "sync" + "time" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +// Peer represents a whisper protocol peer connection. +type Peer struct { + host *Whisper + peer *p2p.Peer + ws p2p.MsgReadWriter + + trusted bool + powRequirement float64 + bloomMu sync.Mutex + bloomFilter []byte + fullNode bool + confirmationsEnabled bool + + known mapset.Set // Messages already known by the peer to avoid wasting bandwidth + + quit chan struct{} +} + +// newPeer creates a new whisper peer object, but does not run the handshake itself. +func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { + return &Peer{ + host: host, + peer: remote, + ws: rw, + trusted: false, + powRequirement: 0.0, + known: mapset.NewSet(), + quit: make(chan struct{}), + bloomFilter: MakeFullNodeBloom(), + fullNode: true, + } +} + +// start initiates the peer updater, periodically broadcasting the whisper packets +// into the network. +func (peer *Peer) start() { + go peer.update() + log.Trace("start", "peer", peer.ID()) +} + +// stop terminates the peer updater, stopping message forwarding to it. +func (peer *Peer) stop() { + close(peer.quit) + log.Trace("stop", "peer", peer.ID()) +} + +// handshake sends the protocol initiation status message to the remote peer and +// verifies the remote status too. +func (peer *Peer) handshake() error { + // Send the handshake status message asynchronously + errc := make(chan error, 1) + isLightNode := peer.host.LightClientMode() + isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted() + go func() { + pow := peer.host.MinPow() + powConverted := math.Float64bits(pow) + bloom := peer.host.BloomFilter() + confirmationsEnabled := !peer.host.disableConfirmations + + errc <- p2p.SendItems(peer.ws, statusCode, ProtocolVersion, powConverted, bloom, isLightNode, confirmationsEnabled) + }() + + // Fetch the remote status packet and verify protocol match + packet, err := peer.ws.ReadMsg() + if err != nil { + return err + } + if packet.Code != statusCode { + return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code) + } + s := rlp.NewStream(packet.Payload, uint64(packet.Size)) + _, err = s.List() + if err != nil { + return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err) + } + peerVersion, err := s.Uint() + if err != nil { + return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", peer.ID(), err) + } + if peerVersion != ProtocolVersion { + return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion) + } + + // only version is mandatory, subsequent parameters are optional + powRaw, err := s.Uint() + if err == nil { + pow := math.Float64frombits(powRaw) + if math.IsInf(pow, 0) || math.IsNaN(pow) || pow < 0.0 { + return fmt.Errorf("peer [%x] sent bad status message: invalid pow", peer.ID()) + } + peer.powRequirement = pow + + var bloom []byte + err = s.Decode(&bloom) + if err == nil { + sz := len(bloom) + if sz != BloomFilterSize && sz != 0 { + return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz) + } + peer.setBloomFilter(bloom) + } + } + + isRemotePeerLightNode, _ := s.Bool() + if isRemotePeerLightNode && isLightNode && isRestrictedLightNodeConnection { + return fmt.Errorf("peer [%x] is useless: two light client communication restricted", peer.ID()) + } + confirmationsEnabled, err := s.Bool() + if err != nil || !confirmationsEnabled { + log.Warn("confirmations are disabled", "peer", peer.ID()) + } else { + peer.confirmationsEnabled = confirmationsEnabled + } + + if err := <-errc; err != nil { + return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err) + } + return nil +} + +// update executes periodic operations on the peer, including message transmission +// and expiration. +func (peer *Peer) update() { + // Start the tickers for the updates + expire := time.NewTicker(expirationCycle) + transmit := time.NewTicker(transmissionCycle) + + // Loop and transmit until termination is requested + for { + select { + case <-expire.C: + peer.expire() + + case <-transmit.C: + if err := peer.broadcast(); err != nil { + log.Trace("broadcast failed", "reason", err, "peer", peer.ID()) + return + } + + case <-peer.quit: + return + } + } +} + +// mark marks an envelope known to the peer so that it won't be sent back. +func (peer *Peer) mark(envelope *Envelope) { + peer.known.Add(envelope.Hash()) +} + +// marked checks if an envelope is already known to the remote peer. +func (peer *Peer) marked(envelope *Envelope) bool { + return peer.known.Contains(envelope.Hash()) +} + +// expire iterates over all the known envelopes in the host and removes all +// expired (unknown) ones from the known list. +func (peer *Peer) expire() { + unmark := make(map[common.Hash]struct{}) + peer.known.Each(func(v interface{}) bool { + if !peer.host.isEnvelopeCached(v.(common.Hash)) { + unmark[v.(common.Hash)] = struct{}{} + } + return true + }) + // Dump all known but no longer cached + for hash := range unmark { + peer.known.Remove(hash) + } +} + +// broadcast iterates over the collection of envelopes and transmits yet unknown +// ones over the network. +func (peer *Peer) broadcast() error { + envelopes := peer.host.Envelopes() + bundle := make([]*Envelope, 0, len(envelopes)) + for _, envelope := range envelopes { + if !peer.marked(envelope) && envelope.PoW() >= peer.powRequirement && peer.bloomMatch(envelope) { + bundle = append(bundle, envelope) + } + } + + if len(bundle) > 0 { + batchHash, err := sendBundle(peer.ws, bundle) + if err != nil { + log.Warn("failed to deliver envelopes", "peer", peer.peer.ID(), "error", err) + return err + } + + // mark envelopes only if they were successfully sent + for _, e := range bundle { + peer.mark(e) + event := EnvelopeEvent{ + Event: EventEnvelopeSent, + Hash: e.Hash(), + Peer: peer.peer.ID(), + } + if peer.confirmationsEnabled { + event.Batch = batchHash + } + peer.host.envelopeFeed.Send(event) + } + + log.Trace("broadcast", "num. messages", len(bundle)) + } + return nil +} + +// ID returns a peer's id +func (peer *Peer) ID() []byte { + id := peer.peer.ID() + return id[:] +} + +func (peer *Peer) notifyAboutPowRequirementChange(pow float64) error { + i := math.Float64bits(pow) + return p2p.Send(peer.ws, powRequirementCode, i) +} + +func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error { + return p2p.Send(peer.ws, bloomFilterExCode, bloom) +} + +func (peer *Peer) bloomMatch(env *Envelope) bool { + peer.bloomMu.Lock() + defer peer.bloomMu.Unlock() + return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom()) +} + +func (peer *Peer) setBloomFilter(bloom []byte) { + peer.bloomMu.Lock() + defer peer.bloomMu.Unlock() + peer.bloomFilter = bloom + peer.fullNode = isFullNode(bloom) + if peer.fullNode && peer.bloomFilter == nil { + peer.bloomFilter = MakeFullNodeBloom() + } +} + +func MakeFullNodeBloom() []byte { + bloom := make([]byte, BloomFilterSize) + for i := 0; i < BloomFilterSize; i++ { + bloom[i] = 0xFF + } + return bloom +} + +func sendBundle(rw p2p.MsgWriter, bundle []*Envelope) (rst common.Hash, err error) { + data, err := rlp.EncodeToBytes(bundle) + if err != nil { + return + } + err = rw.WriteMsg(p2p.Msg{ + Code: messagesCode, + Size: uint32(len(data)), + Payload: bytes.NewBuffer(data), + }) + if err != nil { + return + } + return crypto.Keccak256Hash(data), nil +} diff --git a/whisper/peer_test.go b/whisper/peer_test.go new file mode 100644 index 000000000..9662cca8a --- /dev/null +++ b/whisper/peer_test.go @@ -0,0 +1,568 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "crypto/ecdsa" + "fmt" + mrand "math/rand" + "sync" + "sync/atomic" + "testing" + "time" + + "net" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rlp" +) + +var keys = []string{ + "d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9", + "73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98", + "119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc", + "deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837", + "5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf", + "1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5", + "15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68", + "51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c", + "ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0", + "09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9", + "15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4", + "2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620", + "73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590", + "1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1", + "8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a", + "0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f", + "8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8", + "acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805", + "a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045", + "28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5", + "c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7", + "46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd", + "c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f", + "783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211", + "9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611", + "e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f", + "487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b", + "824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5", + "ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15", + "30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c", + "de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2", + "92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a", + "7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4", +} + +type TestData struct { + started int64 + counter [NumNodes]int + mutex sync.RWMutex +} + +type TestNode struct { + shh *Whisper + id *ecdsa.PrivateKey + server *p2p.Server + filerID string +} + +const NumNodes = 8 // must not exceed the number of keys (32) + +var result TestData +var nodes [NumNodes]*TestNode +var sharedKey = hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31") +var wrongKey = hexutil.MustDecode("0xf91156714d7ec88d3edc1c652c2181dbb3044e8771c683f3b30d33c12b986b11") +var sharedTopic = TopicType{0xF, 0x1, 0x2, 0} +var wrongTopic = TopicType{0, 0, 0, 0} +var expectedMessage = []byte("per aspera ad astra") +var unexpectedMessage = []byte("per rectum ad astra") +var masterBloomFilter []byte +var masterPow = 0.00000001 +var round = 1 +var debugMode = false +var prevTime time.Time +var cntPrev int + +func TestSimulation(t *testing.T) { + // create a chain of whisper nodes, + // installs the filters with shared (predefined) parameters + initialize(t) + + // each node sends one random (not decryptable) message + for i := 0; i < NumNodes; i++ { + sendMsg(t, false, i) + } + + // node #0 sends one expected (decryptable) message + sendMsg(t, true, 0) + + // check if each node have received and decrypted exactly one message + checkPropagation(t, true) + + // check if Status message was correctly decoded + checkBloomFilterExchange(t) + checkPowExchange(t) + + // send new pow and bloom exchange messages + resetParams(t) + + // node #1 sends one expected (decryptable) message + sendMsg(t, true, 1) + + // check if each node (except node #0) have received and decrypted exactly one message + checkPropagation(t, false) + + // check if corresponding protocol-level messages were correctly decoded + checkPowExchangeForNodeZero(t) + checkBloomFilterExchange(t) + + stopServers() +} + +func resetParams(t *testing.T) { + // change pow only for node zero + masterPow = 7777777.0 + nodes[0].shh.SetMinimumPoW(masterPow) + + // change bloom for all nodes + masterBloomFilter = TopicToBloom(sharedTopic) + for i := 0; i < NumNodes; i++ { + nodes[i].shh.SetBloomFilter(masterBloomFilter) + } + + round++ +} + +func initBloom(t *testing.T) { + masterBloomFilter = make([]byte, BloomFilterSize) + _, err := mrand.Read(masterBloomFilter) + if err != nil { + t.Fatalf("rand failed: %s.", err) + } + + msgBloom := TopicToBloom(sharedTopic) + masterBloomFilter = addBloom(masterBloomFilter, msgBloom) + for i := 0; i < 32; i++ { + masterBloomFilter[i] = 0xFF + } + + if !BloomFilterMatch(masterBloomFilter, msgBloom) { + t.Fatalf("bloom mismatch on initBloom.") + } +} + +func initialize(t *testing.T) { + initBloom(t) + + var err error + + for i := 0; i < NumNodes; i++ { + var node TestNode + b := make([]byte, BloomFilterSize) + copy(b, masterBloomFilter) + node.shh = New(&DefaultConfig) + node.shh.SetMinimumPoW(masterPow) + node.shh.SetBloomFilter(b) + if !bytes.Equal(node.shh.BloomFilter(), masterBloomFilter) { + t.Fatalf("bloom mismatch on init.") + } + node.shh.Start(nil) + topics := make([]TopicType, 0) + topics = append(topics, sharedTopic) + f := Filter{KeySym: sharedKey, Messages: NewMemoryMessageStore()} + f.Topics = [][]byte{topics[0][:]} + node.filerID, err = node.shh.Subscribe(&f) + if err != nil { + t.Fatalf("failed to install the filter: %s.", err) + } + node.id, err = crypto.HexToECDSA(keys[i]) + if err != nil { + t.Fatalf("failed convert the key: %s.", keys[i]) + } + name := common.MakeName("whisper-go", "2.0") + + node.server = &p2p.Server{ + Config: p2p.Config{ + PrivateKey: node.id, + MaxPeers: NumNodes/2 + 1, + Name: name, + Protocols: node.shh.Protocols(), + ListenAddr: "127.0.0.1:0", + NAT: nat.Any(), + }, + } + + go startServer(t, node.server) + + nodes[i] = &node + } + + waitForServersToStart(t) + + for i := 0; i < NumNodes; i++ { + for j := 0; j < i; j++ { + peerNodeId := nodes[j].id + address, _ := net.ResolveTCPAddr("tcp", nodes[j].server.ListenAddr) + peer := enode.NewV4(&peerNodeId.PublicKey, address.IP, address.Port, address.Port) + nodes[i].server.AddPeer(peer) + } + } +} + +func startServer(t *testing.T, s *p2p.Server) { + err := s.Start() + if err != nil { + t.Fatalf("failed to start the first server. err: %v", err) + } + + atomic.AddInt64(&result.started, 1) +} + +func stopServers() { + for i := 0; i < NumNodes; i++ { + n := nodes[i] + if n != nil { + n.shh.Unsubscribe(n.filerID) + n.shh.Stop() + n.server.Stop() + } + } +} + +func checkPropagation(t *testing.T, includingNodeZero bool) { + if t.Failed() { + return + } + + prevTime = time.Now() + // (cycle * iterations) should not exceed 50 seconds, since TTL=50 + const cycle = 200 // time in milliseconds + const iterations = 250 + + first := 0 + if !includingNodeZero { + first = 1 + } + + for j := 0; j < iterations; j++ { + for i := first; i < NumNodes; i++ { + f := nodes[i].shh.GetFilter(nodes[i].filerID) + if f == nil { + t.Fatalf("failed to get filterId %s from node %d, round %d.", nodes[i].filerID, i, round) + } + + mail := f.Retrieve() + validateMail(t, i, mail) + + if isTestComplete() { + checkTestStatus() + return + } + } + + checkTestStatus() + time.Sleep(cycle * time.Millisecond) + } + + if !includingNodeZero { + f := nodes[0].shh.GetFilter(nodes[0].filerID) + if f != nil { + t.Fatalf("node zero received a message with low PoW.") + } + } + + t.Fatalf("Test was not complete (%d round): timeout %d seconds. nodes=%v", round, iterations*cycle/1000, nodes) +} + +func validateMail(t *testing.T, index int, mail []*ReceivedMessage) { + var cnt int + for _, m := range mail { + if bytes.Equal(m.Payload, expectedMessage) { + cnt++ + } + } + + if cnt == 0 { + // no messages received yet: nothing is wrong + return + } + if cnt > 1 { + t.Fatalf("node %d received %d.", index, cnt) + } + + if cnt == 1 { + result.mutex.Lock() + defer result.mutex.Unlock() + result.counter[index] += cnt + if result.counter[index] > 1 { + t.Fatalf("node %d accumulated %d.", index, result.counter[index]) + } + } +} + +func checkTestStatus() { + var cnt int + var arr [NumNodes]int + + for i := 0; i < NumNodes; i++ { + arr[i] = nodes[i].server.PeerCount() + envelopes := nodes[i].shh.Envelopes() + if len(envelopes) >= NumNodes { + cnt++ + } + } + + if debugMode { + if cntPrev != cnt { + fmt.Printf(" %v \t number of nodes that have received all msgs: %d, number of peers per node: %v \n", + time.Since(prevTime), cnt, arr) + prevTime = time.Now() + cntPrev = cnt + } + } +} + +func isTestComplete() bool { + result.mutex.RLock() + defer result.mutex.RUnlock() + + for i := 0; i < NumNodes; i++ { + if result.counter[i] < 1 { + return false + } + } + + for i := 0; i < NumNodes; i++ { + envelopes := nodes[i].shh.Envelopes() + if len(envelopes) < NumNodes+1 { + return false + } + } + + return true +} + +func sendMsg(t *testing.T, expected bool, id int) { + if t.Failed() { + return + } + + opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1} + if !expected { + opt.KeySym = wrongKey + opt.Topic = wrongTopic + opt.Payload = unexpectedMessage + opt.Payload[0] = byte(id) + } + + msg, err := NewSentMessage(&opt) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + envelope, err := msg.Wrap(&opt, time.Now()) + if err != nil { + t.Fatalf("failed to seal message: %s", err) + } + + err = nodes[id].shh.Send(envelope) + if err != nil { + t.Fatalf("failed to send message: %s", err) + } +} + +func TestPeerBasic(t *testing.T) { + InitSingleTest() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d.", seed) + } + + params.PoW = 0.001 + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d.", seed) + } + + p := newPeer(nil, nil, nil) + p.mark(env) + if !p.marked(env) { + t.Fatalf("failed mark with seed %d.", seed) + } +} + +func checkPowExchangeForNodeZero(t *testing.T) { + const iterations = 200 + for j := 0; j < iterations; j++ { + lastCycle := (j == iterations-1) + ok := checkPowExchangeForNodeZeroOnce(t, lastCycle) + if ok { + break + } + time.Sleep(50 * time.Millisecond) + } +} + +func checkPowExchangeForNodeZeroOnce(t *testing.T, mustPass bool) bool { + cnt := 0 + for i, node := range nodes { + for peer := range node.shh.peers { + if peer.peer.ID() == nodes[0].server.Self().ID() { + cnt++ + if peer.powRequirement != masterPow { + if mustPass { + t.Fatalf("node %d: failed to set the new pow requirement for node zero.", i) + } else { + return false + } + } + } + } + } + if cnt == 0 { + t.Fatalf("looking for node zero: no matching peers found.") + } + return true +} + +func checkPowExchange(t *testing.T) { + for i, node := range nodes { + for peer := range node.shh.peers { + if peer.peer.ID() != nodes[0].server.Self().ID() { + if peer.powRequirement != masterPow { + t.Fatalf("node %d: failed to exchange pow requirement in round %d; expected %f, got %f", + i, round, masterPow, peer.powRequirement) + } + } + } + } +} + +func checkBloomFilterExchangeOnce(t *testing.T, mustPass bool) bool { + for i, node := range nodes { + for peer := range node.shh.peers { + peer.bloomMu.Lock() + equals := bytes.Equal(peer.bloomFilter, masterBloomFilter) + peer.bloomMu.Unlock() + if !equals { + if mustPass { + t.Fatalf("node %d: failed to exchange bloom filter requirement in round %d. \n%x expected \n%x got", + i, round, masterBloomFilter, peer.bloomFilter) + } else { + return false + } + } + } + } + + return true +} + +func checkBloomFilterExchange(t *testing.T) { + const iterations = 200 + for j := 0; j < iterations; j++ { + lastCycle := (j == iterations-1) + ok := checkBloomFilterExchangeOnce(t, lastCycle) + if ok { + break + } + time.Sleep(50 * time.Millisecond) + } +} + +func waitForServersToStart(t *testing.T) { + const iterations = 200 + var started int64 + for j := 0; j < iterations; j++ { + time.Sleep(50 * time.Millisecond) + started = atomic.LoadInt64(&result.started) + if started == NumNodes { + return + } + } + t.Fatalf("Failed to start all the servers, running: %d", started) +} + +//two generic whisper node handshake +func TestPeerHandshakeWithTwoFullNode(t *testing.T) { + w1 := Whisper{} + p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), false}}) + err := p1.handshake() + if err != nil { + t.Fatal() + } +} + +//two generic whisper node handshake. one don't send light flag +func TestHandshakeWithOldVersionWithoutLightModeFlag(t *testing.T) { + w1 := Whisper{} + p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize)}}) + err := p1.handshake() + if err != nil { + t.Fatal() + } +} + +//two light nodes handshake. restriction disabled +func TestTwoLightPeerHandshakeRestrictionOff(t *testing.T) { + w1 := Whisper{} + w1.settings.Store(restrictConnectionBetweenLightClientsIdx, false) + w1.SetLightClientMode(true) + p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}}) + err := p1.handshake() + if err != nil { + t.FailNow() + } +} + +//two light nodes handshake. restriction enabled +func TestTwoLightPeerHandshakeError(t *testing.T) { + w1 := Whisper{} + w1.settings.Store(restrictConnectionBetweenLightClientsIdx, true) + w1.SetLightClientMode(true) + p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}}) + err := p1.handshake() + if err == nil { + t.FailNow() + } +} + +type rwStub struct { + payload []interface{} +} + +func (stub *rwStub) ReadMsg() (p2p.Msg, error) { + size, r, err := rlp.EncodeToReader(stub.payload) + if err != nil { + return p2p.Msg{}, err + } + return p2p.Msg{Code: statusCode, Size: uint32(size), Payload: r}, nil +} + +func (stub *rwStub) WriteMsg(m p2p.Msg) error { + return nil +} diff --git a/whisper/rate_limiter.go b/whisper/rate_limiter.go new file mode 100644 index 000000000..43166210a --- /dev/null +++ b/whisper/rate_limiter.go @@ -0,0 +1,174 @@ +package whisper + +import ( + "bytes" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/tsenart/tb" +) + +type runLoop func(p *Peer, rw p2p.MsgReadWriter) error + +type RateLimiterHandler interface { + ExceedPeerLimit() + ExceedIPLimit() +} + +type MetricsRateLimiterHandler struct{} + +func (MetricsRateLimiterHandler) ExceedPeerLimit() { rateLimiterPeerExceeded.Inc(1) } +func (MetricsRateLimiterHandler) ExceedIPLimit() { rateLimiterIPExceeded.Inc(1) } + +type PeerRateLimiterConfig struct { + LimitPerSecIP int64 + LimitPerSecPeerID int64 + WhitelistedIPs []string + WhitelistedPeerIDs []enode.ID +} + +var defaultPeerRateLimiterConfig = PeerRateLimiterConfig{ + LimitPerSecIP: 10, + LimitPerSecPeerID: 5, + WhitelistedIPs: nil, + WhitelistedPeerIDs: nil, +} + +type PeerRateLimiter struct { + peerIDThrottler *tb.Throttler + ipThrottler *tb.Throttler + + limitPerSecIP int64 + limitPerSecPeerID int64 + + whitelistedPeerIDs []enode.ID + whitelistedIPs []string + + handler RateLimiterHandler +} + +func NewPeerRateLimiter(handler RateLimiterHandler, cfg *PeerRateLimiterConfig) *PeerRateLimiter { + if cfg == nil { + copy := defaultPeerRateLimiterConfig + cfg = © + } + + return &PeerRateLimiter{ + peerIDThrottler: tb.NewThrottler(time.Millisecond * 100), + ipThrottler: tb.NewThrottler(time.Millisecond * 100), + limitPerSecIP: cfg.LimitPerSecIP, + limitPerSecPeerID: cfg.LimitPerSecPeerID, + whitelistedPeerIDs: cfg.WhitelistedPeerIDs, + whitelistedIPs: cfg.WhitelistedIPs, + handler: handler, + } +} + +func (r *PeerRateLimiter) decorate(p *Peer, rw p2p.MsgReadWriter, runLoop runLoop) error { + in, out := p2p.MsgPipe() + defer in.Close() + defer out.Close() + errC := make(chan error, 1) + + // Read from the original reader and write to the message pipe. + go func() { + for { + packet, err := rw.ReadMsg() + if err != nil { + errC <- fmt.Errorf("failed to read packet: %v", err) + return + } + + rateLimiterProcessed.Inc(1) + + var ip string + if p != nil && p.peer != nil { + ip = p.peer.Node().IP().String() + } + if halted := r.throttleIP(ip); halted { + r.handler.ExceedIPLimit() + } + + var peerID []byte + if p != nil { + peerID = p.ID() + } + if halted := r.throttlePeer(peerID); halted { + r.handler.ExceedPeerLimit() + } + + if err := in.WriteMsg(packet); err != nil { + errC <- fmt.Errorf("failed to write packet to pipe: %v", err) + return + } + } + }() + + // Read from the message pipe and write to the original writer. + go func() { + for { + packet, err := in.ReadMsg() + if err != nil { + errC <- fmt.Errorf("failed to read packet from pipe: %v", err) + return + } + if err := rw.WriteMsg(packet); err != nil { + errC <- fmt.Errorf("failed to write packet: %v", err) + return + } + } + }() + + go func() { + errC <- runLoop(p, out) + }() + + return <-errC +} + +// throttleIP throttles a number of messages incoming from a given IP. +// It allows 10 packets per second. +func (r *PeerRateLimiter) throttleIP(ip string) bool { + if r.limitPerSecIP == 0 { + return false + } + if stringSliceContains(r.whitelistedIPs, ip) { + return false + } + return r.ipThrottler.Halt(ip, 1, r.limitPerSecIP) +} + +// throttlePeer throttles a number of messages incoming from a peer. +// It allows 3 packets per second. +func (r *PeerRateLimiter) throttlePeer(peerID []byte) bool { + if r.limitPerSecIP == 0 { + return false + } + var id enode.ID + copy(id[:], peerID) + if enodeIDSliceContains(r.whitelistedPeerIDs, id) { + return false + } + return r.peerIDThrottler.Halt(id.String(), 1, r.limitPerSecPeerID) +} + +func stringSliceContains(s []string, searched string) bool { + for _, item := range s { + if item == searched { + return true + } + } + return false +} + +func enodeIDSliceContains(s []enode.ID, searched enode.ID) bool { + for _, item := range s { + if bytes.Equal(item.Bytes(), searched.Bytes()) { + return true + } + } + return false +} diff --git a/whisper/rate_limiter_test.go b/whisper/rate_limiter_test.go new file mode 100644 index 000000000..991ff6c34 --- /dev/null +++ b/whisper/rate_limiter_test.go @@ -0,0 +1,157 @@ +package whisper + +import ( + "bytes" + "testing" + "time" + + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/stretchr/testify/require" +) + +func TestPeerRateLimiterDecorator(t *testing.T) { + in, out := p2p.MsgPipe() + payload := []byte{0x01, 0x02, 0x03} + msg := p2p.Msg{ + Code: 1, + Size: uint32(len(payload)), + Payload: bytes.NewReader(payload), + ReceivedAt: time.Now(), + } + + go func() { + err := in.WriteMsg(msg) + require.NoError(t, err) + }() + + messages := make(chan p2p.Msg, 1) + runLoop := func(p *Peer, rw p2p.MsgReadWriter) error { + msg, err := rw.ReadMsg() + if err != nil { + return err + } + messages <- msg + return nil + } + + r := NewPeerRateLimiter(&mockRateLimiterHandler{}, nil) + err := r.decorate(nil, out, runLoop) + require.NoError(t, err) + + receivedMsg := <-messages + receivedPayload := make([]byte, receivedMsg.Size) + _, err = receivedMsg.Payload.Read(receivedPayload) + require.NoError(t, err) + require.Equal(t, msg.Code, receivedMsg.Code) + require.Equal(t, payload, receivedPayload) +} + +func TestPeerLimiterThrottlingWithZeroLimit(t *testing.T) { + r := NewPeerRateLimiter(&mockRateLimiterHandler{}, &PeerRateLimiterConfig{}) + for i := 0; i < 1000; i++ { + throttle := r.throttleIP("") + require.False(t, throttle) + throttle = r.throttlePeer([]byte{0x01, 0x02, 0x03}) + require.False(t, throttle) + } +} + +func TestPeerLimiterHandler(t *testing.T) { + h := &mockRateLimiterHandler{} + r := NewPeerRateLimiter(h, nil) + p := &Peer{ + peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil), + } + rw1, rw2 := p2p.MsgPipe() + count := 100 + + go func() { + err := echoMessages(r, p, rw2) + require.NoError(t, err) + }() + + done := make(chan struct{}) + go func() { + for i := 0; i < count; i++ { + msg, err := rw1.ReadMsg() + require.NoError(t, err) + require.EqualValues(t, 101, msg.Code) + } + close(done) + }() + + for i := 0; i < count; i += 1 { + err := rw1.WriteMsg(p2p.Msg{Code: 101}) + require.NoError(t, err) + } + + <-done + + require.EqualValues(t, 100-defaultPeerRateLimiterConfig.LimitPerSecIP, h.exceedIPLimit) + require.EqualValues(t, 100-defaultPeerRateLimiterConfig.LimitPerSecPeerID, h.exceedPeerLimit) +} + +func TestPeerLimiterHandlerWithWhitelisting(t *testing.T) { + h := &mockRateLimiterHandler{} + r := NewPeerRateLimiter(h, &PeerRateLimiterConfig{ + LimitPerSecIP: 1, + LimitPerSecPeerID: 1, + WhitelistedIPs: []string{""}, // no IP is represented as string + WhitelistedPeerIDs: []enode.ID{enode.ID{0xaa, 0xbb, 0xcc}}, + }) + p := &Peer{ + peer: p2p.NewPeer(enode.ID{0xaa, 0xbb, 0xcc}, "test-peer", nil), + } + rw1, rw2 := p2p.MsgPipe() + count := 100 + + go func() { + err := echoMessages(r, p, rw2) + require.NoError(t, err) + }() + + done := make(chan struct{}) + go func() { + for i := 0; i < count; i++ { + msg, err := rw1.ReadMsg() + require.NoError(t, err) + require.EqualValues(t, 101, msg.Code) + } + close(done) + }() + + for i := 0; i < count; i += 1 { + err := rw1.WriteMsg(p2p.Msg{Code: 101}) + require.NoError(t, err) + } + + <-done + + require.Equal(t, 0, h.exceedIPLimit) + require.Equal(t, 0, h.exceedPeerLimit) +} + +func echoMessages(r *PeerRateLimiter, p *Peer, rw p2p.MsgReadWriter) error { + return r.decorate(p, rw, func(p *Peer, rw p2p.MsgReadWriter) error { + for { + msg, err := rw.ReadMsg() + if err != nil { + return err + } + err = rw.WriteMsg(msg) + if err != nil { + return err + } + } + }) +} + +type mockRateLimiterHandler struct { + exceedPeerLimit int + exceedIPLimit int +} + +func (m *mockRateLimiterHandler) ExceedPeerLimit() { m.exceedPeerLimit += 1 } +func (m *mockRateLimiterHandler) ExceedIPLimit() { m.exceedIPLimit += 1 } diff --git a/whisper/topic.go b/whisper/topic.go new file mode 100644 index 000000000..9b6763688 --- /dev/null +++ b/whisper/topic.go @@ -0,0 +1,57 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the Whisper protocol Topic element. + +package whisper + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// TopicType represents a cryptographically secure, probabilistic partial +// classifications of a message, determined as the first (left) 4 bytes of the +// SHA3 hash of some arbitrary data given by the original author of the message. +type TopicType [TopicLength]byte + +// BytesToTopic converts from the byte array representation of a topic +// into the TopicType type. +func BytesToTopic(b []byte) (t TopicType) { + sz := TopicLength + if x := len(b); x < TopicLength { + sz = x + } + for i := 0; i < sz; i++ { + t[i] = b[i] + } + return t +} + +// String converts a topic byte array to a string representation. +func (t *TopicType) String() string { + return common.ToHex(t[:]) +} + +// MarshalText returns the hex representation of t. +func (t TopicType) MarshalText() ([]byte, error) { + return hexutil.Bytes(t[:]).MarshalText() +} + +// UnmarshalText parses a hex representation to a topic. +func (t *TopicType) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Topic", input, t[:]) +} diff --git a/whisper/topic_test.go b/whisper/topic_test.go new file mode 100644 index 000000000..31d7b4873 --- /dev/null +++ b/whisper/topic_test.go @@ -0,0 +1,134 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "encoding/json" + "testing" +) + +var topicStringTests = []struct { + topic TopicType + str string +}{ + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"}, + {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"}, + {topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"}, + {topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"}, +} + +func TestTopicString(t *testing.T) { + for i, tst := range topicStringTests { + s := tst.topic.String() + if s != tst.str { + t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str) + } + } +} + +var bytesToTopicTests = []struct { + data []byte + topic TopicType +}{ + {topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}}, + {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}}, + {topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}}, + {topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}}, + {topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}}, + {topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil}, +} + +var unmarshalTestsGood = []struct { + topic TopicType + data []byte +}{ + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)}, + {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)}, + {topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)}, + {topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)}, +} + +var unmarshalTestsBad = []struct { + topic TopicType + data []byte +}{ + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)}, + {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)}, +} + +var unmarshalTestsUgly = []struct { + topic TopicType + data []byte +}{ + {topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)}, +} + +func TestBytesToTopic(t *testing.T) { + for i, tst := range bytesToTopicTests { + top := BytesToTopic(tst.data) + if top != tst.topic { + t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic) + } + } +} + +func TestUnmarshalTestsGood(t *testing.T) { + for i, tst := range unmarshalTestsGood { + var top TopicType + err := json.Unmarshal(tst.data, &top) + if err != nil { + t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err) + } else if top != tst.topic { + t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic) + } + } +} + +func TestUnmarshalTestsBad(t *testing.T) { + // in this test UnmarshalJSON() is supposed to fail + for i, tst := range unmarshalTestsBad { + var top TopicType + err := json.Unmarshal(tst.data, &top) + if err == nil { + t.Fatalf("failed test %d. input: %v.", i, tst.data) + } + } +} + +func TestUnmarshalTestsUgly(t *testing.T) { + // in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong + for i, tst := range unmarshalTestsUgly { + var top TopicType + err := json.Unmarshal(tst.data, &top) + if err != nil { + t.Errorf("failed test %d. input: %v.", i, tst.data) + } else if top == tst.topic { + t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic) + } + } +} diff --git a/whisper/whisper.go b/whisper/whisper.go new file mode 100644 index 000000000..d333d7c90 --- /dev/null +++ b/whisper/whisper.go @@ -0,0 +1,1587 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "runtime" + "sync" + "time" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/sync/syncmap" +) + +// TimeSyncError error for clock skew errors. +type TimeSyncError error + +// Statistics holds several message-related counter for analytics +// purposes. +type Statistics struct { + messagesCleared int + memoryCleared int + memoryUsed int + cycles int + totalMessagesCleared int +} + +const ( + maxMsgSizeIdx = iota // Maximal message length allowed by the whisper node + overflowIdx // Indicator of message queue overflow + minPowIdx // Minimal PoW required by the whisper node + minPowToleranceIdx // Minimal PoW tolerated by the whisper node for a limited time + bloomFilterIdx // Bloom filter for topics of interest for this node + bloomFilterToleranceIdx // Bloom filter tolerated by the whisper node for a limited time + lightClientModeIdx // Light client mode. (does not forward any messages) + restrictConnectionBetweenLightClientsIdx // Restrict connection between two light clients +) + +// MailServerResponse is the response payload sent by the mailserver +type MailServerResponse struct { + LastEnvelopeHash common.Hash + Cursor []byte + Error error +} + +// Whisper represents a dark communication interface through the Ethereum +// network, using its very own P2P communication layer. +type Whisper struct { + protocol p2p.Protocol // Protocol description and parameters + filters *Filters // Message filters installed with Subscribe function + + privateKeys map[string]*ecdsa.PrivateKey // Private key storage + symKeys map[string][]byte // Symmetric key storage + keyMu sync.RWMutex // Mutex associated with key storages + + poolMu sync.RWMutex // Mutex to sync the message and expiration pools + envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node + expirations map[uint32]mapset.Set // Message expiration pool + + peerMu sync.RWMutex // Mutex to sync the active peer set + peers map[*Peer]struct{} // Set of currently active peers + + messageQueue chan *Envelope // Message queue for normal whisper messages + p2pMsgQueue chan interface{} // Message queue for peer-to-peer messages (not to be forwarded any further) and history delivery confirmations. + quit chan struct{} // Channel used for graceful exit + + settings syncmap.Map // holds configuration settings that can be dynamically changed + + disableConfirmations bool // do not reply with confirmations + + syncAllowance int // maximum time in seconds allowed to process the whisper-related messages + + statsMu sync.Mutex // guard stats + stats Statistics // Statistics of whisper node + + mailServer MailServer // MailServer interface + + rateLimiter *PeerRateLimiter + + messageStoreFabric func() MessageStore + + envelopeFeed event.Feed + + timeSource func() time.Time // source of time for whisper +} + +// New creates a Whisper client ready to communicate through the Ethereum P2P network. +func New(cfg *Config) *Whisper { + if cfg == nil { + cfg = &DefaultConfig + } + + whisper := &Whisper{ + privateKeys: make(map[string]*ecdsa.PrivateKey), + symKeys: make(map[string][]byte), + envelopes: make(map[common.Hash]*Envelope), + expirations: make(map[uint32]mapset.Set), + peers: make(map[*Peer]struct{}), + messageQueue: make(chan *Envelope, messageQueueLimit), + p2pMsgQueue: make(chan interface{}, messageQueueLimit), + quit: make(chan struct{}), + syncAllowance: DefaultSyncAllowance, + timeSource: time.Now, + disableConfirmations: cfg.DisableConfirmations, + } + + whisper.filters = NewFilters(whisper) + + whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW) + whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize) + whisper.settings.Store(overflowIdx, false) + whisper.settings.Store(restrictConnectionBetweenLightClientsIdx, cfg.RestrictConnectionBetweenLightClients) + + // p2p whisper sub protocol handler + whisper.protocol = p2p.Protocol{ + Name: ProtocolName, + Version: uint(ProtocolVersion), + Length: NumberOfMessageCodes, + Run: whisper.HandlePeer, + NodeInfo: func() interface{} { + return map[string]interface{}{ + "version": ProtocolVersionStr, + "maxMessageSize": whisper.MaxMessageSize(), + "minimumPoW": whisper.MinPow(), + } + }, + } + + return whisper +} + +// NewMessageStore returns object that implements MessageStore. +func (whisper *Whisper) NewMessageStore() MessageStore { + if whisper.messageStoreFabric != nil { + return whisper.messageStoreFabric() + } + return NewMemoryMessageStore() +} + +// SetMessageStore allows to inject custom implementation of the message store. +func (whisper *Whisper) SetMessageStore(fabric func() MessageStore) { + whisper.messageStoreFabric = fabric +} + +// SetTimeSource assigns a particular source of time to a whisper object. +func (whisper *Whisper) SetTimeSource(timesource func() time.Time) { + whisper.timeSource = timesource +} + +// SubscribeEnvelopeEvents subscribes to envelopes feed. +// In order to prevent blocking whisper producers events must be amply buffered. +func (whisper *Whisper) SubscribeEnvelopeEvents(events chan<- EnvelopeEvent) event.Subscription { + return whisper.envelopeFeed.Subscribe(events) +} + +// MinPow returns the PoW value required by this node. +func (whisper *Whisper) MinPow() float64 { + val, exist := whisper.settings.Load(minPowIdx) + if !exist || val == nil { + return DefaultMinimumPoW + } + v, ok := val.(float64) + if !ok { + log.Error("Error loading minPowIdx, using default") + return DefaultMinimumPoW + } + return v +} + +// MinPowTolerance returns the value of minimum PoW which is tolerated for a limited +// time after PoW was changed. If sufficient time have elapsed or no change of PoW +// have ever occurred, the return value will be the same as return value of MinPow(). +func (whisper *Whisper) MinPowTolerance() float64 { + val, exist := whisper.settings.Load(minPowToleranceIdx) + if !exist || val == nil { + return DefaultMinimumPoW + } + return val.(float64) +} + +// BloomFilter returns the aggregated bloom filter for all the topics of interest. +// The nodes are required to send only messages that match the advertised bloom filter. +// If a message does not match the bloom, it will tantamount to spam, and the peer will +// be disconnected. +func (whisper *Whisper) BloomFilter() []byte { + val, exist := whisper.settings.Load(bloomFilterIdx) + if !exist || val == nil { + return nil + } + return val.([]byte) +} + +// BloomFilterTolerance returns the bloom filter which is tolerated for a limited +// time after new bloom was advertised to the peers. If sufficient time have elapsed +// or no change of bloom filter have ever occurred, the return value will be the same +// as return value of BloomFilter(). +func (whisper *Whisper) BloomFilterTolerance() []byte { + val, exist := whisper.settings.Load(bloomFilterToleranceIdx) + if !exist || val == nil { + return nil + } + return val.([]byte) +} + +// MaxMessageSize returns the maximum accepted message size. +func (whisper *Whisper) MaxMessageSize() uint32 { + val, _ := whisper.settings.Load(maxMsgSizeIdx) + return val.(uint32) +} + +// Overflow returns an indication if the message queue is full. +func (whisper *Whisper) Overflow() bool { + val, _ := whisper.settings.Load(overflowIdx) + return val.(bool) +} + +// APIs returns the RPC descriptors the Whisper implementation offers +func (whisper *Whisper) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: ProtocolName, + Version: ProtocolVersionStr, + Service: NewPublicWhisperAPI(whisper), + Public: true, + }, + } +} + +// GetCurrentTime returns current time. +func (whisper *Whisper) GetCurrentTime() time.Time { + return whisper.timeSource() +} + +// RegisterServer registers MailServer interface. +// MailServer will process all the incoming messages with p2pRequestCode. +func (whisper *Whisper) RegisterServer(server MailServer) { + whisper.mailServer = server +} + +// Protocols returns the whisper sub-protocols ran by this particular client. +func (whisper *Whisper) Protocols() []p2p.Protocol { + return []p2p.Protocol{whisper.protocol} +} + +// Version returns the whisper sub-protocols version number. +func (whisper *Whisper) Version() uint { + return whisper.protocol.Version +} + +// SetMaxMessageSize sets the maximal message size allowed by this node +func (whisper *Whisper) SetMaxMessageSize(size uint32) error { + if size > MaxMessageSize { + return fmt.Errorf("message size too large [%d>%d]", size, MaxMessageSize) + } + whisper.settings.Store(maxMsgSizeIdx, size) + return nil +} + +// SetBloomFilter sets the new bloom filter +func (whisper *Whisper) SetBloomFilter(bloom []byte) error { + if len(bloom) != BloomFilterSize { + return fmt.Errorf("invalid bloom filter size: %d", len(bloom)) + } + + b := make([]byte, BloomFilterSize) + copy(b, bloom) + + whisper.settings.Store(bloomFilterIdx, b) + whisper.notifyPeersAboutBloomFilterChange(b) + + go func() { + // allow some time before all the peers have processed the notification + time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) + whisper.settings.Store(bloomFilterToleranceIdx, b) + }() + + return nil +} + +// SetMinimumPoW sets the minimal PoW required by this node +func (whisper *Whisper) SetMinimumPoW(val float64) error { + if val < 0.0 { + return fmt.Errorf("invalid PoW: %f", val) + } + + whisper.settings.Store(minPowIdx, val) + whisper.notifyPeersAboutPowRequirementChange(val) + + go func() { + // allow some time before all the peers have processed the notification + time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) + whisper.settings.Store(minPowToleranceIdx, val) + }() + + return nil +} + +// SetMinimumPowTest sets the minimal PoW in test environment +func (whisper *Whisper) SetMinimumPowTest(val float64) { + whisper.settings.Store(minPowIdx, val) + whisper.notifyPeersAboutPowRequirementChange(val) + whisper.settings.Store(minPowToleranceIdx, val) +} + +//SetLightClientMode makes node light client (does not forward any messages) +func (whisper *Whisper) SetLightClientMode(v bool) { + whisper.settings.Store(lightClientModeIdx, v) +} + +func (whisper *Whisper) SetRateLimiter(r *PeerRateLimiter) { + whisper.rateLimiter = r +} + +//LightClientMode indicates is this node is light client (does not forward any messages) +func (whisper *Whisper) LightClientMode() bool { + val, exist := whisper.settings.Load(lightClientModeIdx) + if !exist || val == nil { + return false + } + v, ok := val.(bool) + return v && ok +} + +//LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed +func (whisper *Whisper) LightClientModeConnectionRestricted() bool { + val, exist := whisper.settings.Load(restrictConnectionBetweenLightClientsIdx) + if !exist || val == nil { + return false + } + v, ok := val.(bool) + return v && ok +} + +func (whisper *Whisper) notifyPeersAboutPowRequirementChange(pow float64) { + arr := whisper.getPeers() + for _, p := range arr { + err := p.notifyAboutPowRequirementChange(pow) + if err != nil { + // allow one retry + err = p.notifyAboutPowRequirementChange(pow) + } + if err != nil { + log.Warn("failed to notify peer about new pow requirement", "peer", p.ID(), "error", err) + } + } +} + +func (whisper *Whisper) notifyPeersAboutBloomFilterChange(bloom []byte) { + arr := whisper.getPeers() + for _, p := range arr { + err := p.notifyAboutBloomFilterChange(bloom) + if err != nil { + // allow one retry + err = p.notifyAboutBloomFilterChange(bloom) + } + if err != nil { + log.Warn("failed to notify peer about new bloom filter", "peer", p.ID(), "error", err) + } + } +} + +func (whisper *Whisper) getPeers() []*Peer { + arr := make([]*Peer, len(whisper.peers)) + i := 0 + whisper.peerMu.Lock() + for p := range whisper.peers { + arr[i] = p + i++ + } + whisper.peerMu.Unlock() + return arr +} + +// getPeer retrieves peer by ID +func (whisper *Whisper) getPeer(peerID []byte) (*Peer, error) { + whisper.peerMu.Lock() + defer whisper.peerMu.Unlock() + for p := range whisper.peers { + id := p.peer.ID() + if bytes.Equal(peerID, id[:]) { + return p, nil + } + } + return nil, fmt.Errorf("Could not find peer with ID: %x", peerID) +} + +// AllowP2PMessagesFromPeer marks specific peer trusted, +// which will allow it to send historic (expired) messages. +func (whisper *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error { + p, err := whisper.getPeer(peerID) + if err != nil { + return err + } + p.trusted = true + return nil +} + +// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer, +// which is known to implement MailServer interface, and is supposed to process this +// request and respond with a number of peer-to-peer messages (possibly expired), +// which are not supposed to be forwarded any further. +// The whisper protocol is agnostic of the format and contents of envelope. +func (whisper *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { + return whisper.RequestHistoricMessagesWithTimeout(peerID, envelope, 0) +} + +func (whisper *Whisper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope *Envelope, timeout time.Duration) error { + p, err := whisper.getPeer(peerID) + if err != nil { + return err + } + whisper.envelopeFeed.Send(EnvelopeEvent{ + Peer: p.peer.ID(), + Hash: envelope.Hash(), + Event: EventMailServerRequestSent, + }) + p.trusted = true + err = p2p.Send(p.ws, p2pRequestCode, envelope) + if timeout != 0 { + go whisper.expireRequestHistoricMessages(p.peer.ID(), envelope.Hash(), timeout) + } + return err +} + +func (whisper *Whisper) SendMessagesRequest(peerID []byte, request MessagesRequest) error { + if err := request.Validate(); err != nil { + return err + } + p, err := whisper.getPeer(peerID) + if err != nil { + return err + } + p.trusted = true + if err := p2p.Send(p.ws, p2pRequestCode, request); err != nil { + return err + } + whisper.envelopeFeed.Send(EnvelopeEvent{ + Peer: p.peer.ID(), + Hash: common.BytesToHash(request.ID), + Event: EventMailServerRequestSent, + }) + return nil +} + +func (whisper *Whisper) expireRequestHistoricMessages(peer enode.ID, hash common.Hash, timeout time.Duration) { + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-whisper.quit: + return + case <-timer.C: + whisper.envelopeFeed.Send(EnvelopeEvent{ + Peer: peer, + Hash: hash, + Event: EventMailServerRequestExpired, + }) + } +} + +func (whisper *Whisper) SendHistoricMessageResponse(peer *Peer, payload []byte) error { + size, r, err := rlp.EncodeToReader(payload) + if err != nil { + return err + } + + return peer.ws.WriteMsg(p2p.Msg{Code: p2pRequestCompleteCode, Size: uint32(size), Payload: r}) +} + +// SyncMessages can be sent between two Mail Servers and syncs envelopes between them. +func (whisper *Whisper) SyncMessages(peerID []byte, req SyncMailRequest) error { + if whisper.mailServer == nil { + return errors.New("can not sync messages if Mail Server is not configured") + } + + p, err := whisper.getPeer(peerID) + if err != nil { + return err + } + + if err := req.Validate(); err != nil { + return err + } + + return p2p.Send(p.ws, p2pSyncRequestCode, req) +} + +// SendSyncResponse sends a response to a Mail Server with a slice of envelopes. +func (whisper *Whisper) SendSyncResponse(p *Peer, data SyncResponse) error { + return p2p.Send(p.ws, p2pSyncResponseCode, data) +} + +// SendRawSyncResponse sends a response to a Mail Server with a slice of envelopes. +func (whisper *Whisper) SendRawSyncResponse(p *Peer, data RawSyncResponse) error { + return p2p.Send(p.ws, p2pSyncResponseCode, data) +} + +// SendP2PMessage sends a peer-to-peer message to a specific peer. +func (whisper *Whisper) SendP2PMessage(peerID []byte, envelopes ...*Envelope) error { + p, err := whisper.getPeer(peerID) + if err != nil { + return err + } + return whisper.SendP2PDirect(p, envelopes...) +} + +// SendP2PDirect sends a peer-to-peer message to a specific peer. +// If only a single envelope is given, data is sent as a single object +// rather than a slice. This is important to keep this method backward compatible +// as it used to send only single envelopes. +func (whisper *Whisper) SendP2PDirect(peer *Peer, envelopes ...*Envelope) error { + if len(envelopes) == 1 { + return p2p.Send(peer.ws, p2pMessageCode, envelopes[0]) + } + return p2p.Send(peer.ws, p2pMessageCode, envelopes) +} + +// SendRawP2PDirect sends a peer-to-peer message to a specific peer. +// If only a single envelope is given, data is sent as a single object +// rather than a slice. This is important to keep this method backward compatible +// as it used to send only single envelopes. +func (whisper *Whisper) SendRawP2PDirect(peer *Peer, envelopes ...rlp.RawValue) error { + if len(envelopes) == 1 { + return p2p.Send(peer.ws, p2pMessageCode, envelopes[0]) + } + return p2p.Send(peer.ws, p2pMessageCode, envelopes) +} + +// NewKeyPair generates a new cryptographic identity for the client, and injects +// it into the known identities for message decryption. Returns ID of the new key pair. +func (whisper *Whisper) NewKeyPair() (string, error) { + key, err := crypto.GenerateKey() + if err != nil || !validatePrivateKey(key) { + key, err = crypto.GenerateKey() // retry once + } + if err != nil { + return "", err + } + if !validatePrivateKey(key) { + return "", fmt.Errorf("failed to generate valid key") + } + + id, err := toDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + if err != nil { + return "", err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.privateKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") + } + whisper.privateKeys[id] = key + return id, nil +} + +// DeleteKeyPair deletes the specified key if it exists. +func (whisper *Whisper) DeleteKeyPair(key string) bool { + deterministicID, err := toDeterministicID(key, keyIDSize) + if err != nil { + return false + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.privateKeys[deterministicID] != nil { + delete(whisper.privateKeys, deterministicID) + return true + } + return false +} + +// AddKeyPair imports a asymmetric private key and returns it identifier. +func (whisper *Whisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { + id, err := makeDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + if err != nil { + return "", err + } + if whisper.HasKeyPair(id) { + return id, nil // no need to re-inject + } + + whisper.keyMu.Lock() + whisper.privateKeys[id] = key + whisper.keyMu.Unlock() + log.Info("Whisper identity added", "id", id, "pubkey", common.ToHex(crypto.FromECDSAPub(&key.PublicKey))) + + return id, nil +} + +// SelectKeyPair adds cryptographic identity, and makes sure +// that it is the only private key known to the node. +func (whisper *Whisper) SelectKeyPair(key *ecdsa.PrivateKey) error { + id, err := makeDeterministicID(common.ToHex(crypto.FromECDSAPub(&key.PublicKey)), keyIDSize) + if err != nil { + return err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + whisper.privateKeys = make(map[string]*ecdsa.PrivateKey) // reset key store + whisper.privateKeys[id] = key + + log.Info("Whisper identity selected", "id", id, "key", common.ToHex(crypto.FromECDSAPub(&key.PublicKey))) + return nil +} + +// DeleteKeyPairs removes all cryptographic identities known to the node +func (whisper *Whisper) DeleteKeyPairs() error { + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + whisper.privateKeys = make(map[string]*ecdsa.PrivateKey) + + return nil +} + +// HasKeyPair checks if the whisper node is configured with the private key +// of the specified public pair. +func (whisper *Whisper) HasKeyPair(id string) bool { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return false + } + + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + return whisper.privateKeys[deterministicID] != nil +} + +// GetPrivateKey retrieves the private key of the specified identity. +func (whisper *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return nil, err + } + + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + key := whisper.privateKeys[deterministicID] + if key == nil { + return nil, fmt.Errorf("invalid id") + } + return key, nil +} + +// GenerateSymKey generates a random symmetric key and stores it under id, +// which is then returned. Will be used in the future for session key exchange. +func (whisper *Whisper) GenerateSymKey() (string, error) { + key, err := generateSecureRandomData(aesKeyLength) + if err != nil { + return "", err + } else if !validateDataIntegrity(key, aesKeyLength) { + return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") + } + + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.symKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") + } + whisper.symKeys[id] = key + return id, nil +} + +// AddSymKey stores the key with a given id. +func (whisper *Whisper) AddSymKey(id string, key []byte) (string, error) { + deterministicID, err := toDeterministicID(id, keyIDSize) + if err != nil { + return "", err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.symKeys[deterministicID] != nil { + return "", fmt.Errorf("key already exists: %v", id) + } + whisper.symKeys[deterministicID] = key + return deterministicID, nil +} + +// AddSymKeyDirect stores the key, and returns its id. +func (whisper *Whisper) AddSymKeyDirect(key []byte) (string, error) { + if len(key) != aesKeyLength { + return "", fmt.Errorf("wrong key size: %d", len(key)) + } + + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + if whisper.symKeys[id] != nil { + return "", fmt.Errorf("failed to generate unique ID") + } + whisper.symKeys[id] = key + return id, nil +} + +// AddSymKeyFromPassword generates the key from password, stores it, and returns its id. +func (whisper *Whisper) AddSymKeyFromPassword(password string) (string, error) { + id, err := GenerateRandomID() + if err != nil { + return "", fmt.Errorf("failed to generate ID: %s", err) + } + if whisper.HasSymKey(id) { + return "", fmt.Errorf("failed to generate unique ID") + } + + // kdf should run no less than 0.1 seconds on an average computer, + // because it's an once in a session experience + derived := pbkdf2.Key([]byte(password), nil, 65356, aesKeyLength, sha256.New) + if err != nil { + return "", err + } + + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + + // double check is necessary, because deriveKeyMaterial() is very slow + if whisper.symKeys[id] != nil { + return "", fmt.Errorf("critical error: failed to generate unique ID") + } + whisper.symKeys[id] = derived + return id, nil +} + +// HasSymKey returns true if there is a key associated with the given id. +// Otherwise returns false. +func (whisper *Whisper) HasSymKey(id string) bool { + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + return whisper.symKeys[id] != nil +} + +// DeleteSymKey deletes the key associated with the name string if it exists. +func (whisper *Whisper) DeleteSymKey(id string) bool { + whisper.keyMu.Lock() + defer whisper.keyMu.Unlock() + if whisper.symKeys[id] != nil { + delete(whisper.symKeys, id) + return true + } + return false +} + +// GetSymKey returns the symmetric key associated with the given id. +func (whisper *Whisper) GetSymKey(id string) ([]byte, error) { + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + if whisper.symKeys[id] != nil { + return whisper.symKeys[id], nil + } + return nil, fmt.Errorf("non-existent key ID") +} + +// Subscribe installs a new message handler used for filtering, decrypting +// and subsequent storing of incoming messages. +func (whisper *Whisper) Subscribe(f *Filter) (string, error) { + s, err := whisper.filters.Install(f) + if err == nil { + whisper.updateBloomFilter(f) + } + return s, err +} + +// updateBloomFilter recalculates the new value of bloom filter, +// and informs the peers if necessary. +func (whisper *Whisper) updateBloomFilter(f *Filter) { + aggregate := make([]byte, BloomFilterSize) + for _, t := range f.Topics { + top := BytesToTopic(t) + b := TopicToBloom(top) + aggregate = addBloom(aggregate, b) + } + + if !BloomFilterMatch(whisper.BloomFilter(), aggregate) { + // existing bloom filter must be updated + aggregate = addBloom(whisper.BloomFilter(), aggregate) + whisper.SetBloomFilter(aggregate) + } +} + +// GetFilter returns the filter by id. +func (whisper *Whisper) GetFilter(id string) *Filter { + return whisper.filters.Get(id) +} + +// Unsubscribe removes an installed message handler. +func (whisper *Whisper) Unsubscribe(id string) error { + ok := whisper.filters.Uninstall(id) + if !ok { + return fmt.Errorf("Unsubscribe: Invalid ID") + } + return nil +} + +// Send injects a message into the whisper send queue, to be distributed in the +// network in the coming cycles. +func (whisper *Whisper) Send(envelope *Envelope) error { + ok, err := whisper.add(envelope, false) + if err == nil && !ok { + return fmt.Errorf("failed to add envelope") + } + return err +} + +// Start implements node.Service, starting the background data propagation thread +// of the Whisper protocol. +func (whisper *Whisper) Start(*p2p.Server) error { + log.Info("started whisper v." + ProtocolVersionStr) + go whisper.update() + + numCPU := runtime.NumCPU() + for i := 0; i < numCPU; i++ { + go whisper.processQueue() + } + go whisper.processP2P() + + return nil +} + +// Stop implements node.Service, stopping the background data propagation thread +// of the Whisper protocol. +func (whisper *Whisper) Stop() error { + close(whisper.quit) + log.Info("whisper stopped") + return nil +} + +// HandlePeer is called by the underlying P2P layer when the whisper sub-protocol +// connection is negotiated. +func (whisper *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { + // Create the new peer and start tracking it + whisperPeer := newPeer(whisper, peer, rw) + + whisper.peerMu.Lock() + whisper.peers[whisperPeer] = struct{}{} + whisper.peerMu.Unlock() + + defer func() { + whisper.peerMu.Lock() + delete(whisper.peers, whisperPeer) + whisper.peerMu.Unlock() + }() + + // Run the peer handshake and state updates + if err := whisperPeer.handshake(); err != nil { + return err + } + whisperPeer.start() + defer whisperPeer.stop() + + if whisper.rateLimiter != nil { + return whisper.rateLimiter.decorate(whisperPeer, rw, whisper.runMessageLoop) + } + return whisper.runMessageLoop(whisperPeer, rw) +} + +func (whisper *Whisper) sendConfirmation(peer enode.ID, rw p2p.MsgReadWriter, data []byte, + envelopeErrors []EnvelopeError) { + batchHash := crypto.Keccak256Hash(data) + if err := p2p.Send(rw, messageResponseCode, NewMessagesResponse(batchHash, envelopeErrors)); err != nil { + log.Warn("failed to deliver messages response", "hash", batchHash, "envelopes errors", envelopeErrors, + "peer", peer, "error", err) + } + if err := p2p.Send(rw, batchAcknowledgedCode, batchHash); err != nil { + log.Warn("failed to deliver confirmation", "hash", batchHash, "peer", peer, "error", err) + } +} + +// runMessageLoop reads and processes inbound messages directly to merge into client-global state. +func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { + for { + // fetch the next packet + packet, err := rw.ReadMsg() + if err != nil { + log.Info("message loop", "peer", p.peer.ID(), "err", err) + return err + } + if packet.Size > whisper.MaxMessageSize() { + log.Warn("oversized message received", "peer", p.peer.ID()) + return errors.New("oversized message received") + } + + switch packet.Code { + case statusCode: + // this should not happen, but no need to panic; just ignore this message. + log.Warn("unxepected status message received", "peer", p.peer.ID()) + case messagesCode: + // decode the contained envelopes + data, err := ioutil.ReadAll(packet.Payload) + if err != nil { + log.Warn("failed to read envelopes data", "peer", p.peer.ID(), "error", err) + return errors.New("invalid enveloopes") + } + + var envelopes []*Envelope + if err := rlp.DecodeBytes(data, &envelopes); err != nil { + log.Warn("failed to decode envelopes, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid envelopes") + } + trouble := false + envelopeErrors := []EnvelopeError{} + for _, env := range envelopes { + cached, err := whisper.add(env, whisper.LightClientMode()) + if err != nil { + _, isTimeSyncError := err.(TimeSyncError) + if !isTimeSyncError { + trouble = true + log.Error("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) + } + envelopeErrors = append(envelopeErrors, ErrorToEnvelopeError(env.Hash(), err)) + } + + whisper.envelopeFeed.Send(EnvelopeEvent{ + Event: EventEnvelopeReceived, + Hash: env.Hash(), + Peer: p.peer.ID(), + }) + if cached { + p.mark(env) + } + } + if !whisper.disableConfirmations { + go whisper.sendConfirmation(p.peer.ID(), rw, data, envelopeErrors) + } + + if trouble { + return errors.New("invalid envelope") + } + case messageResponseCode: + var multiResponse MultiVersionResponse + if err := packet.Decode(&multiResponse); err != nil { + log.Error("failed to decode messages response", "peer", p.peer.ID(), "error", err) + return errors.New("invalid response message") + } + if multiResponse.Version == 1 { + response, err := multiResponse.DecodeResponse1() + if err != nil { + log.Error("failed to decode messages response into first version of response", "peer", p.peer.ID(), "error", err) + } + whisper.envelopeFeed.Send(EnvelopeEvent{ + Batch: response.Hash, + Event: EventBatchAcknowledged, + Peer: p.peer.ID(), + Data: response.Errors, + }) + } else { + log.Warn("unknown version of the messages response was received. response is ignored", "peer", p.peer.ID(), "version", multiResponse.Version) + } + case batchAcknowledgedCode: + var batchHash common.Hash + if err := packet.Decode(&batchHash); err != nil { + log.Error("failed to decode confirmation into common.Hash", "peer", p.peer.ID(), "error", err) + return errors.New("invalid confirmation message") + } + whisper.envelopeFeed.Send(EnvelopeEvent{ + Batch: batchHash, + Event: EventBatchAcknowledged, + Peer: p.peer.ID(), + }) + case powRequirementCode: + s := rlp.NewStream(packet.Payload, uint64(packet.Size)) + i, err := s.Uint() + if err != nil { + log.Warn("failed to decode powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid powRequirementCode message") + } + f := math.Float64frombits(i) + if math.IsInf(f, 0) || math.IsNaN(f) || f < 0.0 { + log.Warn("invalid value in powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid value in powRequirementCode message") + } + p.powRequirement = f + case bloomFilterExCode: + var bloom []byte + err := packet.Decode(&bloom) + if err == nil && len(bloom) != BloomFilterSize { + err = fmt.Errorf("wrong bloom filter size %d", len(bloom)) + } + + if err != nil { + log.Warn("failed to decode bloom filter exchange message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid bloom filter exchange message") + } + p.setBloomFilter(bloom) + case p2pMessageCode: + // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. + // this message is not supposed to be forwarded to other peers, and + // therefore might not satisfy the PoW, expiry and other requirements. + // these messages are only accepted from the trusted peer. + if p.trusted { + var ( + envelope *Envelope + envelopes []*Envelope + err error + ) + + // Read all data as we will try to decode it possibly twice + // to keep backward compatibility. + data, err := ioutil.ReadAll(packet.Payload) + if err != nil { + return fmt.Errorf("invalid direct messages: %v", err) + } + r := bytes.NewReader(data) + + packet.Payload = r + + if err = packet.Decode(&envelopes); err == nil { + for _, envelope := range envelopes { + whisper.postP2P(envelope) + } + continue + } + + // As we failed to decode envelopes, let's set the offset + // to the beginning and try decode data again. + // Decoding to a single Envelope is required + // to be backward compatible. + if _, err := r.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("invalid direct messages: %v", err) + } + + if err = packet.Decode(&envelope); err == nil { + whisper.postP2P(envelope) + continue + } + + if err != nil { + log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return fmt.Errorf("invalid direct message: %v", err) + } + } + case p2pSyncRequestCode: + // TODO(adam): should we limit who can send this request? + if whisper.mailServer != nil { + var request SyncMailRequest + if err := packet.Decode(&request); err != nil { + return fmt.Errorf("failed to decode p2pSyncRequestCode payload: %v", err) + } + + if err := request.Validate(); err != nil { + return fmt.Errorf("sync mail request was invalid: %v", err) + } + + if err := whisper.mailServer.SyncMail(p, request); err != nil { + log.Error( + "failed to sync envelopes", + "peer", p.peer.ID().String(), + ) + _ = whisper.SendSyncResponse( + p, + SyncResponse{Error: err.Error()}, + ) + return err + } + } else { + log.Debug("requested to sync messages but mail servers is not registered", "peer", p.peer.ID().String()) + } + case p2pSyncResponseCode: + // TODO(adam): currently, there is no feedback when a sync response + // is received. An idea to fix this: + // 1. Sending a request contains an ID, + // 2. Each sync response contains this ID, + // 3. There is a way to call whisper.SyncMessages() and wait for the response.Final to be received for that particular request ID. + // 4. If Cursor is not empty, another p2pSyncRequestCode should be sent. + if p.trusted && whisper.mailServer != nil { + var resp SyncResponse + if err = packet.Decode(&resp); err != nil { + return fmt.Errorf("failed to decode p2pSyncResponseCode payload: %v", err) + } + + log.Info("received sync response", "count", len(resp.Envelopes), "final", resp.Final, "err", resp.Error, "cursor", resp.Cursor) + + for _, envelope := range resp.Envelopes { + whisper.mailServer.Archive(envelope) + } + + if resp.Error != "" || resp.Final { + whisper.envelopeFeed.Send(EnvelopeEvent{ + Event: EventMailServerSyncFinished, + Peer: p.peer.ID(), + Data: SyncEventResponse{ + Cursor: resp.Cursor, + Error: resp.Error, + }, + }) + } + } + case p2pRequestCode: + // Must be processed if mail server is implemented. Otherwise ignore. + if whisper.mailServer != nil { + // Read all data as we will try to decode it possibly twice. + data, err := ioutil.ReadAll(packet.Payload) + if err != nil { + return fmt.Errorf("invalid direct messages: %v", err) + } + r := bytes.NewReader(data) + packet.Payload = r + + var requestDeprecated Envelope + errDepReq := packet.Decode(&requestDeprecated) + if errDepReq == nil { + whisper.mailServer.DeliverMail(p, &requestDeprecated) + continue + } else { + log.Info("failed to decode p2p request message (deprecated)", "peer", p.peer.ID(), "err", errDepReq) + } + + // As we failed to decode the request, let's set the offset + // to the beginning and try decode it again. + if _, err := r.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("invalid direct messages: %v", err) + } + + var request MessagesRequest + errReq := packet.Decode(&request) + if errReq == nil { + whisper.mailServer.Deliver(p, request) + continue + } else { + log.Info("failed to decode p2p request message", "peer", p.peer.ID(), "err", errReq) + } + + return errors.New("invalid p2p request") + } + case p2pRequestCompleteCode: + if p.trusted { + var payload []byte + if err := packet.Decode(&payload); err != nil { + log.Warn("failed to decode response message, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid request response message") + } + + event, err := CreateMailServerEvent(p.peer.ID(), payload) + + if err != nil { + log.Warn("error while parsing request complete code, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return err + } + + if event != nil { + whisper.postP2P(*event) + } + + } + default: + // New message types might be implemented in the future versions of Whisper. + // For forward compatibility, just ignore. + } + + packet.Discard() + } +} + +// add inserts a new envelope into the message pool to be distributed within the +// whisper network. It also inserts the envelope into the expiration pool at the +// appropriate time-stamp. In case of error, connection should be dropped. +// param isP2P indicates whether the message is peer-to-peer (should not be forwarded). +func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { + now := uint32(whisper.timeSource().Unix()) + sent := envelope.Expiry - envelope.TTL + + envelopeAddedCounter.Inc(1) + if sent > now { + if sent-DefaultSyncAllowance > now { + envelopeErrFromFutureCounter.Inc(1) + log.Warn("envelope created in the future", "hash", envelope.Hash()) + return false, TimeSyncError(errors.New("envelope from future")) + } + // recalculate PoW, adjusted for the time difference, plus one second for latency + envelope.calculatePoW(sent - now + 1) + } + + if envelope.Expiry < now { + if envelope.Expiry+DefaultSyncAllowance*2 < now { + envelopeErrVeryOldCounter.Inc(1) + log.Warn("very old envelope", "hash", envelope.Hash()) + return false, TimeSyncError(errors.New("very old envelope")) + } + log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) + envelopeErrExpiredCounter.Inc(1) + return false, nil // drop envelope without error + } + + if uint32(envelope.size()) > whisper.MaxMessageSize() { + envelopeErrOversizedCounter.Inc(1) + return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) + } + + if envelope.PoW() < whisper.MinPow() { + // maybe the value was recently changed, and the peers did not adjust yet. + // in this case the previous value is retrieved by MinPowTolerance() + // for a short period of peer synchronization. + if envelope.PoW() < whisper.MinPowTolerance() { + envelopeErrLowPowCounter.Inc(1) + return false, fmt.Errorf("envelope with low PoW received: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex()) + } + } + + if !BloomFilterMatch(whisper.BloomFilter(), envelope.Bloom()) { + // maybe the value was recently changed, and the peers did not adjust yet. + // in this case the previous value is retrieved by BloomFilterTolerance() + // for a short period of peer synchronization. + if !BloomFilterMatch(whisper.BloomFilterTolerance(), envelope.Bloom()) { + envelopeErrNoBloomMatchCounter.Inc(1) + return false, fmt.Errorf("envelope does not match bloom filter, hash=[%v], bloom: \n%x \n%x \n%x", + envelope.Hash().Hex(), whisper.BloomFilter(), envelope.Bloom(), envelope.Topic) + } + } + + hash := envelope.Hash() + + whisper.poolMu.Lock() + _, alreadyCached := whisper.envelopes[hash] + if !alreadyCached { + whisper.envelopes[hash] = envelope + if whisper.expirations[envelope.Expiry] == nil { + whisper.expirations[envelope.Expiry] = mapset.NewThreadUnsafeSet() + } + if !whisper.expirations[envelope.Expiry].Contains(hash) { + whisper.expirations[envelope.Expiry].Add(hash) + } + } + whisper.poolMu.Unlock() + + if alreadyCached { + log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) + } else { + log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) + envelopeNewAddedCounter.Inc(1) + envelopeSizeMeter.Mark(int64(envelope.size())) + whisper.statsMu.Lock() + whisper.stats.memoryUsed += envelope.size() + whisper.statsMu.Unlock() + whisper.postEvent(envelope, isP2P) // notify the local node about the new message + if whisper.mailServer != nil { + whisper.mailServer.Archive(envelope) + whisper.envelopeFeed.Send(EnvelopeEvent{ + Hash: envelope.Hash(), + Event: EventMailServerEnvelopeArchived, + }) + } + } + return true, nil +} + +func (whisper *Whisper) postP2P(event interface{}) { + whisper.p2pMsgQueue <- event +} + +// postEvent queues the message for further processing. +func (whisper *Whisper) postEvent(envelope *Envelope, isP2P bool) { + if isP2P { + whisper.postP2P(envelope) + } else { + whisper.checkOverflow() + whisper.messageQueue <- envelope + } + +} + +// checkOverflow checks if message queue overflow occurs and reports it if necessary. +func (whisper *Whisper) checkOverflow() { + queueSize := len(whisper.messageQueue) + + if queueSize == messageQueueLimit { + if !whisper.Overflow() { + whisper.settings.Store(overflowIdx, true) + log.Warn("message queue overflow") + } + } else if queueSize <= messageQueueLimit/2 { + if whisper.Overflow() { + whisper.settings.Store(overflowIdx, false) + log.Warn("message queue overflow fixed (back to normal)") + } + } +} + +// processQueue delivers the messages to the watchers during the lifetime of the whisper node. +func (whisper *Whisper) processQueue() { + for { + select { + case <-whisper.quit: + return + case e := <-whisper.messageQueue: + whisper.filters.NotifyWatchers(e, false) + whisper.envelopeFeed.Send(EnvelopeEvent{ + Hash: e.Hash(), + Event: EventEnvelopeAvailable, + }) + } + } +} + +func (whisper *Whisper) processP2P() { + for { + select { + case <-whisper.quit: + return + case e := <-whisper.p2pMsgQueue: + switch event := e.(type) { + case *Envelope: + whisper.filters.NotifyWatchers(event, true) + whisper.envelopeFeed.Send(EnvelopeEvent{ + Hash: event.Hash(), + Event: EventEnvelopeAvailable, + }) + case EnvelopeEvent: + whisper.envelopeFeed.Send(event) + } + } + } +} + +// update loops until the lifetime of the whisper node, updating its internal +// state by expiring stale messages from the pool. +func (whisper *Whisper) update() { + // Start a ticker to check for expirations + expire := time.NewTicker(expirationCycle) + + // Repeat updates until termination is requested + for { + select { + case <-expire.C: + whisper.expire() + + case <-whisper.quit: + return + } + } +} + +// expire iterates over all the expiration timestamps, removing all stale +// messages from the pools. +func (whisper *Whisper) expire() { + whisper.poolMu.Lock() + defer whisper.poolMu.Unlock() + + whisper.statsMu.Lock() + defer whisper.statsMu.Unlock() + whisper.stats.reset() + now := uint32(whisper.timeSource().Unix()) + for expiry, hashSet := range whisper.expirations { + if expiry < now { + // Dump all expired messages and remove timestamp + hashSet.Each(func(v interface{}) bool { + sz := whisper.envelopes[v.(common.Hash)].size() + delete(whisper.envelopes, v.(common.Hash)) + envelopeClearedCounter.Inc(1) + whisper.envelopeFeed.Send(EnvelopeEvent{ + Hash: v.(common.Hash), + Event: EventEnvelopeExpired, + }) + whisper.stats.messagesCleared++ + whisper.stats.memoryCleared += sz + whisper.stats.memoryUsed -= sz + return false + }) + whisper.expirations[expiry].Clear() + delete(whisper.expirations, expiry) + } + } +} + +// Stats returns the whisper node statistics. +func (whisper *Whisper) Stats() Statistics { + whisper.statsMu.Lock() + defer whisper.statsMu.Unlock() + + return whisper.stats +} + +// Envelopes retrieves all the messages currently pooled by the node. +func (whisper *Whisper) Envelopes() []*Envelope { + whisper.poolMu.RLock() + defer whisper.poolMu.RUnlock() + + all := make([]*Envelope, 0, len(whisper.envelopes)) + for _, envelope := range whisper.envelopes { + all = append(all, envelope) + } + return all +} + +// isEnvelopeCached checks if envelope with specific hash has already been received and cached. +func (whisper *Whisper) isEnvelopeCached(hash common.Hash) bool { + whisper.poolMu.Lock() + defer whisper.poolMu.Unlock() + + _, exist := whisper.envelopes[hash] + return exist +} + +// reset resets the node's statistics after each expiry cycle. +func (s *Statistics) reset() { + s.cycles++ + s.totalMessagesCleared += s.messagesCleared + + s.memoryCleared = 0 + s.messagesCleared = 0 +} + +// ValidatePublicKey checks the format of the given public key. +func ValidatePublicKey(k *ecdsa.PublicKey) bool { + return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 +} + +// validatePrivateKey checks the format of the given private key. +func validatePrivateKey(k *ecdsa.PrivateKey) bool { + if k == nil || k.D == nil || k.D.Sign() == 0 { + return false + } + return ValidatePublicKey(&k.PublicKey) +} + +// validateDataIntegrity returns false if the data have the wrong or contains all zeros, +// which is the simplest and the most common bug. +func validateDataIntegrity(k []byte, expectedSize int) bool { + if len(k) != expectedSize { + return false + } + if expectedSize > 3 && containsOnlyZeros(k) { + return false + } + return true +} + +// containsOnlyZeros checks if the data contain only zeros. +func containsOnlyZeros(data []byte) bool { + for _, b := range data { + if b != 0 { + return false + } + } + return true +} + +// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer. +func bytesToUintLittleEndian(b []byte) (res uint64) { + mul := uint64(1) + for i := 0; i < len(b); i++ { + res += uint64(b[i]) * mul + mul *= 256 + } + return res +} + +// BytesToUintBigEndian converts the slice to 64-bit unsigned integer. +func BytesToUintBigEndian(b []byte) (res uint64) { + for i := 0; i < len(b); i++ { + res *= 256 + res += uint64(b[i]) + } + return res +} + +// GenerateRandomID generates a random string, which is then returned to be used as a key id +func GenerateRandomID() (id string, err error) { + buf, err := generateSecureRandomData(keyIDSize) + if err != nil { + return "", err + } + if !validateDataIntegrity(buf, keyIDSize) { + return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") + } + id = common.Bytes2Hex(buf) + return id, err +} + +// makeDeterministicID generates a deterministic ID, based on a given input +func makeDeterministicID(input string, keyLen int) (id string, err error) { + buf := pbkdf2.Key([]byte(input), nil, 4096, keyLen, sha256.New) + if !validateDataIntegrity(buf, keyIDSize) { + return "", fmt.Errorf("error in GenerateDeterministicID: failed to generate key") + } + id = common.Bytes2Hex(buf) + return id, err +} + +// toDeterministicID reviews incoming id, and transforms it to format +// expected internally be private key store. Originally, public keys +// were used as keys, now random keys are being used. And in order to +// make it easier to consume, we now allow both random IDs and public +// keys to be passed. +func toDeterministicID(id string, expectedLen int) (string, error) { + if len(id) != (expectedLen * 2) { // we received hex key, so number of chars in id is doubled + var err error + id, err = makeDeterministicID(id, expectedLen) + if err != nil { + return "", err + } + } + + return id, nil +} + +func isFullNode(bloom []byte) bool { + if bloom == nil { + return true + } + for _, b := range bloom { + if b != 255 { + return false + } + } + return true +} + +func BloomFilterMatch(filter, sample []byte) bool { + if filter == nil { + return true + } + + for i := 0; i < BloomFilterSize; i++ { + f := filter[i] + s := sample[i] + if (f | s) != f { + return false + } + } + + return true +} + +func addBloom(a, b []byte) []byte { + c := make([]byte, BloomFilterSize) + for i := 0; i < BloomFilterSize; i++ { + c[i] = a[i] | b[i] + } + return c +} + +// SelectedKeyPairID returns the id of currently selected key pair. +// It helps distinguish between different users w/o exposing the user identity itself. +func (whisper *Whisper) SelectedKeyPairID() string { + whisper.keyMu.RLock() + defer whisper.keyMu.RUnlock() + + for id := range whisper.privateKeys { + return id + } + return "" +} diff --git a/whisper/whisper_test.go b/whisper/whisper_test.go new file mode 100644 index 000000000..08c7b73cf --- /dev/null +++ b/whisper/whisper_test.go @@ -0,0 +1,1646 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisper + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "math" + mrand "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/syndtr/goleveldb/leveldb/errors" + "golang.org/x/crypto/pbkdf2" +) + +func TestWhisperBasic(t *testing.T) { + w := New(&DefaultConfig) + p := w.Protocols() + shh := p[0] + if shh.Name != ProtocolName { + t.Fatalf("failed Protocol Name: %v.", shh.Name) + } + if uint64(shh.Version) != ProtocolVersion { + t.Fatalf("failed Protocol Version: %v.", shh.Version) + } + if shh.Length != NumberOfMessageCodes { + t.Fatalf("failed Protocol Length: %v.", shh.Length) + } + if shh.Run == nil { + t.Fatalf("failed shh.Run.") + } + if uint64(w.Version()) != ProtocolVersion { + t.Fatalf("failed whisper Version: %v.", shh.Version) + } + if w.GetFilter("non-existent") != nil { + t.Fatalf("failed GetFilter.") + } + + peerID := make([]byte, 64) + mrand.Read(peerID) + peer, _ := w.getPeer(peerID) + if peer != nil { + t.Fatal("found peer for random key.") + } + if err := w.AllowP2PMessagesFromPeer(peerID); err == nil { + t.Fatalf("failed MarkPeerTrusted.") + } + exist := w.HasSymKey("non-existing") + if exist { + t.Fatalf("failed HasSymKey.") + } + key, err := w.GetSymKey("non-existing") + if err == nil { + t.Fatalf("failed GetSymKey(non-existing): false positive.") + } + if key != nil { + t.Fatalf("failed GetSymKey: false positive.") + } + mail := w.Envelopes() + if len(mail) != 0 { + t.Fatalf("failed w.Envelopes().") + } + + derived := pbkdf2.Key(peerID, nil, 65356, aesKeyLength, sha256.New) + if !validateDataIntegrity(derived, aesKeyLength) { + t.Fatalf("failed validateSymmetricKey with param = %v.", derived) + } + if containsOnlyZeros(derived) { + t.Fatalf("failed containsOnlyZeros with param = %v.", derived) + } + + buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0} + le := bytesToUintLittleEndian(buf) + be := BytesToUintBigEndian(buf) + if le != uint64(0x280e5ff) { + t.Fatalf("failed bytesToIntLittleEndian: %d.", le) + } + if be != uint64(0xffe5800200) { + t.Fatalf("failed BytesToIntBigEndian: %d.", be) + } + + id, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + pk, err := w.GetPrivateKey(id) + if err != nil { + t.Fatalf("failed to retrieve new key pair: %s.", err) + } + if !validatePrivateKey(pk) { + t.Fatalf("failed validatePrivateKey: %v.", pk) + } + if !ValidatePublicKey(&pk.PublicKey) { + t.Fatalf("failed ValidatePublicKey: %v.", pk) + } +} + +func TestWhisperAsymmetricKeyImport(t *testing.T) { + var ( + w = New(&DefaultConfig) + privateKeys []*ecdsa.PrivateKey + ) + + for i := 0; i < 50; i++ { + id, err := w.NewKeyPair() + if err != nil { + t.Fatalf("could not generate key: %v", err) + } + + pk, err := w.GetPrivateKey(id) + if err != nil { + t.Fatalf("could not export private key: %v", err) + } + + privateKeys = append(privateKeys, pk) + + if !w.DeleteKeyPair(id) { + t.Fatalf("could not delete private key") + } + } + + for _, pk := range privateKeys { + if _, err := w.AddKeyPair(pk); err != nil { + t.Fatalf("could not import private key: %v", err) + } + } +} + +func TestWhisperIdentityManagement(t *testing.T) { + w := New(&DefaultConfig) + id1, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + id2, err := w.NewKeyPair() + if err != nil { + t.Fatalf("failed to generate new key pair: %s.", err) + } + pk1, err := w.GetPrivateKey(id1) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + pk2, err := w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + + if !w.HasKeyPair(id1) { + t.Fatalf("failed HasIdentity(pk1).") + } + if !w.HasKeyPair(id2) { + t.Fatalf("failed HasIdentity(pk2).") + } + if pk1 == nil { + t.Fatalf("failed GetIdentity(pk1).") + } + if pk2 == nil { + t.Fatalf("failed GetIdentity(pk2).") + } + + if !validatePrivateKey(pk1) { + t.Fatalf("pk1 is invalid.") + } + if !validatePrivateKey(pk2) { + t.Fatalf("pk2 is invalid.") + } + + // Delete one identity + done := w.DeleteKeyPair(id1) + if !done { + t.Fatalf("failed to delete id1.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasKeyPair(id1) { + t.Fatalf("failed DeleteIdentity(pub1): still exist.") + } + if !w.HasKeyPair(id2) { + t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.") + } + if pk1 != nil { + t.Fatalf("failed DeleteIdentity(pub1): first key still exist.") + } + if pk2 == nil { + t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.") + } + + // Delete again non-existing identity + done = w.DeleteKeyPair(id1) + if done { + t.Fatalf("delete id1: false positive.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err != nil { + t.Fatalf("failed to retrieve the key pair: %s.", err) + } + if w.HasKeyPair(id1) { + t.Fatalf("failed delete non-existing identity: exist.") + } + if !w.HasKeyPair(id2) { + t.Fatalf("failed delete non-existing identity: pub2 does not exist.") + } + if pk1 != nil { + t.Fatalf("failed delete non-existing identity: first key exist.") + } + if pk2 == nil { + t.Fatalf("failed delete non-existing identity: second key does not exist.") + } + + // Delete second identity + done = w.DeleteKeyPair(id2) + if !done { + t.Fatalf("failed to delete id2.") + } + pk1, err = w.GetPrivateKey(id1) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + pk2, err = w.GetPrivateKey(id2) + if err == nil { + t.Fatalf("retrieve the key pair: false positive.") + } + if w.HasKeyPair(id1) { + t.Fatalf("failed delete second identity: first identity exist.") + } + if w.HasKeyPair(id2) { + t.Fatalf("failed delete second identity: still exist.") + } + if pk1 != nil { + t.Fatalf("failed delete second identity: first key exist.") + } + if pk2 != nil { + t.Fatalf("failed delete second identity: second key exist.") + } +} + +func TestWhisperSymKeyManagement(t *testing.T) { + InitSingleTest() + + var err error + var k1, k2 []byte + w := New(&DefaultConfig) + id1 := string("arbitrary-string-1") + id2 := string("arbitrary-string-2") + + id1, err = w.GenerateSymKey() + if err != nil { + t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) + } + + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed GetSymKey(id2): false positive.") + } + if !w.HasSymKey(id1) { + t.Fatalf("failed HasSymKey(id1).") + } + if w.HasSymKey(id2) { + t.Fatalf("failed HasSymKey(id2): false positive.") + } + if k1 == nil { + t.Fatalf("first key does not exist.") + } + if k2 != nil { + t.Fatalf("second key still exist.") + } + + // add existing id, nothing should change + randomKey := make([]byte, aesKeyLength) + mrand.Read(randomKey) + id1, err = w.AddSymKeyDirect(randomKey) + if err != nil { + t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err) + } + + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed w.GetSymKey(id2): false positive.") + } + if !w.HasSymKey(id1) { + t.Fatalf("failed w.HasSymKey(id1).") + } + if w.HasSymKey(id2) { + t.Fatalf("failed w.HasSymKey(id2): false positive.") + } + if k1 == nil { + t.Fatalf("first key does not exist.") + } + if !bytes.Equal(k1, randomKey) { + t.Fatalf("k1 != randomKey.") + } + if k2 != nil { + t.Fatalf("second key already exist.") + } + + id2, err = w.AddSymKeyDirect(randomKey) + if err != nil { + t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err) + } + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } + if !w.HasSymKey(id1) { + t.Fatalf("HasSymKey(id1) failed.") + } + if !w.HasSymKey(id2) { + t.Fatalf("HasSymKey(id2) failed.") + } + if k1 == nil { + t.Fatalf("k1 does not exist.") + } + if k2 == nil { + t.Fatalf("k2 does not exist.") + } + if !bytes.Equal(k1, k2) { + t.Fatalf("k1 != k2.") + } + if !bytes.Equal(k1, randomKey) { + t.Fatalf("k1 != randomKey.") + } + if len(k1) != aesKeyLength { + t.Fatalf("wrong length of k1.") + } + if len(k2) != aesKeyLength { + t.Fatalf("wrong length of k2.") + } + + w.DeleteSymKey(id1) + k1, err = w.GetSymKey(id1) + if err == nil { + t.Fatalf("failed w.GetSymKey(id1): false positive.") + } + if k1 != nil { + t.Fatalf("failed GetSymKey(id1): false positive.") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } + if w.HasSymKey(id1) { + t.Fatalf("failed to delete first key: still exist.") + } + if !w.HasSymKey(id2) { + t.Fatalf("failed to delete first key: second key does not exist.") + } + if k1 != nil { + t.Fatalf("failed to delete first key.") + } + if k2 == nil { + t.Fatalf("failed to delete first key: second key is nil.") + } + + w.DeleteSymKey(id1) + w.DeleteSymKey(id2) + k1, err = w.GetSymKey(id1) + if err == nil { + t.Fatalf("failed w.GetSymKey(id1): false positive.") + } + k2, err = w.GetSymKey(id2) + if err == nil { + t.Fatalf("failed w.GetSymKey(id2): false positive.") + } + if k1 != nil || k2 != nil { + t.Fatalf("k1 or k2 is not nil") + } + if w.HasSymKey(id1) { + t.Fatalf("failed to delete second key: first key exist.") + } + if w.HasSymKey(id2) { + t.Fatalf("failed to delete second key: still exist.") + } + if k1 != nil { + t.Fatalf("failed to delete second key: first key is not nil.") + } + if k2 != nil { + t.Fatalf("failed to delete second key: second key is not nil.") + } + + randomKey = make([]byte, aesKeyLength+1) + mrand.Read(randomKey) + _, err = w.AddSymKeyDirect(randomKey) + if err == nil { + t.Fatalf("added the key with wrong size, seed %d.", seed) + } + + const password = "arbitrary data here" + id1, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err) + } + id2, err = w.AddSymKeyFromPassword(password) + if err != nil { + t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err) + } + k1, err = w.GetSymKey(id1) + if err != nil { + t.Fatalf("failed w.GetSymKey(id1).") + } + k2, err = w.GetSymKey(id2) + if err != nil { + t.Fatalf("failed w.GetSymKey(id2).") + } + if !w.HasSymKey(id1) { + t.Fatalf("HasSymKey(id1) failed.") + } + if !w.HasSymKey(id2) { + t.Fatalf("HasSymKey(id2) failed.") + } + if !validateDataIntegrity(k2, aesKeyLength) { + t.Fatalf("key validation failed.") + } + if !bytes.Equal(k1, k2) { + t.Fatalf("k1 != k2.") + } +} + +func TestExpiry(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + w.SetMinimumPowTest(0.0000001) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + w.Start(nil) + defer w.Stop() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params.TTL = 1 + + messagesCount := 5 + + // Send a few messages one after another. Due to low PoW and expiration buckets + // with one second resolution, it covers a case when there are multiple items + // in a single expiration bucket. + for i := 0; i < messagesCount; i++ { + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + } + + // wait till received or timeout + var received, expired bool + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) == messagesCount { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // wait till expired or timeout + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) == 0 { + expired = true + break + } + } + + if !expired { + t.Fatalf("expire failed, seed: %d.", seed) + } +} + +func TestCustomization(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + defer w.SetMaxMessageSize(DefaultMaxMessageSize) + w.Start(nil) + defer w.Stop() + + const smallPoW = 0.00001 + + f, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + params.KeySym = f.KeySym + params.Topic = BytesToTopic(f.Topics[2]) + params.PoW = smallPoW + params.TTL = 3600 * 24 // one day + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed) + } + + w.SetMinimumPowTest(smallPoW / 2) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + + params.TTL++ + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err = msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + w.SetMaxMessageSize(uint32(env.size() - 1)) + err = w.Send(env) + if err == nil { + t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed) + } + + w.SetMaxMessageSize(DefaultMaxMessageSize) + err = w.Send(env) + if err != nil { + t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 20; j++ { + time.Sleep(100 * time.Millisecond) + if len(w.Envelopes()) > 1 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + _, err = w.Subscribe(f) + if err != nil { + t.Fatalf("failed subscribe with seed %d: %s.", seed, err) + } + time.Sleep(5 * time.Millisecond) + mail := f.Retrieve() + if len(mail) > 0 { + t.Fatalf("received premature mail") + } +} + +func TestSymmetricSendCycle(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + defer w.SetMaxMessageSize(DefaultMaxMessageSize) + w.Start(nil) + defer w.Stop() + + filter1, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + filter1.PoW = DefaultMinimumPoW + + // Copy the first filter since some of its fields + // are randomly gnerated. + filter2 := &Filter{ + KeySym: filter1.KeySym, + Topics: filter1.Topics, + PoW: filter1.PoW, + AllowP2P: filter1.AllowP2P, + Messages: NewMemoryMessageStore(), + } + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + filter1.Src = ¶ms.Src.PublicKey + filter2.Src = ¶ms.Src.PublicKey + + params.KeySym = filter1.KeySym + params.Topic = BytesToTopic(filter1.Topics[2]) + params.PoW = filter1.PoW + params.WorkTime = 10 + params.TTL = 50 + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + _, err = w.Subscribe(filter1) + if err != nil { + t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) + } + + _, err = w.Subscribe(filter2) + if err != nil { + t.Fatalf("failed subscribe 2 with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err != nil { + t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 200; j++ { + time.Sleep(10 * time.Millisecond) + if len(w.Envelopes()) > 0 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + time.Sleep(5 * time.Millisecond) + mail1 := filter1.Retrieve() + mail2 := filter2.Retrieve() + if len(mail2) == 0 { + t.Fatalf("did not receive any email for filter 2") + } + if len(mail1) == 0 { + t.Fatalf("did not receive any email for filter 1") + } + +} + +func TestSymmetricSendWithoutAKey(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + defer w.SetMaxMessageSize(DefaultMaxMessageSize) + w.Start(nil) + defer w.Stop() + + filter, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + filter.PoW = DefaultMinimumPoW + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + filter.Src = nil + + params.KeySym = filter.KeySym + params.Topic = BytesToTopic(filter.Topics[2]) + params.PoW = filter.PoW + params.WorkTime = 10 + params.TTL = 50 + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + _, err = w.Subscribe(filter) + if err != nil { + t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err != nil { + t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 200; j++ { + time.Sleep(10 * time.Millisecond) + if len(w.Envelopes()) > 0 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + time.Sleep(5 * time.Millisecond) + mail := filter.Retrieve() + if len(mail) == 0 { + t.Fatalf("did not receive message in spite of not setting a public key") + } +} + +func TestSymmetricSendKeyMismatch(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + defer w.SetMaxMessageSize(DefaultMaxMessageSize) + w.Start(nil) + defer w.Stop() + + filter, err := generateFilter(t, true) + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + filter.PoW = DefaultMinimumPoW + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + + params.KeySym = filter.KeySym + params.Topic = BytesToTopic(filter.Topics[2]) + params.PoW = filter.PoW + params.WorkTime = 10 + params.TTL = 50 + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + _, err = w.Subscribe(filter) + if err != nil { + t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) + } + + err = w.Send(env) + if err != nil { + t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) + } + + // wait till received or timeout + var received bool + for j := 0; j < 200; j++ { + time.Sleep(10 * time.Millisecond) + if len(w.Envelopes()) > 0 { + received = true + break + } + } + + if !received { + t.Fatalf("did not receive the sent envelope, seed: %d.", seed) + } + + // check w.messages() + time.Sleep(5 * time.Millisecond) + mail := filter.Retrieve() + if len(mail) > 0 { + t.Fatalf("received a message when keys weren't matching") + } +} + +func TestBloom(t *testing.T) { + topic := TopicType{0, 0, 255, 6} + b := TopicToBloom(topic) + x := make([]byte, BloomFilterSize) + x[0] = byte(1) + x[32] = byte(1) + x[BloomFilterSize-1] = byte(128) + if !BloomFilterMatch(x, b) || !BloomFilterMatch(b, x) { + t.Fatalf("bloom filter does not match the mask") + } + + _, err := mrand.Read(b) + if err != nil { + t.Fatalf("math rand error") + } + _, err = mrand.Read(x) + if err != nil { + t.Fatalf("math rand error") + } + if !BloomFilterMatch(b, b) { + t.Fatalf("bloom filter does not match self") + } + x = addBloom(x, b) + if !BloomFilterMatch(x, b) { + t.Fatalf("bloom filter does not match combined bloom") + } + if !isFullNode(nil) { + t.Fatalf("isFullNode did not recognize nil as full node") + } + x[17] = 254 + if isFullNode(x) { + t.Fatalf("isFullNode false positive") + } + for i := 0; i < BloomFilterSize; i++ { + b[i] = byte(255) + } + if !isFullNode(b) { + t.Fatalf("isFullNode false negative") + } + if BloomFilterMatch(x, b) { + t.Fatalf("bloomFilterMatch false positive") + } + if !BloomFilterMatch(b, x) { + t.Fatalf("bloomFilterMatch false negative") + } + + w := New(&DefaultConfig) + f := w.BloomFilter() + if f != nil { + t.Fatalf("wrong bloom on creation") + } + err = w.SetBloomFilter(x) + if err != nil { + t.Fatalf("failed to set bloom filter: %s", err) + } + f = w.BloomFilter() + if !BloomFilterMatch(f, x) || !BloomFilterMatch(x, f) { + t.Fatalf("retireved wrong bloom filter") + } +} + +func TestSendP2PDirect(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + w.SetMinimumPowTest(0.0000001) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + w.Start(nil) + defer w.Stop() + + rwStub := &rwP2PMessagesStub{} + peerW := newPeer(w, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rwStub) + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params.TTL = 1 + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + // verify sending a single envelope + err = w.SendP2PDirect(peerW, env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + if len(rwStub.messages) != 1 { + t.Fatalf("invalid number of messages sent to peer: %d, expected 1", len(rwStub.messages)) + } + var envelope Envelope + if err := rwStub.messages[0].Decode(&envelope); err != nil { + t.Fatalf("failed to decode envelopes: %s", err) + } + if envelope.Hash() != env.Hash() { + t.Fatalf("invalid envelope %d, expected %d", envelope.Hash(), env.Hash()) + } + rwStub.messages = nil + + // send a batch of envelopes + err = w.SendP2PDirect(peerW, env, env, env) + if err != nil { + t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) + } + if len(rwStub.messages) != 1 { + t.Fatalf("invalid number of messages sent to peer: %d, expected 1", len(rwStub.messages)) + } + var envelopes []*Envelope + if err := rwStub.messages[0].Decode(&envelopes); err != nil { + t.Fatalf("failed to decode envelopes: %s", err) + } + if len(envelopes) != 3 { + t.Fatalf("invalid number of envelopes in a message: %d, expected 3", len(envelopes)) + } + rwStub.messages = nil + envelopes = nil +} + +func TestHandleP2PMessageCode(t *testing.T) { + InitSingleTest() + + w := New(&DefaultConfig) + w.SetMinimumPowTest(0.0000001) + defer w.SetMinimumPowTest(DefaultMinimumPoW) + w.Start(nil) + defer w.Stop() + + envelopeEvents := make(chan EnvelopeEvent, 10) + sub := w.SubscribeEnvelopeEvents(envelopeEvents) + defer sub.Unsubscribe() + + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + params.TTL = 1 + + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params, time.Now()) + if err != nil { + t.Fatalf("failed Wrap with seed %d: %s.", seed, err) + } + + // read a single envelope + rwStub := &rwP2PMessagesStub{} + rwStub.payload = []interface{}{env} + + peer := newPeer(nil, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), nil) + peer.trusted = true + + err = w.runMessageLoop(peer, rwStub) + if err != nil && err != errRWStub { + t.Fatalf("failed run message loop: %s", err) + } + if e := <-envelopeEvents; e.Hash != env.Hash() { + t.Fatalf("received envelope %s while expected %s", e.Hash, env.Hash()) + } + + // read a batch of envelopes + rwStub = &rwP2PMessagesStub{} + rwStub.payload = []interface{}{[]*Envelope{env, env, env}} + + err = w.runMessageLoop(peer, rwStub) + if err != nil && err != errRWStub { + t.Fatalf("failed run message loop: %s", err) + } + for i := 0; i < 3; i++ { + if e := <-envelopeEvents; e.Hash != env.Hash() { + t.Fatalf("received envelope %s while expected %s", e.Hash, env.Hash()) + } + } +} + +var errRWStub = errors.New("no more messages") + +type rwP2PMessagesStub struct { + // payload stores individual messages that will be sent returned + // on ReadMsg() class + payload []interface{} + messages []p2p.Msg +} + +func (stub *rwP2PMessagesStub) ReadMsg() (p2p.Msg, error) { + if len(stub.payload) == 0 { + return p2p.Msg{}, errRWStub + } + size, r, err := rlp.EncodeToReader(stub.payload[0]) + if err != nil { + return p2p.Msg{}, err + } + stub.payload = stub.payload[1:] + return p2p.Msg{Code: p2pMessageCode, Size: uint32(size), Payload: r}, nil +} + +func (stub *rwP2PMessagesStub) WriteMsg(m p2p.Msg) error { + stub.messages = append(stub.messages, m) + return nil +} + +func testConfirmationsHandshake(t *testing.T, expectConfirmations bool) { + conf := &Config{ + MinimumAcceptedPOW: 0, + DisableConfirmations: !expectConfirmations, + } + w := New(conf) + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + // so that actual read won't hang forever + time.AfterFunc(5*time.Second, func() { + rw1.Close() + }) + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, expectConfirmations})) +} + +func TestConfirmationHadnshakeExtension(t *testing.T) { + testConfirmationsHandshake(t, true) +} + +func TestHandshakeWithConfirmationsDisabled(t *testing.T) { + testConfirmationsHandshake(t, false) +} + +func TestConfirmationReceived(t *testing.T) { + conf := &Config{ + MinimumAcceptedPOW: 0, + MaxMessageSize: 10 << 20, + } + w := New(conf) + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + time.AfterFunc(5*time.Second, func() { + rw1.Close() + }) + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, true})) + require.NoError(t, p2p.SendItems(rw1, statusCode, ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), true, true)) + + e := Envelope{ + Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + data, err := rlp.EncodeToBytes([]*Envelope{&e}) + require.NoError(t, err) + hash := crypto.Keccak256Hash(data) + require.NoError(t, p2p.SendItems(rw1, messagesCode, &e)) + require.NoError(t, p2p.ExpectMsg(rw1, messageResponseCode, nil)) + require.NoError(t, p2p.ExpectMsg(rw1, batchAcknowledgedCode, hash)) +} + +func TestMessagesResponseWithError(t *testing.T) { + conf := &Config{ + MinimumAcceptedPOW: 0, + MaxMessageSize: 10 << 20, + } + w := New(conf) + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + defer func() { + rw1.Close() + rw2.Close() + }() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, true})) + require.NoError(t, p2p.SendItems(rw1, statusCode, ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), true, true)) + + failed := Envelope{ + Expiry: uint32(time.Now().Add(time.Hour).Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + normal := Envelope{ + Expiry: uint32(time.Now().Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + + data, err := rlp.EncodeToBytes([]*Envelope{&failed, &normal}) + require.NoError(t, err) + hash := crypto.Keccak256Hash(data) + require.NoError(t, p2p.SendItems(rw1, messagesCode, &failed, &normal)) + require.NoError(t, p2p.ExpectMsg(rw1, messageResponseCode, NewMessagesResponse(hash, []EnvelopeError{ + {Hash: failed.Hash(), Code: EnvelopeTimeNotSynced, Description: "envelope from future"}, + }))) + require.NoError(t, p2p.ExpectMsg(rw1, batchAcknowledgedCode, hash)) +} + +func testConfirmationEvents(t *testing.T, envelope Envelope, envelopeErrors []EnvelopeError) { + conf := &Config{ + MinimumAcceptedPOW: 0, + MaxMessageSize: 10 << 20, + } + w := New(conf) + events := make(chan EnvelopeEvent, 2) + sub := w.SubscribeEnvelopeEvents(events) + defer sub.Unsubscribe() + + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + time.AfterFunc(5*time.Second, func() { + rw1.Close() + }) + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, true})) + require.NoError(t, p2p.SendItems(rw1, statusCode, ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), true, true)) + + require.NoError(t, w.Send(&envelope)) + require.NoError(t, p2p.ExpectMsg(rw1, messagesCode, []*Envelope{&envelope})) + + var hash common.Hash + select { + case ev := <-events: + require.Equal(t, EventEnvelopeSent, ev.Event) + require.Equal(t, p.ID(), ev.Peer) + require.NotEqual(t, common.Hash{}, ev.Batch) + hash = ev.Batch + case <-time.After(5 * time.Second): + require.FailNow(t, "timed out waiting for an envelope.sent event") + } + require.NoError(t, p2p.Send(rw1, messageResponseCode, NewMessagesResponse(hash, envelopeErrors))) + require.NoError(t, p2p.Send(rw1, batchAcknowledgedCode, hash)) + select { + case ev := <-events: + require.Equal(t, EventBatchAcknowledged, ev.Event) + require.Equal(t, p.ID(), ev.Peer) + require.Equal(t, hash, ev.Batch) + require.Equal(t, envelopeErrors, ev.Data) + case <-time.After(5 * time.Second): + require.FailNow(t, "timed out waiting for an batch.acknowledged event") + } +} + +func TestConfirmationEventsReceived(t *testing.T) { + e := Envelope{ + Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + testConfirmationEvents(t, e, []EnvelopeError{}) +} + +func TestConfirmationEventsExtendedWithErrors(t *testing.T) { + e := Envelope{ + Expiry: uint32(time.Now().Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + testConfirmationEvents(t, e, []EnvelopeError{ + { + Hash: e.Hash(), + Code: EnvelopeTimeNotSynced, + Description: "test error", + }}) +} + +func TestEventsWithoutConfirmation(t *testing.T) { + conf := &Config{ + MinimumAcceptedPOW: 0, + MaxMessageSize: 10 << 20, + } + w := New(conf) + events := make(chan EnvelopeEvent, 2) + sub := w.SubscribeEnvelopeEvents(events) + defer sub.Unsubscribe() + + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + time.AfterFunc(5*time.Second, func() { + rw1.Close() + }) + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, true})) + require.NoError(t, p2p.SendItems(rw1, statusCode, ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), true, false)) + + e := Envelope{ + Expiry: uint32(time.Now().Add(10 * time.Second).Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + require.NoError(t, w.Send(&e)) + require.NoError(t, p2p.ExpectMsg(rw1, messagesCode, []*Envelope{&e})) + + select { + case ev := <-events: + require.Equal(t, EventEnvelopeSent, ev.Event) + require.Equal(t, p.ID(), ev.Peer) + require.Equal(t, common.Hash{}, ev.Batch) + case <-time.After(5 * time.Second): + require.FailNow(t, "timed out waiting for an envelope.sent event") + } +} + +func discardPipe() *p2p.MsgPipeRW { + rw1, rw2 := p2p.MsgPipe() + go func() { + for { + msg, err := rw1.ReadMsg() + if err != nil { + return + } + msg.Discard() + } + }() + return rw2 +} + +func TestWhisperTimeDesyncEnvelopeIgnored(t *testing.T) { + c := &Config{ + MaxMessageSize: DefaultMaxMessageSize, + MinimumAcceptedPOW: 0, + } + rw1, rw2 := p2p.MsgPipe() + defer func() { + rw1.Close() + rw2.Close() + }() + p1 := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + p2 := p2p.NewPeer(enode.ID{2}, "2", []p2p.Cap{{"shh", 6}}) + w1, w2 := New(c), New(c) + errc := make(chan error) + go func() { + w1.HandlePeer(p2, rw2) + }() + go func() { + errc <- w2.HandlePeer(p1, rw1) + }() + w1.SetTimeSource(func() time.Time { + return time.Now().Add(time.Hour) + }) + env := &Envelope{ + Expiry: uint32(time.Now().Add(time.Hour).Unix()), + TTL: 30, + Topic: TopicType{1}, + Data: []byte{1, 1, 1}, + } + require.NoError(t, w1.Send(env)) + select { + case err := <-errc: + require.NoError(t, err) + case <-time.After(time.Second): + } + rw2.Close() + select { + case err := <-errc: + require.Error(t, err, "p2p: read or write on closed message pipe") + case <-time.After(time.Second): + require.FailNow(t, "connection wasn't closed in expected time") + } +} + +func TestRequestSentEventWithExpiry(t *testing.T) { + w := New(nil) + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw := discardPipe() + defer rw.Close() + w.peers[newPeer(w, p, rw)] = struct{}{} + events := make(chan EnvelopeEvent, 1) + sub := w.SubscribeEnvelopeEvents(events) + defer sub.Unsubscribe() + e := &Envelope{Nonce: 1} + require.NoError(t, w.RequestHistoricMessagesWithTimeout(p.ID().Bytes(), e, time.Millisecond)) + verifyEvent := func(etype EventType) { + select { + case <-time.After(time.Second): + require.FailNow(t, "error waiting for a event type %s", etype) + case ev := <-events: + require.Equal(t, etype, ev.Event) + require.Equal(t, p.ID(), ev.Peer) + require.Equal(t, e.Hash(), ev.Hash) + } + } + verifyEvent(EventMailServerRequestSent) + verifyEvent(EventMailServerRequestExpired) +} + +func TestSendMessagesRequest(t *testing.T) { + validMessagesRequest := MessagesRequest{ + ID: make([]byte, 32), + From: 0, + To: 10, + Bloom: []byte{0x01}, + } + + t.Run("InvalidID", func(t *testing.T) { + w := New(nil) + err := w.SendMessagesRequest([]byte{0x01, 0x02}, MessagesRequest{}) + require.EqualError(t, err, "invalid 'ID', expected a 32-byte slice") + }) + + t.Run("WithoutPeer", func(t *testing.T) { + w := New(nil) + err := w.SendMessagesRequest([]byte{0x01, 0x02}, validMessagesRequest) + require.EqualError(t, err, "Could not find peer with ID: 0102") + }) + + t.Run("AllGood", func(t *testing.T) { + p := p2p.NewPeer(enode.ID{0x01}, "peer01", nil) + rw1, rw2 := p2p.MsgPipe() + w := New(nil) + w.peers[newPeer(w, p, rw1)] = struct{}{} + + go func() { + err := w.SendMessagesRequest(p.ID().Bytes(), validMessagesRequest) + require.NoError(t, err) + }() + + require.NoError(t, p2p.ExpectMsg(rw2, p2pRequestCode, nil)) + }) +} + +func TestSyncMessages(t *testing.T) { + t.Run("WithoutMailServer", func(t *testing.T) { + w := New(nil) + err := w.SyncMessages(nil, SyncMailRequest{}) + require.EqualError(t, err, "can not sync messages if Mail Server is not configured") + }) + + t.Run("WithoutPeer", func(t *testing.T) { + w := New(nil) + w.RegisterServer(&stubMailServer{}) + err := w.SyncMessages([]byte{0x01, 0x02}, SyncMailRequest{}) + require.EqualError(t, err, "Could not find peer with ID: 0102") + }) + + t.Run("WithInvalidRequest", func(t *testing.T) { + w := New(nil) + w.RegisterServer(&stubMailServer{}) + + p := p2p.NewPeer(enode.ID{0x01}, "peer01", nil) + rw1, _ := p2p.MsgPipe() + + w.peers[newPeer(w, p, rw1)] = struct{}{} + + err := w.SyncMessages(p.ID().Bytes(), SyncMailRequest{Limit: 10, Lower: 10, Upper: 5}) + require.EqualError(t, err, "invalid 'Lower' value, can't be greater than 'Upper'") + }) + + t.Run("AllGood", func(t *testing.T) { + w := New(nil) + w.RegisterServer(&stubMailServer{}) + + p := p2p.NewPeer(enode.ID{0x01}, "peer01", nil) + rw1, rw2 := p2p.MsgPipe() + + w.peers[newPeer(w, p, rw1)] = struct{}{} + + go func() { + err := w.SyncMessages(p.ID().Bytes(), SyncMailRequest{Limit: 10}) + require.NoError(t, err) + }() + + require.NoError(t, p2p.ExpectMsg(rw2, p2pSyncRequestCode, nil)) + }) +} + +func TestSendSyncResponse(t *testing.T) { + w := New(nil) + p := p2p.NewPeer(enode.ID{0x01}, "peer01", nil) + rw1, rw2 := p2p.MsgPipe() + whisperPeer := newPeer(w, p, rw1) + + go func() { + err := w.SendSyncResponse(whisperPeer, SyncResponse{}) + require.NoError(t, err) + }() + + require.NoError(t, p2p.ExpectMsg(rw2, p2pSyncResponseCode, nil)) +} + +func TestHandleP2PSyncRequestCode(t *testing.T) { + rw1, rw2 := p2p.MsgPipe() + peer := newPeer(nil, p2p.NewPeer(enode.ID{}, "test", nil), nil) + + mailMock := &mockMailServer{} + mailMock.On("SyncMail", peer, mock.Anything).Return(nil) + + w := New(nil) + w.RegisterServer(mailMock) + + go func() { + err := p2p.Send(rw1, p2pSyncRequestCode, SyncMailRequest{Limit: 10}) + require.NoError(t, err) + require.NoError(t, rw1.Close()) + }() + + err := w.runMessageLoop(peer, rw2) + if err != nil && err != p2p.ErrPipeClosed { + require.FailNow(t, "invalid err", err.Error()) + } + + mailMock.AssertNumberOfCalls(t, "SyncMail", 1) +} + +func TestHandleP2PSyncRequestCodeWithInvalidRequest(t *testing.T) { + rw1, rw2 := p2p.MsgPipe() + peer := newPeer(nil, p2p.NewPeer(enode.ID{}, "test", nil), nil) + + mailMock := &mockMailServer{} + mailMock.On("SyncMail", peer, mock.Anything).Return(nil) + + w := New(nil) + w.RegisterServer(mailMock) + + // create an invalid request + req := SyncMailRequest{Limit: 10, Lower: 10, Upper: 5} + require.Error(t, req.Validate()) + + go func() { + err := p2p.Send(rw1, p2pSyncRequestCode, req) + require.NoError(t, err) + require.NoError(t, rw1.Close()) + }() + + err := w.runMessageLoop(peer, rw2) + require.EqualError(t, err, "sync mail request was invalid: invalid 'Lower' value, can't be greater than 'Upper'") + + mailMock.AssertNumberOfCalls(t, "SyncMail", 0) +} + +func TestHandleP2PSyncResponseCode(t *testing.T) { + rw1, rw2 := p2p.MsgPipe() + peer := newPeer(nil, p2p.NewPeer(enode.ID{}, "test", nil), nil) + peer.trusted = true + + mailMock := &mockMailServer{} + mailMock.On("Archive", mock.Anything) + + w := New(nil) + w.RegisterServer(mailMock) + + envelopesCount := 3 + + go func() { + params, err := generateMessageParams() + require.NoError(t, err) + msg, err := NewSentMessage(params) + require.NoError(t, err) + + var envelopes []*Envelope + for i := 0; i < envelopesCount; i++ { + env, err := msg.Wrap(params, time.Now()) + require.NoError(t, err) + envelopes = append(envelopes, env) + } + + err = p2p.Send(rw1, p2pSyncResponseCode, SyncResponse{ + Envelopes: envelopes, + }) + require.NoError(t, err) + require.NoError(t, rw1.Close()) + }() + + err := w.runMessageLoop(peer, rw2) + if err != nil && err != p2p.ErrPipeClosed { + require.FailNow(t, "invalid err", err) + } + + mailMock.AssertNumberOfCalls(t, "Archive", envelopesCount) +} + +func TestRateLimiterIntegration(t *testing.T) { + conf := &Config{ + MinimumAcceptedPOW: 0, + MaxMessageSize: 10 << 20, + } + w := New(conf) + w.SetRateLimiter(NewPeerRateLimiter(&MetricsRateLimiterHandler{}, nil)) + p := p2p.NewPeer(enode.ID{1}, "1", []p2p.Cap{{"shh", 6}}) + rw1, rw2 := p2p.MsgPipe() + defer func() { + rw1.Close() + rw2.Close() + }() + errorc := make(chan error, 1) + go func() { + err := w.HandlePeer(p, rw2) + errorc <- err + }() + require.NoError(t, p2p.ExpectMsg(rw1, statusCode, []interface{}{ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), false, true})) + require.NoError(t, p2p.SendItems(rw1, statusCode, ProtocolVersion, math.Float64bits(w.MinPow()), w.BloomFilter(), true, true)) + + envelope := Envelope{ + Expiry: uint32(time.Now().Unix()), + TTL: 10, + Topic: TopicType{1}, + Data: make([]byte, 1<<10), + Nonce: 1, + } + + data, err := rlp.EncodeToBytes([]*Envelope{&envelope}) + require.NoError(t, err) + hash := crypto.Keccak256Hash(data) + require.NoError(t, p2p.SendItems(rw1, messagesCode, &envelope)) + require.NoError(t, p2p.ExpectMsg(rw1, messageResponseCode, NewMessagesResponse(hash, nil))) +} + +type stubMailServer struct{} + +func (stubMailServer) Archive(*Envelope) {} +func (stubMailServer) DeliverMail(*Peer, *Envelope) {} +func (stubMailServer) Deliver(*Peer, MessagesRequest) {} +func (stubMailServer) SyncMail(*Peer, SyncMailRequest) error { return nil } + +type mockMailServer struct { + mock.Mock +} + +func (m *mockMailServer) Archive(env *Envelope) { + m.Called(env) +} + +func (m *mockMailServer) DeliverMail(p *Peer, env *Envelope) { + m.Called(p, env) +} + +func (m *mockMailServer) Deliver(p *Peer, r MessagesRequest) { + m.Called(p, r) +} + +func (m *mockMailServer) SyncMail(p *Peer, r SyncMailRequest) error { + args := m.Called(p, r) + return args.Error(0) +} + +func TestMailserverCompletionEvent(t *testing.T) { + w := New(nil) + require.NoError(t, w.Start(nil)) + defer w.Stop() + + rw1, rw2 := p2p.MsgPipe() + peer := newPeer(w, p2p.NewPeer(enode.ID{1}, "1", nil), rw1) + peer.trusted = true + w.peers[peer] = struct{}{} + + events := make(chan EnvelopeEvent) + sub := w.SubscribeEnvelopeEvents(events) + defer sub.Unsubscribe() + + envelopes := []*Envelope{{Data: []byte{1}}, {Data: []byte{2}}} + go func() { + require.NoError(t, p2p.Send(rw2, p2pMessageCode, envelopes)) + require.NoError(t, p2p.Send(rw2, p2pRequestCompleteCode, [100]byte{})) // 2 hashes + cursor size + rw2.Close() + }() + require.EqualError(t, w.runMessageLoop(peer, rw1), "p2p: read or write on closed message pipe") + + after := time.After(2 * time.Second) + count := 0 + for { + select { + case <-after: + require.FailNow(t, "timed out waiting for all events") + case ev := <-events: + switch ev.Event { + case EventEnvelopeAvailable: + count++ + case EventMailServerRequestCompleted: + require.Equal(t, count, len(envelopes), + "all envelope.avaiable events mut be recevied before request is compelted") + return + } + } + } +}