From 6181b6d2c7a2d9a43a8fba35c8d24f54952f20bb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 24 Oct 2014 21:28:55 +0200 Subject: [PATCH] Whisper is functional from the JS API. --- alethzero/MainWin.cpp | 3 + libqethereum/QEthereum.cpp | 139 +++++++++++++++++++++++++++++++++---- libqethereum/QEthereum.h | 22 +++--- libwhisper/Common.cpp | 16 +++-- libwhisper/Common.h | 6 +- libwhisper/Interface.h | 4 +- libwhisper/Message.cpp | 7 +- libwhisper/Message.h | 14 ++-- libwhisper/WhisperHost.cpp | 2 +- third/MainWin.cpp | 3 + 10 files changed, 172 insertions(+), 44 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 68fe0a0f1..9a98f54ee 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -151,6 +151,7 @@ Main::Main(QWidget *parent) : connect(ui->webView, &QWebView::loadFinished, [=]() { m_ethereum->poll(); + m_whisper->poll(); }); connect(ui->webView, &QWebView::titleChanged, [=]() @@ -1001,6 +1002,8 @@ void Main::timerEvent(QTimerEvent*) if (m_ethereum) m_ethereum->poll(); + if (m_whisper) + m_whisper->poll(); for (auto const& i: m_handlers) if (ethereum()->checkWatch(i.first)) diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 267fed35e..d87f4ecb3 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "QEthereum.h" using namespace std; using namespace dev; @@ -21,7 +23,7 @@ dev::bytes toBytes(QString const& _s) else { // Binary - cwarn << "THIS FUNCTIONALITY IS DEPRECATED. DO NOT ASSUME ASCII/BINARY-STRINGS WILL BE ACCEPTED. USE eth.fromAscii()."; + cwarn << "'" << _s.toStdString() << "': Unrecognised format for number/hash. USE eth.fromAscii() if you mean to convert from ASCII."; return asBytes(_s); } } @@ -558,37 +560,148 @@ void QWhisper::faceDieing() } -void QWhisper::send(QString /*dev::Address*/ _dest, QString /*ev::KeyPair*/ _from, QString /*dev::h256 const&*/ _topic, QString /*dev::bytes const&*/ _payload) +static shh::Message toMessage(QString _json) { - (void)_dest; - (void)_from; - (void)_topic; - (void)_payload; + shh::Message ret; + + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); + if (f.contains("from")) + ret.setFrom(toPublic(f["from"].toString())); + if (f.contains("to")) + ret.setTo(toPublic(f["to"].toString())); + if (f.contains("payload")) + ret.setPayload(toBytes(f["payload"].toString())); + + return ret; } -unsigned QWhisper::newWatch(QString _json) +static shh::Envelope toSealed(QString _json, shh::Message const& _m, Secret _from) { - (void)_json; - return 0; + unsigned ttl = 50; + unsigned workToProve = 50; + + shh::BuildTopic bt; + + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); + if (f.contains("ttl")) + ttl = f["ttl"].toInt(); + if (f.contains("workToProve")) + workToProve = f["workToProve"].toInt(); + if (f.contains("topic")) + { + if (f["topic"].isString()) + bt.shift(asBytes(padded(f["topic"].toString(), 32))); + else if (f["topic"].isArray()) + for (auto i: f["topic"].toArray()) + bt.shift(asBytes(padded(i.toString(), 32))); + } + return _m.seal(_from, bt, workToProve, ttl); } -QString QWhisper::watchMessages(unsigned _w) +void QWhisper::doPost(QString _json) { - (void)_w; - return ""; + shh::Message m = toMessage(_json); + Secret from; + + if (m.from() && m_ids.count(m.from())) + { + cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; + // TODO: insert validification hook here. + from = m_ids[m.from()]; + } + + face()->inject(toSealed(_json, m, from)); +} + +static pair toWatch(QString _json) +{ + shh::BuildTopicMask bt(shh::BuildTopicMask::Empty); + Public to; + + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); + if (f.contains("to")) + to = toPublic(f["to"].toString()); + + if (f.contains("topic")) + { + if (f["topic"].isString()) + bt.shift(asBytes(padded(f["topic"].toString(), 32))); + else if (f["topic"].isArray()) + for (auto i: f["topic"].toArray()) + if (i.isString()) + bt.shift(asBytes(padded(i.toString(), 32))); + else + bt.shift(); + } + return make_pair(bt.toTopicMask(), to); +} + +// _json contains +// topic: the topic as an array of components, some may be null. +// to: specifies the id to which the message is encrypted. null if broadcast. +unsigned QWhisper::newWatch(QString _json) +{ + auto w = toWatch(_json); + auto ret = face()->installWatch(w.first); + m_watches.insert(make_pair(ret, w.second)); + return ret; } void QWhisper::killWatch(unsigned _w) { - (void)_w; + face()->uninstallWatch(_w); + m_watches.erase(_w); } void QWhisper::clearWatches() { + for (auto i: m_watches) + face()->uninstallWatch(i.first); + m_watches.clear(); +} + +QString QWhisper::newIdentity() +{ + KeyPair kp = KeyPair::create(); + m_ids[kp.pub()] = kp.sec(); + return toQJS(kp.pub()); +} + +static QString toJson(h256 const& _h, shh::Envelope const& _e, shh::Message const& _m) +{ + QJsonObject v; + v["hash"] = toQJS(_h); + + v["expiry"] = (int)_e.expiry(); + v["sent"] = (int)_e.sent(); + v["ttl"] = (int)_e.ttl(); + v["workProved"] = (int)_e.workProved(); + v["topic"] = toQJS(_e.topic()); + + v["payload"] = toQJS(_m.payload()); + v["from"] = toQJS(_m.from()); + v["to"] = toQJS(_m.to()); + + return QString::fromUtf8(QJsonDocument(v).toJson()); } void QWhisper::poll() { + for (auto const& w: m_watches) + if (!w.second || m_ids.count(w.second)) + for (h256 const& h: face()->checkWatch(w.first)) + { + auto e = face()->envelope(h); + shh::Message m; + if (w.second) + { + cwarn << "Silently decrypting message from identity" << w.second.abridged() << ": User validation hook goes here."; + m = e.open(m_ids[w.second]); + } + else + m = e.open(); + emit watchChanged(w.first, toJson(h, e, m)); + } } // extra bits needed to link on VS diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 2d6ea8b4c..42696d40c 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -59,8 +59,9 @@ template dev::FixedHash toFixed(QString const& _s) template inline boost::multiprecision::number> toInt(QString const& _s); -inline dev::Address toAddress(QString const& _s) { return toFixed<20>(_s); } -inline dev::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } +inline dev::Address toAddress(QString const& _s) { return toFixed(_s); } +inline dev::Public toPublic(QString const& _s) { return toFixed(_s); } +inline dev::Secret toSecret(QString const& _s) { return toFixed(_s); } inline dev::u256 toU256(QString const& _s) { return toInt<32>(_s); } template QString toQJS(dev::FixedHash const& _h) { return QString::fromStdString("0x" + toHex(_h.ref())); } @@ -225,12 +226,12 @@ public: Q_INVOKABLE QWhisper* self() { return this; } /// Basic message send. - Q_INVOKABLE void send(QString /*dev::Address*/ _dest, QString /*ev::KeyPair*/ _from, QString /*dev::h256 const&*/ _topic, QString /*dev::bytes const&*/ _payload); + Q_INVOKABLE void doPost(QString _json); - // Watches interface + Q_INVOKABLE QString newIdentity(); + // Watches interface Q_INVOKABLE unsigned newWatch(QString _json); - Q_INVOKABLE QString watchMessages(unsigned _w); Q_INVOKABLE void killWatch(unsigned _w); void clearWatches(); @@ -240,11 +241,13 @@ public slots: void poll(); signals: - void watchChanged(unsigned _w); + void watchChanged(unsigned _w, QString _envelopeJson); private: std::weak_ptr m_face; - std::vector m_watches; + std::map m_watches; + + std::map m_ids; }; // TODO: add p2p object @@ -269,8 +272,9 @@ private: if (_shh) \ { \ _frame->addToJavaScriptWindowObject("_web3_dot_shh", _shh, QWebFrame::ScriptOwnership); \ - _frame->evaluateJavaScript("_web3_dot_shh.makeWatch = function(a) { var ww = _web3_dot_shh.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { _web3_dot_shh.killWatch(w); }; ret.changed = function(f) { _web3_dot_shh.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.messages = function() { return JSON.parse(_web3_dot_shh.watchMessages(this.w)) }; return ret; }"); \ - _frame->evaluateJavaScript("_web3_dot_shh.watch = function(a) { return _web3_dot_shh.makeWatch(JSON.stringify(a)) }"); \ + _frame->evaluateJavaScript("_web3_dot_shh.makeWatch = function(json) { var ww = _web3_dot_shh.newWatch(json); var ret = { w: ww }; ret.uninstall = function() { _web3_dot_shh.killWatch(w); }; ret.arrived = function(f) { _web3_dot_shh.watchChanged.connect(function(nw, envelope) { if (nw == ww) f(JSON.parse(envelope)) }); }; return ret; }"); \ + _frame->evaluateJavaScript("_web3_dot_shh.watch = function(filter) { return _web3_dot_shh.makeWatch(JSON.stringify(filter)) }"); \ + _frame->evaluateJavaScript("_web3_dot_shh.post = function(message) { return _web3_dot_shh.doPost(JSON.stringify(message)) }"); \ _frame->evaluateJavaScript("web3.shh = _web3_dot_shh"); \ } \ } diff --git a/libwhisper/Common.cpp b/libwhisper/Common.cpp index e4903821c..c4c103de3 100644 --- a/libwhisper/Common.cpp +++ b/libwhisper/Common.cpp @@ -43,10 +43,16 @@ h256 TopicFilter::sha3() const TopicMask BuildTopicMask::toTopicMask() const { TopicMask ret; - for (auto i = 0; i < 32; ++i) - { - ret.first[i] = m_parts[i * m_parts.size() / 32][i]; - ret.second[i] = m_parts[i * m_parts.size() / 32] ? 255 : 0; - } + if (m_parts.size()) + for (auto i = 0; i < 32; ++i) + { + ret.first[i] = m_parts[i * m_parts.size() / 32][i]; + ret.second[i] = m_parts[i * m_parts.size() / 32] ? 255 : 0; + } return ret; } +/* +web3.shh.watch({}, function(m) { env.note("New message:\n"+JSON.stringify(m)); }) +k = web3.shh.newIdentity() +web3.shh.post({from: k, topic: web3.fromAscii("test"), payload: web3.fromAscii("Hello world!")}) +*/ diff --git a/libwhisper/Common.h b/libwhisper/Common.h index 1f4c4a17f..a85caafe1 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -64,6 +64,7 @@ using Topic = h256; class BuildTopic { public: + BuildTopic() {} template BuildTopic(T const& _t) { shift(_t); } template BuildTopic& shift(T const& _r) { return shiftBytes(RLPStream().append(_r).out()); } @@ -75,8 +76,6 @@ public: Topic toTopic() const { Topic ret; for (auto i = 0; i < 32; ++i) ret[i] = m_parts[i * m_parts.size() / 32][i]; return ret; } protected: - BuildTopic() {} - BuildTopic& shiftBytes(bytes const& _b); h256s m_parts; @@ -105,7 +104,10 @@ private: class BuildTopicMask: BuildTopic { public: + enum EmptyType { Empty }; + BuildTopicMask() { shift(); } + BuildTopicMask(EmptyType) {} template BuildTopicMask(T const& _t) { shift(_t); } template BuildTopicMask& shift(T const& _r) { BuildTopic::shift(_r); return *this; } diff --git a/libwhisper/Interface.h b/libwhisper/Interface.h index 63cc44a9b..8bc37ff5e 100644 --- a/libwhisper/Interface.h +++ b/libwhisper/Interface.h @@ -79,9 +79,9 @@ public: virtual Envelope envelope(h256 _m) const = 0; void post(bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_topic, _ttl, _workToProve)); } - void post(Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_to, _topic, _ttl, _workToProve)); } + void post(Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_to, _topic, _ttl, _workToProve)); } void post(Secret _from, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _topic, _ttl, _workToProve)); } - void post(Secret _from, Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _to, _topic, _ttl, _workToProve)); } + void post(Secret _from, Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_from, _to, _topic, _ttl, _workToProve)); } }; struct WatshhChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; diff --git a/libwhisper/Message.cpp b/libwhisper/Message.cpp index 80b493076..52caf2f90 100644 --- a/libwhisper/Message.cpp +++ b/libwhisper/Message.cpp @@ -60,7 +60,7 @@ void Message::populate(bytes const& _data) m_payload = bytesConstRef(&_data).cropped(1).toBytes(); } -Envelope Message::seal(Secret _from, Topic const& _topic, unsigned _ttl, unsigned _workToProve) +Envelope Message::seal(Secret _from, Topic const& _topic, unsigned _ttl, unsigned _workToProve) const { Envelope ret(time(0) + _ttl, _ttl, _topic); @@ -91,11 +91,6 @@ Message Envelope::open(Secret const& _s) const return Message(*this, _s); } -Message Envelope::open() const -{ - return Message(*this); -} - unsigned Envelope::workProved() const { h256 d[2]; diff --git a/libwhisper/Message.h b/libwhisper/Message.h index fa5cc3662..67b34e321 100644 --- a/libwhisper/Message.h +++ b/libwhisper/Message.h @@ -65,8 +65,7 @@ public: Topic const& topic() const { return m_topic; } bytes const& data() const { return m_data; } - Message open(Secret const& _s) const; - Message open() const; + Message open(Secret const& _s = Secret()) const; unsigned workProved() const; void proveWork(unsigned _ms); @@ -102,16 +101,19 @@ public: Public to() const { return m_to; } bytes const& payload() const { return m_payload; } + void setFrom(Public _from) { m_from = _from; } void setTo(Public _to) { m_to = _to; } + void setPayload(bytes const& _payload) { m_payload = _payload; } + void setPayload(bytes&& _payload) { swap(m_payload, _payload); } operator bool() const { return !!m_payload.size() || m_from || m_to; } /// Turn this message into a ditributable Envelope. - Envelope seal(Secret _from, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50); + Envelope seal(Secret _from, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) const; // Overloads for skipping _from or specifying _to. - Envelope seal(Topic const& _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { return seal(Secret(), _topic, _workToProve, _ttl); } - Envelope seal(Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(Secret(), _topic, _workToProve, _ttl); } - Envelope seal(Secret _from, Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(_from, _topic, _workToProve, _ttl); } + Envelope seal(Topic const& _topic, unsigned _ttl = 50, unsigned _workToProve = 50) const { return seal(Secret(), _topic, _workToProve, _ttl); } + Envelope sealTo(Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(Secret(), _topic, _workToProve, _ttl); } + Envelope sealTo(Secret _from, Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(_from, _topic, _workToProve, _ttl); } private: void populate(bytes const& _data); diff --git a/libwhisper/WhisperHost.cpp b/libwhisper/WhisperHost.cpp index 2d9a5e6b6..b11b4af3a 100644 --- a/libwhisper/WhisperHost.cpp +++ b/libwhisper/WhisperHost.cpp @@ -62,7 +62,7 @@ void WhisperHost::inject(Envelope const& _m, WhisperPeer* _p) m_messages[h] = _m; } - if (_p) +// if (_p) { Guard l(m_filterLock); for (auto const& f: m_filters) diff --git a/third/MainWin.cpp b/third/MainWin.cpp index d1f5938aa..dd5b01733 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -120,6 +120,7 @@ Main::Main(QWidget *parent) : connect(ui->webView, &QWebView::loadFinished, [=]() { m_ethereum->poll(); + m_whisper->poll(); }); connect(ui->webView, &QWebView::titleChanged, [=]() @@ -522,6 +523,8 @@ void Main::timerEvent(QTimerEvent*) if (m_ethereum) m_ethereum->poll(); + if (m_whisper) + m_whisper->poll(); for (auto const& i: m_handlers) if (ethereum()->checkWatch(i.first))