diff --git a/CMakeLists.txt b/CMakeLists.txt index b197d4033..2081f5f7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,6 +242,7 @@ set(LIB_SRC llarp/service/address.cpp llarp/service/context.cpp llarp/service/endpoint.cpp + llarp/service/frame.cpp llarp/service/lookup.cpp llarp/service/protocol.cpp llarp/service/tag.cpp diff --git a/doc/proto_v0.txt b/doc/proto_v0.txt index bc786fcd4..acaeaa9ad 100644 --- a/doc/proto_v0.txt +++ b/doc/proto_v0.txt @@ -525,6 +525,8 @@ TODO: document this better intro message (variant 1) +start a new session + { A: "H", D: "", @@ -535,6 +537,10 @@ intro message (variant 1) Z: "<64 bytes signature of entire message using sender's signing key>" } +D is encrypted with session key K which is derived by + +K = PKE(H, SI.enckey, N) + ordered data message (variant 2) { @@ -552,6 +558,7 @@ data sent anonymously over the network to a recipiant from a sender. sent inside a HSFM encrypted with a shared secret. { + A: protocol_number_uint, D: "", I: Introduction for reply, S: SI of sender, diff --git a/include/llarp/messages/path_transfer.hpp b/include/llarp/messages/path_transfer.hpp index e2594f4ab..57904d84d 100644 --- a/include/llarp/messages/path_transfer.hpp +++ b/include/llarp/messages/path_transfer.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace llarp { @@ -12,8 +13,8 @@ namespace llarp struct PathTransferMessage : public IMessage { PathID_t P; - Encrypted T; - uint64_t V = 0; + service::ProtocolFrame* T = nullptr; + uint64_t V = 0; TunnelNonce Y; PathTransferMessage(); diff --git a/include/llarp/pathset.hpp b/include/llarp/pathset.hpp index 19e099abd..dc11055f2 100644 --- a/include/llarp/pathset.hpp +++ b/include/llarp/pathset.hpp @@ -81,6 +81,9 @@ namespace llarp Path* PickRandomEstablishedPath(); + Path* + GetPathByRouter(const RouterID& router); + bool GetCurrentIntroductions( std::set< llarp::service::Introduction >& intros) const; diff --git a/include/llarp/service/Intro.hpp b/include/llarp/service/Intro.hpp index 4b2b41ef3..2ef0ac2f8 100644 --- a/include/llarp/service/Intro.hpp +++ b/include/llarp/service/Intro.hpp @@ -43,6 +43,9 @@ namespace llarp bool DecodeKey(llarp_buffer_t key, llarp_buffer_t* buf); + void + Clear(); + bool operator<(const Introduction& other) const { diff --git a/include/llarp/service/endpoint.hpp b/include/llarp/service/endpoint.hpp index d51665f20..e670a9823 100644 --- a/include/llarp/service/endpoint.hpp +++ b/include/llarp/service/endpoint.hpp @@ -36,6 +36,12 @@ namespace llarp llarp_threadpool* Worker(); + llarp_router* + Router() + { + return m_Router; + } + bool Start(); @@ -63,8 +69,11 @@ namespace llarp bool ForgetPathToService(const Address& remote); - byte_t* - GetEncryptionSecretKey(); + Identity* + GetIdentity() + { + return &m_Identity; + } /// context needed to initiate an outbound hidden service session struct OutboundContext : public llarp_pathbuilder_context @@ -74,6 +83,12 @@ namespace llarp /// the remote hidden service's curren intro set IntroSet currentIntroSet; + /// the current selected intro + Introduction selectedIntro; + + /// update the current selected intro to be a new best introduction + void + ShiftIntroduction(); /// encrypt asynchronously and send to remote endpoint from us void @@ -91,26 +106,17 @@ namespace llarp private: void - AsyncEncrypt(ProtocolMessage* msg, - std::function< void(ProtocolMessage*) > result); - void - AsyncGenIntro(ProtocolMessage* msg, - std::function< void(ProtocolMessage*) > result); + AsyncEncrypt(llarp_buffer_t payload); - /// handle key exchange done void - HandleIntroGen(ProtocolMessage* msg); - /// send an encrypted message + AsyncGenIntro(llarp_buffer_t payload); + + /// send a fully encrypted hidden service frame void - SendMessage(ProtocolMessage* msg); + Send(ProtocolFrame& f); uint64_t sequenceNo = 0; llarp::SharedSecret sharedKey; - llarp::util::CoDelQueue< - ProtocolMessage*, ProtocolMessage::GetTime, - ProtocolMessage::PutTime, ProtocolMessage::Compare, - llarp::util::DummyMutex, llarp::util::DummyLock > - m_SendQueue; Endpoint* m_Parent; }; @@ -157,6 +163,8 @@ namespace llarp Identity m_Identity; std::unordered_map< Address, OutboundContext*, Address::Hash > m_RemoteSessions; + std::unordered_map< Address, PathEnsureHook, Address::Hash > + m_PendingServiceLookups; uint64_t m_CurrentPublishTX = 0; llarp_time_t m_LastPublish = 0; llarp_time_t m_LastPublishAttempt = 0; diff --git a/include/llarp/service/frame.hpp b/include/llarp/service/frame.hpp new file mode 100644 index 000000000..11e598d2b --- /dev/null +++ b/include/llarp/service/frame.hpp @@ -0,0 +1,28 @@ +#ifndef LLARP_SERVICE_FRAME_HPP +#define LLARP_SERVICE_FRAME_HPP +#include +#include +#include + +namespace llarp +{ + namespace service + { + struct DataFrame : public llarp::IBEncodeMessage + { + llarp::Encrypted D; + llarp::PubKey H; + llarp::KeyExchangeNonce N; + uint64_t S = 0; + llarp::Signature Z; + + bool + BEncode(llarp_buffer_t* buf) const; + + bool + DecodeKey(llarp_buffer_t key, llarp_buffer_t* val); + }; + } // namespace service +} // namespace llarp + +#endif \ No newline at end of file diff --git a/include/llarp/service/protocol.hpp b/include/llarp/service/protocol.hpp index 6f32e9104..d80c494e4 100644 --- a/include/llarp/service/protocol.hpp +++ b/include/llarp/service/protocol.hpp @@ -3,27 +3,33 @@ #include #include #include +#include +#include +#include #include namespace llarp { namespace service { + constexpr std::size_t MAX_PROTOCOL_MESSAGE_SIZE = 2048; + enum ProtocolType { eProtocolText = 0, eProtocolTraffic = 1 }; + /// inner message struct ProtocolMessage : public llarp::IBEncodeMessage { - ProtocolMessage(ProtocolType t, uint64_t seqno); + ProtocolMessage(); ~ProtocolMessage(); ProtocolType proto; llarp_time_t queued = 0; std::vector< byte_t > payload; - llarp::KeyExchangeNonce N; - uint64_t sequenceNum; + Introduction introReply; + ServiceInfo sender; bool DecodeKey(llarp_buffer_t key, llarp_buffer_t* val); @@ -32,34 +38,35 @@ namespace llarp void PutBuffer(llarp_buffer_t payload); + }; + + /// outer message + struct ProtocolFrame : public llarp::IBEncodeMessage + { + llarp::Encrypted D; + uint64_t S = 0; + llarp::PubKey H; + llarp::KeyExchangeNonce N; + llarp::Signature Z; + + ~ProtocolFrame(); + + bool + EncryptAndSign(llarp_crypto* c, const ProtocolMessage* msg, + byte_t* sharedkey, byte_t* signingkey); + + bool + DecryptPayloadInto(llarp_crypto* c, byte_t* sharedkey, + ProtocolMessage* into) const; - struct Compare - { - bool - operator()(const ProtocolMessage* left, - const ProtocolMessage* right) const - { - return left->sequenceNum < right->sequenceNum; - } - }; + bool + DecodeKey(llarp_buffer_t key, llarp_buffer_t* val); - struct GetTime - { - llarp_time_t - operator()(const ProtocolMessage* msg) const - { - return msg->queued; - } - }; + bool + BEncode(llarp_buffer_t* buf) const; - struct PutTime - { - void - operator()(ProtocolMessage* msg, llarp_time_t now) const - { - msg->queued = now; - } - }; + bool + Verify(llarp_crypto* c, byte_t* signingkey); }; } // namespace service } // namespace llarp diff --git a/llarp/buffer.hpp b/llarp/buffer.hpp index 229444254..fc1da19e4 100644 --- a/llarp/buffer.hpp +++ b/llarp/buffer.hpp @@ -27,6 +27,18 @@ namespace llarp buff.sz = t.size(); return buff; } + + template < typename T > + llarp_buffer_t + ConstBuffer(const T& t) + { + llarp_buffer_t buff; + buff.base = (byte_t*)&t[0]; + buff.cur = buff.base; + buff.sz = t.size(); + return buff; + } + } // namespace llarp #endif diff --git a/llarp/pathset.cpp b/llarp/pathset.cpp index 8b391fc12..f890e6149 100644 --- a/llarp/pathset.cpp +++ b/llarp/pathset.cpp @@ -45,6 +45,19 @@ namespace llarp } } + Path* + PathSet::GetPathByRouter(const RouterID& id) + { + auto itr = m_Paths.begin(); + while(itr != m_Paths.end()) + { + if(itr->first.first == id) + return itr->second; + ++itr; + } + return nullptr; + } + size_t PathSet::NumInStatus(PathStatus st) const { diff --git a/llarp/routing/path_transfer.cpp b/llarp/routing/path_transfer.cpp index 876b7e885..1a08922bc 100644 --- a/llarp/routing/path_transfer.cpp +++ b/llarp/routing/path_transfer.cpp @@ -1,4 +1,5 @@ #include +#include "../buffer.hpp" #include "../router.hpp" namespace llarp @@ -19,8 +20,13 @@ namespace llarp bool read = false; if(!BEncodeMaybeReadDictEntry("P", P, read, key, val)) return false; - if(!BEncodeMaybeReadDictEntry("T", T, read, key, val)) - return false; + if(llarp_buffer_eq(key, "T")) + { + if(T) + delete T; + T = new service::ProtocolFrame(); + return T->BDecode(val); + } if(!BEncodeMaybeReadDictInt("V", V, read, key, val)) return false; if(!BEncodeMaybeReadDictEntry("Y", Y, read, key, val)) @@ -37,8 +43,12 @@ namespace llarp return false; if(!BEncodeWriteDictEntry("P", P, buf)) return false; - if(!BEncodeWriteDictEntry("T", T, buf)) + + if(!bencode_write_bytestring(buf, "T", 1)) + return false; + if(!T->BEncode(buf)) return false; + if(!BEncodeWriteDictInt(buf, "V", LLARP_PROTO_VERSION)) return false; if(!BEncodeWriteDictEntry("Y", Y, buf)) @@ -51,15 +61,31 @@ namespace llarp PathTransferMessage::HandleMessage(IMessageHandler* h, llarp_router* r) const { - auto path = r->paths.GetByUpstream(r->pubkey(), P); - if(path) + auto path = r->paths.GetByDownstream(r->pubkey(), P); + if(!path) { - return path->HandleDownstream(T.Buffer(), Y, r); + llarp::LogWarn("No such path for path transfer pathid=", P); + return false; } - llarp::LogWarn("No such local path for path transfer src=", from, - " dst=", P); - return false; + if(!T) + { + llarp::LogError("no data to transfer on data message"); + return false; + } + + byte_t tmp[service::MAX_PROTOCOL_MESSAGE_SIZE]; + auto buf = llarp::StackBuffer< decltype(tmp) >(tmp); + if(!T->BEncode(&buf)) + { + llarp::LogWarn("failed to transfer data message, encode failed"); + return false; + } + // rewind + buf.sz = buf.cur - buf.base; + buf.cur = buf.base; + // send + return path->HandleDownstream(buf, Y, r); } - } // namespace routing -} // namespace llarp \ No newline at end of file + } // namespace routing +} // namespace llarp diff --git a/llarp/service.cpp b/llarp/service.cpp index 370d04ce7..5aae9cb23 100644 --- a/llarp/service.cpp +++ b/llarp/service.cpp @@ -140,6 +140,15 @@ namespace llarp return bencode_end(buf); } + void + Introduction::Clear() + { + router.Zero(); + pathID.Zero(); + latency = 0; + expiresAt = 0; + } + Identity::~Identity() { } diff --git a/llarp/service/endpoint.cpp b/llarp/service/endpoint.cpp index 23548a617..0b57369bf 100644 --- a/llarp/service/endpoint.cpp +++ b/llarp/service/endpoint.cpp @@ -300,32 +300,80 @@ namespace llarp struct HiddenServiceAddressLookup : public IServiceLookup { Endpoint* endpoint; - HiddenServiceAddressLookup(Endpoint* parent) : endpoint(parent) + Address remote; + uint64_t txid; + HiddenServiceAddressLookup(Endpoint* parent, const Address& addr, + uint64_t tx) + : endpoint(parent), remote(addr), txid(tx) { } bool HandleResponse(const std::set< IntroSet >& results) { - if(results.size() == 0) - { - auto itr = results.begin(); - endpoint->PutNewOutboundContext(*itr); - } - else + if(results.size() == 1) { - // TODO: retry request? + endpoint->PutNewOutboundContext(*results.begin()); } + delete this; return true; } + + llarp::routing::IMessage* + BuildRequestMessage() + { + llarp::routing::DHTMessage* msg = new llarp::routing::DHTMessage(); + msg->M.push_back(new llarp::dht::FindIntroMessage(remote, txid)); + return msg; + } }; + void + Endpoint::PutNewOutboundContext(const llarp::service::IntroSet& introset) + { + Address addr; + introset.A.CalculateAddress(addr); + + // only add new session if it's not there + if(m_RemoteSessions.find(addr) == m_RemoteSessions.end()) + { + OutboundContext* ctx = new OutboundContext(introset, this); + m_RemoteSessions.insert(std::make_pair(addr, ctx)); + llarp::LogInfo("Created New outbound context for ", addr.ToString()); + } + + // inform pending + auto itr = m_PendingServiceLookups.find(addr); + if(itr != m_PendingServiceLookups.end()) + { + itr->second(m_RemoteSessions.at(addr)); + m_PendingServiceLookups.erase(itr); + } + } + bool Endpoint::EnsurePathToService(const Address& remote, PathEnsureHook hook, llarp_time_t timeoutMS) { - // TODO: implement me - return false; + { + auto itr = m_RemoteSessions.find(remote); + if(itr != m_RemoteSessions.end()) + { + hook(itr->second); + return true; + } + } + auto itr = m_PendingServiceLookups.find(remote); + if(itr != m_PendingServiceLookups.end()) + { + // duplicate + return false; + } + m_PendingServiceLookups.insert(std::make_pair(remote, hook)); + HiddenServiceAddressLookup* job = + new HiddenServiceAddressLookup(this, remote, GenTXID()); + m_PendingLookups.insert(std::make_pair(job->txid, job)); + return true; } Endpoint::OutboundContext::OutboundContext(const IntroSet& intro, @@ -333,16 +381,28 @@ namespace llarp : llarp_pathbuilder_context(parent->m_Router, parent->m_Router->dht, 2, 4) , currentIntroSet(intro) - , m_SendQueue(parent->Name() + "::outbound_queue") , m_Parent(parent) { + selectedIntro.Clear(); } Endpoint::OutboundContext::~OutboundContext() { } + void + Endpoint::OutboundContext::ShiftIntroduction() + { + for(const auto& intro : currentIntroSet.I) + { + if(intro.expiresAt > selectedIntro.expiresAt) + { + selectedIntro = intro; + } + } + } + bool Endpoint::OutboundContext::HandleGotIntroMessage( const llarp::dht::GotIntroMessage* msg) @@ -355,6 +415,7 @@ namespace llarp if(itr->VerifySignature(crypto) && currentIntroSet.A == itr->A) { currentIntroSet = *itr; + ShiftIntroduction(); return true; } else @@ -371,68 +432,89 @@ namespace llarp Endpoint::OutboundContext::AsyncEncryptAndSendTo(llarp_buffer_t data, ProtocolType protocol) { - auto sendto = - std::bind(&OutboundContext::SendMessage, this, std::placeholders::_1); - ProtocolMessage* msg = new ProtocolMessage(protocol, sequenceNo); - msg->PutBuffer(data); if(sequenceNo) { - AsyncEncrypt(msg, sendto); + AsyncEncrypt(data); } else { - AsyncGenIntro(msg, sendto); + AsyncGenIntro(data); } } - struct AsyncKeyExchange + struct AsyncIntroGen { llarp_logic* logic; llarp_crypto* crypto; byte_t* sharedKey; byte_t* remotePubkey; - byte_t* localSeckey; - byte_t* nonce; - ProtocolMessage* msg = nullptr; - std::function< void(ProtocolMessage*) > hook; + Identity* m_LocalIdentity; + ProtocolMessage msg; + ProtocolFrame frame; + std::function< void(ProtocolFrame&) > hook; - AsyncKeyExchange(llarp_logic* l, llarp_crypto* c, byte_t* key, - byte_t* remote, byte_t* localSecret, byte_t* n) + AsyncIntroGen(llarp_logic* l, llarp_crypto* c, byte_t* key, + byte_t* remote, Identity* localident) : logic(l) , crypto(c) , sharedKey(key) , remotePubkey(remote) - , localSeckey(localSecret) - , nonce(n) + , m_LocalIdentity(localident) { } + static void + Result(void* user) + { + AsyncIntroGen* self = static_cast< AsyncIntroGen* >(user); + self->hook(self->frame); + delete self; + } + static void Work(void* user) { - AsyncKeyExchange* self = static_cast< AsyncKeyExchange* >(user); + AsyncIntroGen* self = static_cast< AsyncIntroGen* >(user); + // randomize Nounce + self->frame.N.Randomize(); + // derive session key self->crypto->dh_server(self->sharedKey, self->remotePubkey, - self->localSeckey, self->nonce); + self->m_LocalIdentity->enckey, self->frame.N); + // encrypt and sign + self->frame.EncryptAndSign(self->crypto, &self->msg, self->sharedKey, + self->m_LocalIdentity->signkey); + // inform result + llarp_logic_queue_job(self->logic, {self, &Result}); } }; void - Endpoint::OutboundContext::AsyncGenIntro( - ProtocolMessage* msg, std::function< void(ProtocolMessage*) > result) + Endpoint::OutboundContext::AsyncGenIntro(llarp_buffer_t payload) { - msg->N.Randomize(); - AsyncKeyExchange* ex = new AsyncKeyExchange( - m_Parent->Logic(), m_Parent->Crypto(), sharedKey, - currentIntroSet.A.enckey, m_Parent->GetEncryptionSecretKey(), msg->N); + AsyncIntroGen* ex = + new AsyncIntroGen(m_Parent->Logic(), m_Parent->Crypto(), sharedKey, + currentIntroSet.A.enckey, m_Parent->GetIdentity()); + ex->hook = std::bind(&Endpoint::OutboundContext::Send, this, + std::placeholders::_1); + + ex->msg.PutBuffer(payload); llarp_threadpool_queue_job(m_Parent->Worker(), - {ex, &AsyncKeyExchange::Work}); + {ex, &AsyncIntroGen::Work}); } void - Endpoint::OutboundContext::SendMessage(ProtocolMessage* msg) + Endpoint::OutboundContext::Send(ProtocolFrame& msg) { - // TODO: delete msg - // TODO: implement me + // in this context we assume the message contents are encrypted + auto path = GetPathByRouter(selectedIntro.router); + if(path) + { + routing::PathTransferMessage transfer; + transfer.T = &msg; + transfer.Y.Randomize(); + transfer.P = selectedIntro.pathID; + path->SendRoutingMessage(&transfer, m_Parent->Router()); + } } bool @@ -473,8 +555,7 @@ namespace llarp } void - Endpoint::OutboundContext::AsyncEncrypt( - ProtocolMessage* msg, std::function< void(ProtocolMessage*) > result) + Endpoint::OutboundContext::AsyncEncrypt(llarp_buffer_t payload) { // TODO: implement me } @@ -497,11 +578,5 @@ namespace llarp return m_Router->tp; } - byte_t* - Endpoint::GetEncryptionSecretKey() - { - return m_Identity.enckey; - } - } // namespace service } // namespace llarp diff --git a/llarp/service/frame.cpp b/llarp/service/frame.cpp new file mode 100644 index 000000000..ad042f728 --- /dev/null +++ b/llarp/service/frame.cpp @@ -0,0 +1,8 @@ +#include + +namespace llarp +{ + namespace service + { + } +} // namespace llarp \ No newline at end of file diff --git a/llarp/service/protocol.cpp b/llarp/service/protocol.cpp index db7cc0e19..61c9735f0 100644 --- a/llarp/service/protocol.cpp +++ b/llarp/service/protocol.cpp @@ -1,11 +1,11 @@ #include +#include "buffer.hpp" namespace llarp { namespace service { - ProtocolMessage::ProtocolMessage(ProtocolType t, uint64_t seqno) - : proto(t), sequenceNum(seqno) + ProtocolMessage::ProtocolMessage() { } @@ -32,6 +32,105 @@ namespace llarp { payload.resize(buf.sz); memcpy(payload.data(), buf.base, buf.sz); + payload.shrink_to_fit(); } + + ProtocolFrame::~ProtocolFrame() + { + } + + bool + ProtocolFrame::BEncode(llarp_buffer_t* buf) const + { + if(!bencode_start_dict(buf)) + return false; + if(!BEncodeWriteDictMsgType(buf, "A", "H")) + return false; + if(!BEncodeWriteDictEntry("D", D, buf)) + return false; + if(S == 0) + { + if(!BEncodeWriteDictEntry("H", H, buf)) + return false; + } + if(!BEncodeWriteDictEntry("N", N, buf)) + return false; + if(!BEncodeWriteDictInt(buf, "S", S)) + return false; + if(!BEncodeWriteDictInt(buf, "V", version)) + return false; + if(!BEncodeWriteDictEntry("Z", Z, buf)) + return false; + return bencode_end(buf); + } + + bool + ProtocolFrame::DecodeKey(llarp_buffer_t key, llarp_buffer_t* val) + { + bool read = false; + if(!BEncodeMaybeReadDictEntry("D", D, read, key, val)) + return false; + if(!BEncodeMaybeReadDictEntry("H", H, read, key, val)) + return false; + if(!BEncodeMaybeReadDictEntry("N", N, read, key, val)) + return false; + if(!BEncodeMaybeReadDictInt("S", S, read, key, val)) + return false; + if(!BEncodeMaybeReadVersion("V", version, LLARP_PROTO_VERSION, read, key, + val)) + return false; + if(!BEncodeMaybeReadDictEntry("Z", Z, read, key, val)) + return false; + return read; + } + + bool + ProtocolFrame::EncryptAndSign(llarp_crypto* crypto, + const ProtocolMessage* msg, + byte_t* sessionKey, byte_t* signingkey) + { + // put payload and encrypt + D = llarp::ConstBuffer(msg->payload); + memcpy(D.data(), msg->payload.data(), D.size()); + auto dbuf = D.Buffer(); + crypto->xchacha20(*dbuf, sessionKey, N); + // zero out signature + Z.Zero(); + // encode + byte_t tmp[MAX_PROTOCOL_MESSAGE_SIZE]; + auto buf = llarp::StackBuffer< decltype(tmp) >(tmp); + if(!BEncode(&buf)) + return false; + // rewind + buf.sz = buf.cur - buf.base; + buf.cur = buf.base; + // sign + return crypto->sign(Z, signingkey, buf); + } + + bool + ProtocolFrame::Verify(llarp_crypto* crypto, byte_t* signkey) + { + // save signature + llarp::Signature sig = Z; + // zero out signature for verify + Z.Zero(); + bool result = false; + // serialize + byte_t tmp[MAX_PROTOCOL_MESSAGE_SIZE]; + auto buf = llarp::StackBuffer< decltype(tmp) >(tmp); + if(BEncode(&buf)) + { + // rewind buffer + buf.sz = buf.cur - buf.base; + buf.cur = buf.base; + // verify + result = crypto->verify(sig, buf, signkey); + } + // restore signature + Z = sig; + return result; + } + } // namespace service } // namespace llarp \ No newline at end of file diff --git a/llarp/transit_hop.cpp b/llarp/transit_hop.cpp index 22598e2c8..4c526c5da 100644 --- a/llarp/transit_hop.cpp +++ b/llarp/transit_hop.cpp @@ -127,12 +127,6 @@ namespace llarp TransitHop::HandlePathTransferMessage( const llarp::routing::PathTransferMessage* msg, llarp_router* r) { - auto path = r->paths.GetByDownstream(r->pubkey(), msg->P); - if(path) - { - return path->HandleDownstream(msg->T.Buffer(), msg->Y, r); - } - llarp::LogWarn("No such path for path transfer pathid=", msg->P); return false; }