diff --git a/grunt.js b/grunt.js index 152cef351..dab1bcef5 100644 --- a/grunt.js +++ b/grunt.js @@ -32,7 +32,11 @@ module.exports = function(grunt) { "src/js/sjcl/core/convenience.js", "src/js/sjcl/core/bn.js", "src/js/sjcl/core/ecc.js", - "src/js/sjcl/core/srp.js" + "src/js/sjcl/core/srp.js", + "src/js/sjcl-custom/sjcl-secp256k1.js", + "src/js/sjcl-custom/sjcl-ripemd160.js", + "src/js/sjcl-custom/sjcl-extramath.js", + "src/js/sjcl-custom/sjcl-ecdsa-der.js" ], dest: 'build/sjcl.js' } diff --git a/package.json b/package.json index 11ee2d9db..70566db75 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,17 @@ { - "name": "ripple", + "name": "ripple-lib", "version": "0.7.0", - "description": "Open-source peer-to-peer payment network", + "description": "Ripple JavaScript client library", + + "files": [ + "src/js/*.js", + "build/sjcl.js" + ], + "main": "src/js", "directories": { "test": "test" }, + "dependencies": { "async": "~0.1.22", "ws": "~0.4.22", @@ -15,13 +22,15 @@ "buster": "~0.6.2", "grunt-webpack": "~0.4.0" }, + "scripts": { "test": "buster test" }, + "repository": { "type": "git", "url": "git://github.com/jedmccaleb/NewCoin.git" }, - "readmeFilename": "README" + "readmeFilename": "README.md" } diff --git a/rippled-example.cfg b/rippled-example.cfg index 6eb7172bf..f682966e7 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -219,7 +219,8 @@ # [cluster_nodes]: # To extend full trust to other nodes, place their node public keys here. # Generally, you should only do this for nodes under common administration. -# Node public keys start with an 'n'. +# Node public keys start with an 'n'. To give a node a name for identification +# place a space after the public key and then the name. # # [ledger_history]: # The number of past ledgers to acquire on server startup and the minimum to diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index 330886433..df0fc8f45 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -142,7 +142,7 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) std::vector elements; boost::split(elements, val, boost::is_any_of("\t\n\r ,/")); - if ((elements.size() < 0) || (elements.size() > 3)) + if (elements.size() > 3) throw std::runtime_error("invalid amount string"); value = elements[0]; @@ -1214,6 +1214,40 @@ std::string STAmount::getFullText() const } } +STAmount STAmount::getRound() const +{ + if (mIsNative) + return *this; + + uint64 valueDigits = mValue % 1000000000ull; + if (valueDigits == 1) + return STAmount(mCurrency, mIssuer, mValue - 1, mOffset, mIsNegative); + else if (valueDigits == 999999999ull) + return STAmount(mCurrency, mIssuer, mValue + 1, mOffset, mIsNegative); + + return *this; +} + +void STAmount::roundSelf() +{ + if (mIsNative) + return; + + uint64 valueDigits = mValue % 1000000000ull; + if (valueDigits == 1) + { + mValue -= 1; + if (mValue < cMinValue) + canonicalize(); + } + else if (valueDigits == 999999999ull) + { + mValue += 1; + if (mValue > cMaxValue) + canonicalize(); + } +} + #if 0 std::string STAmount::getExtendedText() const { @@ -1460,7 +1494,7 @@ BOOST_AUTO_TEST_CASE( CustomCurrency_test ) BOOST_TEST_MESSAGE("Amount CC Complete"); } -static void roundTest(int n, int d, int m) +static bool roundTest(int n, int d, int m) { // check STAmount rounding STAmount num(CURRENCY_ONE, ACCOUNT_ONE, n); STAmount den(CURRENCY_ONE, ACCOUNT_ONE, d); @@ -1469,18 +1503,19 @@ static void roundTest(int n, int d, int m) STAmount res = STAmount::multiply(quot, mul, CURRENCY_ONE, ACCOUNT_ONE); if (res.isNative()) BOOST_FAIL("Product is native"); - - cLog(lsDEBUG) << n << " / " << d << " = " << quot.getText(); + res.roundSelf(); STAmount cmp(CURRENCY_ONE, ACCOUNT_ONE, (n * m) / d); if (cmp.isNative()) BOOST_FAIL("Comparison amount is native"); if (res == cmp) - return; + return true; cmp.throwComparable(res); - cLog(lsINFO) << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = " + cLog(lsWARNING) << "(" << num.getText() << "/" << den.getText() << ") X " << mul.getText() << " = " << res.getText() << " not " << cmp.getText(); + BOOST_FAIL("Round fail"); + return false; } static void mulTest(int a, int b) diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index a2d608279..bb615cec3 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -50,7 +50,7 @@ Application::Application() : getRand(mNonce256.begin(), mNonce256.size()); getRand(reinterpret_cast(&mNonceST), sizeof(mNonceST)); mJobQueue.setThreadCount(); - mSweepTimer.expires_from_now(boost::posix_time::seconds(20)); + mSweepTimer.expires_from_now(boost::posix_time::seconds(10)); mSweepTimer.async_wait(boost::bind(&Application::sweep, this)); } diff --git a/src/cpp/ripple/AutoSocket.h b/src/cpp/ripple/AutoSocket.h index 8b87ec125..904341960 100644 --- a/src/cpp/ripple/AutoSocket.h +++ b/src/cpp/ripple/AutoSocket.h @@ -152,6 +152,15 @@ public: boost::asio::async_write(PlainSocket(), buffers, handler); } + template + void async_write(boost::asio::basic_streambuf& buffers, Handler handler) + { + if (isSecure()) + boost::asio::async_write(*mSocket, buffers, handler); + else + boost::asio::async_write(PlainSocket(), buffers, handler); + } + template void async_read(const Buf& buffers, Condition cond, Handler handler) { diff --git a/src/cpp/ripple/DBInit.cpp b/src/cpp/ripple/DBInit.cpp index b16ba0f41..bdc6c29b5 100644 --- a/src/cpp/ripple/DBInit.cpp +++ b/src/cpp/ripple/DBInit.cpp @@ -55,15 +55,16 @@ const char *LedgerDBInit[] = { );", "CREATE INDEX SeqLedger ON Ledgers(LedgerSeq);", - "CREATE TABLE LedgerValidations ( \ + "CREATE TABLE Validations ( \ LedgerHash CHARACTER(64), \ NodePubKey CHARACTER(56), \ - Flags BIGINT UNSIGNED, \ SignTime BIGINT UNSIGNED, \ - Signature BLOB \ + RawData BLOB \ );", - "CREATE INDEX ValidationByHash ON \ - LedgerValidations(LedgerHash);", + "CREATE INDEX ValidationsByHash ON \ + Validations(LedgerHash);", + "CREATE INDEX ValidationsByTime ON \ + Validations(SignTime);", "END TRANSACTION;" }; diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index 44cfffbd4..204f4eb00 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -166,15 +166,9 @@ void HashedObjectStore::bulkWrite() HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) { - HashedObject::pointer obj; - { - obj = mCache.fetch(hash); - if (obj) - { -// cLog(lsTRACE) << "HOS: " << hash << " fetch: incache"; - return obj; - } - } + HashedObject::pointer obj = mCache.fetch(hash); + if (obj) + return obj; if (mNegativeCache.isPresent(hash)) return obj; diff --git a/src/cpp/ripple/KeyCache.h b/src/cpp/ripple/KeyCache.h index 95b0277b6..63bc2e80c 100644 --- a/src/cpp/ripple/KeyCache.h +++ b/src/cpp/ripple/KeyCache.h @@ -23,7 +23,7 @@ public: KeyCache(const std::string& name, int size = 0, int age = 120) : mName(name), mTargetSize(size), mTargetAge(age) { - assert((mTargetSize >= 0) && (mTargetAge > 2)); + assert((size >= 0) && (age > 2)); } void getSize() diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 61e7f59de..0cb2f0b2a 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -113,14 +113,31 @@ Ledger::Ledger(const std::string& rawLedger, bool hasPrefix) : zeroFees(); } +void Ledger::setImmutable() +{ + if (!mImmutable) + { + updateHash(); + mImmutable = true; + if (mTransactionMap) + mTransactionMap->setImmutable(); + if (mAccountStateMap) + mAccountStateMap->setImmutable(); + } +} + void Ledger::updateHash() { if (!mImmutable) { - if (mTransactionMap) mTransHash = mTransactionMap->getHash(); - else mTransHash.zero(); - if (mAccountStateMap) mAccountHash = mAccountStateMap->getHash(); - else mAccountHash.zero(); + if (mTransactionMap) + mTransHash = mTransactionMap->getHash(); + else + mTransHash.zero(); + if (mAccountStateMap) + mAccountHash = mAccountStateMap->getHash(); + else + mAccountHash.zero(); } Serializer s(118); @@ -170,18 +187,17 @@ void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctClos mCloseTime = correctCloseTime ? (closeTime - (closeTime % closeResolution)) : closeTime; mCloseResolution = closeResolution; mCloseFlags = correctCloseTime ? 0 : sLCF_NoConsensusTime; - updateHash(); mAccepted = true; - mImmutable = true; + setImmutable(); } void Ledger::setAccepted() { // used when we acquired the ledger // FIXME assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); - mCloseTime -= mCloseTime % mCloseResolution; - updateHash(); + if ((mCloseFlags & sLCF_NoConsensusTime) == 0) + mCloseTime -= mCloseTime % mCloseResolution; mAccepted = true; - mImmutable = true; + setImmutable(); } AccountState::pointer Ledger::getAccountState(const RippleAddress& accountID) @@ -504,10 +520,7 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) ScopedLock sl(theApp->getLedgerDB()->getDBLock()); if (!db->executeSQL(sql) || !db->startIterRows()) - { - cLog(lsDEBUG) << "No ledger for query: " << sql; return Ledger::pointer(); - } db->getStr("LedgerHash", hash); ledgerHash.SetHex(hash, true); @@ -530,6 +543,8 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) Ledger::pointer ret = boost::make_shared(prevHash, transHash, accountHash, totCoins, closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq); ret->setClosed(); + if (theApp->getOPs().haveLedger(ledgerSeq)) + ret->setAccepted(); if (ret->getHash() != ledgerHash) { if (sLog(lsERROR)) @@ -550,7 +565,7 @@ uint256 Ledger::getHashByIndex(uint32 ledgerIndex) { uint256 ret; - std::string sql="SELECT LedgerHash FROM Ledgers WHERE LedgerSeq='"; + std::string sql="SELECT LedgerHash FROM Ledgers INDEXED BY SeqLedger WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index 7b11adc26..71ede9357 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -124,7 +124,7 @@ public: void setClosed() { mClosed = true; } void setAccepted(uint32 closeTime, int closeResolution, bool correctCloseTime); void setAccepted(); - void setImmutable() { updateHash(); mImmutable = true; } + void setImmutable(); bool isClosed() { return mClosed; } bool isAccepted() { return mAccepted; } bool isImmutable() { return mImmutable; } diff --git a/src/cpp/ripple/LedgerAcquire.cpp b/src/cpp/ripple/LedgerAcquire.cpp index a17cc6ef6..75a2e4243 100644 --- a/src/cpp/ripple/LedgerAcquire.cpp +++ b/src/cpp/ripple/LedgerAcquire.cpp @@ -160,6 +160,7 @@ bool LedgerAcquire::tryLocal() { cLog(lsDEBUG) << "Had everything locally"; mComplete = true; + mLedger->setClosed(); } return mComplete; @@ -244,11 +245,9 @@ void LedgerAcquire::done() if (isComplete() && !isFailed() && mLedger) { + mLedger->setClosed(); if (mAccept) - { - mLedger->setClosed(); mLedger->setAccepted(); - } theApp->getLedgerMaster().storeLedger(mLedger); } else diff --git a/src/cpp/ripple/LedgerHistory.cpp b/src/cpp/ripple/LedgerHistory.cpp index 39aab0a04..420b44255 100644 --- a/src/cpp/ripple/LedgerHistory.cpp +++ b/src/cpp/ripple/LedgerHistory.cpp @@ -30,7 +30,7 @@ void LedgerHistory::addLedger(Ledger::pointer ledger) void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus) { - assert(ledger && ledger->isAccepted()); + assert(ledger && ledger->isAccepted() && ledger->isImmutable()); uint256 h(ledger->getHash()); boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex()); mLedgersByHash.canonicalize(h, ledger, true); @@ -70,6 +70,7 @@ Ledger::pointer LedgerHistory::getLedgerBySeq(uint32 index) assert(ret->getLedgerSeq() == index); sl.lock(); + assert(ret->isImmutable()); mLedgersByHash.canonicalize(ret->getHash(), ret); mLedgersByIndex[ret->getLedgerSeq()] = ret->getHash(); return (ret->getLedgerSeq() == index) ? ret : Ledger::pointer(); @@ -80,13 +81,15 @@ Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash) Ledger::pointer ret = mLedgersByHash.fetch(hash); if (ret) { - assert(ret->getHash() == hash); + assert(ret->isImmutable()); + assert(ret->getHash() == hash); // FIXME: We seem to be getting these return ret; } ret = Ledger::loadByHash(hash); if (!ret) return ret; + assert(ret->isImmutable()); assert(ret->getHash() == hash); mLedgersByHash.canonicalize(ret->getHash(), ret); assert(ret->getHash() == hash); diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 553a4e147..63be1e8f9 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -133,6 +133,12 @@ bool LedgerMaster::haveLedgerRange(uint32 from, uint32 to) return (prevMissing == RangeSet::RangeSetAbsent) || (prevMissing < from); } +bool LedgerMaster::haveLedger(uint32 seq) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return mCompleteLedgers.hasValue(seq); +} + void LedgerMaster::asyncAccept(Ledger::pointer ledger) { uint32 seq = ledger->getLedgerSeq(); diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index dfcf8ef2e..e3cb37d98 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -126,6 +126,7 @@ public: void fixMismatch(Ledger::ref ledger); bool haveLedgerRange(uint32 from, uint32 to); + bool haveLedger(uint32 seq); void resumeAcquiring(); diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 3fbf0768b..4816d3622 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -113,6 +113,11 @@ bool NetworkOPs::haveLedgerRange(uint32 from, uint32 to) return mLedgerMaster->haveLedgerRange(from, to); } +bool NetworkOPs::haveLedger(uint32 seq) +{ + return mLedgerMaster->haveLedger(seq); +} + bool NetworkOPs::addWantedHash(const uint256& h) { boost::recursive_mutex::scoped_lock sl(mWantedHashLock); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 0979671fb..d4bf0bee8 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -150,6 +150,7 @@ public: // Do we have this inclusive range of ledgers in our database bool haveLedgerRange(uint32 from, uint32 to); + bool haveLedger(uint32 seq); SerializedValidation::ref getLastValidation() { return mLastValidation; } void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; } diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index 7b686f3af..460f2108c 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -686,7 +686,7 @@ void Peer::recvHello(ripple::TMHello& packet) << "Peer speaks version " << (packet.protoversion() >> 16) << "." << (packet.protoversion() & 0xFF); mHello = packet; - if (theApp->getUNL().nodeInCluster(mNodePublic)) + if (theApp->getUNL().nodeInCluster(mNodePublic, mNodeName)) { mCluster = true; mLoad.setPrivileged(); @@ -1785,7 +1785,11 @@ Json::Value Peer::getJson() if (mInbound) ret["inbound"] = true; if (mCluster) + { ret["cluster"] = true; + if (!mNodeName.empty()) + ret["name"] = mNodeName; + } if (mHello.has_fullversion()) ret["version"] = mHello.fullversion(); diff --git a/src/cpp/ripple/Peer.h b/src/cpp/ripple/Peer.h index 2d3fb9527..f21e96b30 100644 --- a/src/cpp/ripple/Peer.h +++ b/src/cpp/ripple/Peer.h @@ -40,6 +40,7 @@ private: bool mActive; bool mCluster; // Node in our cluster RippleAddress mNodePublic; // Node public key of peer. + std::string mNodeName; ipPort mIpPort; ipPort mIpPortConnect; uint256 mCookieHash; diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 43a4aefbb..85ab661a7 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -296,6 +296,11 @@ Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit) return jvResult; } + if (jvRequest.isMember("debug_signing")) { + jvResult["tx_unsigned"] = strHex(stpTrans->getSerializer().peekData()); + jvResult["tx_signing_hash"] = stpTrans->getSigningHash().ToString(); + } + // FIXME: For performance, transactions should not be signed in this code path. stpTrans->sign(naAccountPrivate); diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index c6bb32aff..fd0c2fc6f 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -387,6 +387,9 @@ public: static bool issuerFromString(uint160& uDstIssuer, const std::string& sIssuer); Json::Value getJson(int) const; + + STAmount getRound() const; + void roundSelf(); }; extern STAmount saZero; diff --git a/src/cpp/ripple/TaggedCache.h b/src/cpp/ripple/TaggedCache.h index a5930494f..f8cb5d499 100644 --- a/src/cpp/ripple/TaggedCache.h +++ b/src/cpp/ripple/TaggedCache.h @@ -31,30 +31,39 @@ public: typedef boost::weak_ptr weak_data_ptr; typedef boost::shared_ptr data_ptr; - typedef bool (*visitor_func)(const c_Key&, c_Data&); - protected: - typedef std::pair cache_entry; + class cache_entry + { + public: + time_t last_use; + data_ptr ptr; + weak_data_ptr weak_ptr; + + cache_entry(time_t l, const data_ptr& d) : last_use(l), ptr(d), weak_ptr(d) { ; } + bool isCached() { return !!ptr; } + bool isExpired() { return weak_ptr.expired(); } + data_ptr lock() { return weak_ptr.lock(); } + void touch() { last_use = time(NULL); } + }; + typedef std::pair cache_pair; typedef boost::unordered_map cache_type; typedef typename cache_type::iterator cache_iterator; - typedef boost::unordered_map map_type; - typedef typename map_type::iterator map_iterator; mutable boost::recursive_mutex mLock; std::string mName; // Used for logging unsigned int mTargetSize; // Desired number of cache entries (0 = ignore) int mTargetAge; // Desired maximum cache age + int mCacheCount; // Number of items cached cache_type mCache; // Hold strong reference to recent objects - map_type mMap; // Track stored objects time_t mLastSweep; public: TaggedCache(const char *name, int size, int age) - : mName(name), mTargetSize(size), mTargetAge(age), mLastSweep(time(NULL)) { ; } + : mName(name), mTargetSize(size), mTargetAge(age), mCacheCount(0), mLastSweep(time(NULL)) { ; } int getTargetSize() const; int getTargetAge() const; @@ -66,8 +75,6 @@ public: void setTargetSize(int size); void setTargetAge(int age); void sweep(); - void visitAll(visitor_func); // Visits all tracked objects, removes selected objects - void visitCached(visitor_func); // Visits all cached objects, uncaches selected objects bool touch(const key_type& key); bool del(const key_type& key, bool valid); @@ -108,13 +115,13 @@ template void TaggedCache::setTa template int TaggedCache::getCacheSize() { boost::recursive_mutex::scoped_lock sl(mLock); - return mCache.size(); + return mCacheCount; } template int TaggedCache::getTrackSize() { boost::recursive_mutex::scoped_lock sl(mLock); - return mMap.size(); + return mCache.size(); } template void TaggedCache::sweep() @@ -123,134 +130,108 @@ template void TaggedCache::sweep time_t mLastSweep = time(NULL); time_t target = mLastSweep - mTargetAge; + int cacheRemovals = 0, mapRemovals = 0, cc = 0; - // Pass 1, remove old objects from cache - int cacheRemovals = 0; - if ((mTargetSize == 0) || (mCache.size() > mTargetSize)) + if ((mTargetSize != 0) && (mCache.size() > mTargetSize)) { - if (mTargetSize != 0) - { - target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); - if (target > (mLastSweep - 2)) - target = mLastSweep - 2; + target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); + if (target > (mLastSweep - 2)) + target = mLastSweep - 2; + Log(lsINFO, TaggedCachePartition) << mName << " is growing fast " << + mCache.size() << " of " << mTargetSize << + " aging at " << (mLastSweep - target) << " of " << mTargetAge; + } - Log(lsINFO, TaggedCachePartition) << mName << " is growing fast " << - mCache.size() << " of " << mTargetSize << - " aging at " << (mLastSweep - target) << " of " << mTargetAge; - } - else - target = mLastSweep - mTargetAge; - - cache_iterator cit = mCache.begin(); - while (cit != mCache.end()) - { - if (cit->second.first < target) + cache_iterator cit = mCache.begin(); + while (cit != mCache.end()) + { + if (!cit->second.ptr) + { // weak + if (cit->second.weak_ptr.expired()) { - ++cacheRemovals; + ++mapRemovals; mCache.erase(cit++); } else ++cit; } - } - - // Pass 2, remove dead objects from map - int mapRemovals = 0; - map_iterator mit = mMap.begin(); - while (mit != mMap.end()) - { - if (mit->second.expired()) - { - ++mapRemovals; - mMap.erase(mit++); - } else - ++mit; + { // strong + if (cit->second.last_use < target) + { // expired + --mCacheCount; + ++cacheRemovals; + cit->second.ptr.reset(); + if (cit->second.weak_ptr.expired()) + { + ++mapRemovals; + mCache.erase(cit++); + } + else + ++cit; + } + else + { + ++cc; + ++cit; + } + } } + assert(cc == mCacheCount); if (TaggedCachePartition.doLog(lsTRACE) && (mapRemovals || cacheRemovals)) Log(lsTRACE, TaggedCachePartition) << mName << ": cache = " << mCache.size() << "-" << cacheRemovals << - ", map = " << mMap.size() << "-" << mapRemovals; -} - -template void TaggedCache::visitAll(visitor_func func) -{ // Visits all tracked objects, removes selected objects - boost::recursive_mutex::scoped_lock sl(mLock); - - map_iterator mit = mMap.begin(); - while (mit != mMap.end()) - { - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - mMap.erase(mit++); // dead reference found - else if (func(mit->first, mit->second)) - { - mCache.erase(mit->first); - mMap.erase(mit++); - } - else - ++mit; - } -} - -template void TaggedCache::visitCached(visitor_func func) -{ // Visits all cached objects, uncaches selected objects - boost::recursive_mutex::scoped_lock sl(mLock); - - cache_iterator cit = mCache.begin(); - while (cit != mCache.end()) - { - if (func(cit->first, cit->second.second)) - mCache.erase(cit++); - else - ++cit; - } + ", map-=" << mapRemovals; } template bool TaggedCache::touch(const key_type& key) { // If present, make current in cache boost::recursive_mutex::scoped_lock sl(mLock); - // Is the object in the map? - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - return false; - if (mit->second.expired()) - { // in map, but expired - mMap.erase(mit); - return false; - } - - // Is the object in the cache? cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) - { // in both map and cache - cit->second.first = time(NULL); + if (cit == mCache.end()) // Don't have the object + return false; + cache_entry& entry = cit->second; + + if (entry.isCached()) + { + entry.touch(); return true; } - // In map but not cache, put in cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), data_ptr(cit->second.second)))); - return true; + entry.ptr = entry.lock(); + if (entry.isCached()) + { // We just put the object back in cache + ++mCacheCount; + entry.touch(); + return true; + } + + // Object fell out + mCache.erase(cit); + return false; } template bool TaggedCache::del(const key_type& key, bool valid) { // Remove from cache, if !valid, remove from map too. Returns true if removed from cache boost::recursive_mutex::scoped_lock sl(mLock); - if (!valid) - { // remove from map too - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) // not in map, cannot be in cache - return false; - mMap.erase(mit); - } - cache_iterator cit = mCache.find(key); if (cit == mCache.end()) return false; - mCache.erase(cit); - return true; + cache_entry& entry = cit->second; + + bool ret = false; + if (entry.isCached()) + { + --mCacheCount; + entry.ptr.reset(); + ret = true; + } + + if (!valid || entry.isExpired()) + mCache.erase(cit); + return true; } template @@ -259,40 +240,50 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared // Return values: true=we had the data already boost::recursive_mutex::scoped_lock sl(mLock); - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - { // not in map + cache_iterator cit = mCache.find(key); + if (cit == mCache.end()) + { mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); - mMap.insert(std::make_pair(key, data)); + ++mCacheCount; return false; } + cache_entry& entry = cit->second; + entry.touch(); - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - { // in map, but expired. Update in map, insert in cache - mit->second = data; - mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); + if (entry.isCached()) + { + if (replace) + { + entry.ptr = data; + entry.weak_ptr = data; + } + else + data = entry.ptr; return true; } - // in map and cache, canonicalize - if (replace) - mit->second = data; - else - data = cachedData; - - // Valid in map, is it in cache? - cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) + data_ptr cachedData = entry.lock(); + if (cachedData) { - cit->second.first = time(NULL); // Yes, refesh if (replace) - cit->second.second = data; + { + entry.ptr = data; + entry.weak_ptr = data; + } + else + { + entry.ptr = cachedData; + data = cachedData; + } + ++mCacheCount; + return true; } - else // no, add to cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); - return true; + entry.ptr = data; + entry.weak_ptr = data; + ++mCacheCount; + + return false; } template @@ -300,29 +291,23 @@ boost::shared_ptr TaggedCache::fetch(const key_type& key) { // fetch us a shared pointer to the stored data object boost::recursive_mutex::scoped_lock sl(mLock); - // Is it in the cache? cache_iterator cit = mCache.find(key); - if (cit != mCache.end()) + if (cit == mCache.end()) + return data_ptr(); + cache_entry& entry = cit->second; + entry.touch(); + + if (entry.isCached()) + return entry.ptr; + + entry.ptr = entry.lock(); + if (entry.isCached()) { - cit->second.first = time(NULL); // Yes, refresh - return cit->second.second; + ++mCacheCount; + return entry.ptr; } - - // Is it in the map? - map_iterator mit = mMap.find(key); - if (mit == mMap.end()) - return data_ptr(); // No, we're done - - data_ptr cachedData = mit->second.lock(); - if (!cachedData) - { // in map, but expired. Sorry, we don't have it - mMap.erase(mit); - return cachedData; - } - - // Put it back in the cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), cachedData))); - return cachedData; + mCache.erase(cit); + return data_ptr(); } template diff --git a/src/cpp/ripple/UniqueNodeList.cpp b/src/cpp/ripple/UniqueNodeList.cpp index 706183ced..49b3f23bb 100644 --- a/src/cpp/ripple/UniqueNodeList.cpp +++ b/src/cpp/ripple/UniqueNodeList.cpp @@ -64,15 +64,6 @@ void UniqueNodeList::start() // Load information about when we last updated. bool UniqueNodeList::miscLoad() { - BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) - { - RippleAddress a = RippleAddress::createNodePublic(node); - if (a.isValid()) - sClusterNodes.insert(a); - else - cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; - } - boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); @@ -105,13 +96,18 @@ bool UniqueNodeList::miscSave() void UniqueNodeList::trustedLoad() { - BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) + boost::regex rNode("\\`\\s*(\\S+)[\\s]*(.*)\\'"); + BOOST_FOREACH(const std::string& c, theConfig.CLUSTER_NODES) { - RippleAddress a = RippleAddress::createNodePublic(node); - if (a.isValid()) - sClusterNodes.insert(a); + boost::smatch match; + if (boost::regex_match(c, match, rNode)) + { + RippleAddress a = RippleAddress::createNodePublic(match[1]); + if (a.isValid()) + sClusterNodes.insert(std::make_pair(a, match[2])); + } else - cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; + cLog(lsWARNING) << "Entry in cluster list invalid: '" << c << "'"; } Database* db=theApp->getWalletDB()->getDB(); @@ -1731,7 +1727,17 @@ bool UniqueNodeList::nodeInUNL(const RippleAddress& naNodePublic) bool UniqueNodeList::nodeInCluster(const RippleAddress& naNodePublic) { boost::recursive_mutex::scoped_lock sl(mUNLLock); - return sClusterNodes.count(naNodePublic) != 0; + return sClusterNodes.end() != sClusterNodes.find(naNodePublic); +} + +bool UniqueNodeList::nodeInCluster(const RippleAddress& naNodePublic, std::string& name) +{ + boost::recursive_mutex::scoped_lock sl(mUNLLock); + std::map::iterator it = sClusterNodes.find(naNodePublic); + if (it == sClusterNodes.end()) + return false; + name = it->second; + return true; } // vim:ts=4 diff --git a/src/cpp/ripple/UniqueNodeList.h b/src/cpp/ripple/UniqueNodeList.h index fb662c532..bfd0af800 100644 --- a/src/cpp/ripple/UniqueNodeList.h +++ b/src/cpp/ripple/UniqueNodeList.h @@ -88,7 +88,7 @@ private: std::vector viReferrals; } scoreNode; - std::set sClusterNodes; + std::map sClusterNodes; typedef boost::unordered_map strIndex; typedef std::pair ipPort; @@ -155,6 +155,7 @@ public: bool nodeInUNL(const RippleAddress& naNodePublic); bool nodeInCluster(const RippleAddress& naNodePublic); + bool nodeInCluster(const RippleAddress& naNodePublic, std::string& name); void nodeBootstrap(); bool nodeLoad(boost::filesystem::path pConfig); diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index 5d34e59ec..84dbc8f14 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -300,8 +300,8 @@ void ValidationCollection::condWrite() void ValidationCollection::doWrite() { LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtDISK)); - static boost::format insVal("INSERT INTO LedgerValidations " - "(LedgerHash,NodePubKey,Flags,SignTime,Signature) VALUES ('%s','%s','%u','%u',%s);"); + static boost::format insVal("INSERT INTO Validations " + "(LedgerHash,NodePubKey,SignTime,RawData) VALUES ('%s','%s','%u',%s);"); boost::mutex::scoped_lock sl(mValidationLock); assert(mWriting); @@ -314,11 +314,16 @@ void ValidationCollection::doWrite() Database *db = theApp->getLedgerDB()->getDB(); ScopedLock dbl(theApp->getLedgerDB()->getDBLock()); + Serializer s(1024); db->executeSQL("BEGIN TRANSACTION;"); BOOST_FOREACH(const SerializedValidation::pointer& it, vector) + { + s.erase(); + it->add(s); db->executeSQL(boost::str(insVal % it->getLedgerHash().GetHex() - % it->getSignerPublic().humanNodePublic() % it->getFlags() % it->getSignTime() - % sqlEscape(it->getSignature()))); + % it->getSignerPublic().humanNodePublic() % it->getSignTime() + % sqlEscape(s.peekData()))); + } db->executeSQL("END TRANSACTION;"); } sl.lock(); diff --git a/src/js/README.md b/src/js/README.md new file mode 100644 index 000000000..7b357e08b --- /dev/null +++ b/src/js/README.md @@ -0,0 +1,3 @@ +# Ripple JavaScript library + +This library lets you connect to a ripple server via websockets. diff --git a/src/js/amount.js b/src/js/amount.js index b24ff0397..7614e4872 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -3,27 +3,18 @@ var sjcl = require('../../build/sjcl'); var bn = sjcl.bn; -var utils = require('./utils.js'); -var jsbn = require('./jsbn.js'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); -var BigInteger = jsbn.BigInteger; -var nbi = jsbn.nbi; +var BigInteger = jsbn.BigInteger; -var alphabets = { - 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", - 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", - 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -}; +var UInt160 = require('./uint160').UInt160, + Seed = require('./seed').Seed, + Currency = require('./currency').Currency; var consts = exports.consts = { - 'address_xns' : "rrrrrrrrrrrrrrrrrrrrrhoLvTp", - 'address_one' : "rrrrrrrrrrrrrrrrrrrrBZbvji", 'currency_xns' : 0, 'currency_one' : 1, - 'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"), - 'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"), - 'hex_xns' : "0000000000000000000000000000000000000000", - 'hex_one' : "0000000000000000000000000000000000000001", 'xns_precision' : 6, // BigInteger values prefixed with bi_. @@ -42,421 +33,8 @@ var consts = exports.consts = { 'cMinOffset' : -96, 'cMaxOffset' : 80, - - 'VER_NONE' : 1, - 'VER_NODE_PUBLIC' : 28, - 'VER_NODE_PRIVATE' : 32, - 'VER_ACCOUNT_ID' : 0, - 'VER_ACCOUNT_PUBLIC' : 35, - 'VER_ACCOUNT_PRIVATE' : 34, - 'VER_FAMILY_GENERATOR' : 41, - 'VER_FAMILY_SEED' : 33, }; -// --> input: big-endian array of bytes. -// <-- string at least as long as input. -var encode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_q = nbi(); - var bi_r = nbi(); - var bi_value = new BigInteger(input); - var buffer = []; - - while (bi_value.compareTo(BigInteger.ZERO) > 0) - { - bi_value.divRemTo(bi_base, bi_q, bi_r); - bi_q.copyTo(bi_value); - - buffer.push(alphabet[bi_r.intValue()]); - } - - var i; - - for (i = 0; i != input.length && !input[i]; i += 1) { - buffer.push(alphabet[0]); - } - - return buffer.reverse().join(""); -}; - -// --> input: String -// <-- array of bytes or undefined. -var decode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_value = nbi(); - var i; - - for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) - ; - - for (; i != input.length; i += 1) { - var v = alphabet.indexOf(input[i]); - - if (v < 0) - return undefined; - - var r = nbi(); - - r.fromInt(v); - - bi_value = bi_value.multiply(bi_base).add(r); - } - - // toByteArray: - // - Returns leading zeros! - // - Returns signed bytes! - var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var extra = 0; - - while (extra != bytes.length && !bytes[extra]) - extra += 1; - - if (extra) - bytes = bytes.slice(extra); - - var zeros = 0; - - while (zeros !== input.length && input[zeros] === alphabet[0]) - zeros += 1; - - return [].concat(utils.arraySet(zeros, 0), bytes); -}; - -var sha256 = function (bytes) { - return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); -}; - -var sha256hash = function (bytes) { - return sha256(sha256(bytes)); -}; - -// --> input: Array -// <-- String -var encode_base_check = function (version, input, alphabet) { - var buffer = [].concat(version, input); - var check = sha256(sha256(buffer)).slice(0, 4); - - return encode_base([].concat(buffer, check), alphabet); -} - -// --> input : String -// <-- NaN || BigInteger -var decode_base_check = function (version, input, alphabet) { - var buffer = decode_base(input, alphabet); - - if (!buffer || buffer[0] !== version || buffer.length < 5) - return NaN; - - var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); - var checksum = buffer.slice(-4); - var i; - - for (i = 0; i != 4; i += 1) - if (computed[i] !== checksum[i]) - return NaN; - - return new BigInteger(buffer.slice(1, -4)); -} - -// -// Seed support -// - -var Seed = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -Seed.json_rewrite = function (j) { - return Seed.from_json(j).to_json(); -}; - -// Return a new Seed from j. -Seed.from_json = function (j) { - return 'string' === typeof j - ? (new Seed()).parse_json(j) - : j.clone(); -}; - -Seed.is_valid = function (j) { - return Seed.from_json(j).is_valid(); -}; - -Seed.prototype.clone = function () { - return this.copyTo(new Seed()); -}; - -// Returns copy. -Seed.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Seed.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -Seed.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -// One day this will support rfc1751 too. -Seed.prototype.parse_json = function (j) { - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "s") { - this._value = decode_base_check(consts.VER_FAMILY_SEED, j); - } - else if (16 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 128); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -Seed.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_FAMILY_SEED, array); - - return output; -}; - -// -// UInt160 support -// - -var UInt160 = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -UInt160.json_rewrite = function (j) { - return UInt160.from_json(j).to_json(); -}; - -// Return a new UInt160 from j. -UInt160.from_generic = function (j) { - return 'string' === typeof j - ? (new UInt160()).parse_generic(j) - : j.clone(); -}; - -// Return a new UInt160 from j. -UInt160.from_json = function (j) { - if ('string' === typeof j) { - return (new UInt160()).parse_json(j); - } else if (j instanceof UInt160) { - return j.clone(); - } else { - return new UInt160(); - } -}; - -UInt160.is_valid = function (j) { - return UInt160.from_json(j).is_valid(); -}; - -UInt160.prototype.clone = function () { - return this.copyTo(new UInt160()); -}; - -// Returns copy. -UInt160.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -UInt160.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -UInt160.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -UInt160.prototype.parse_generic = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - switch (j) { - case undefined: - case "0": - case consts.address_xns: - case consts.uint160_xns: - case consts.hex_xns: - this._value = nbi(); - break; - - case "1": - case consts.address_one: - case consts.uint160_one: - case consts.hex_one: - this._value = new BigInteger([1]); - - break; - - default: - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else if (20 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 256); - } - else if (40 === j.length) { - // XXX Check char set! - this._value = new BigInteger(j, 16); - } - else { - this._value = NaN; - } - } - - return this; -}; - -// value = NaN on error. -UInt160.prototype.parse_json = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. -UInt160.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_ACCOUNT_ID, array); - - return output; -}; - -// -// Currency support -// - -// XXX Internal form should be UInt160. -var Currency = function () { - // Internal form: 0 = XRP. 3 letter-code. - // XXX Internal should be 0 or hex with three letter annotation when valid. - - // Json form: - // '', 'XRP', '0': 0 - // 3-letter code: ... - // XXX Should support hex, C++ doesn't currently allow it. - - this._value = NaN; -} - -// Given "USD" return the json. -Currency.json_rewrite = function (j) { - return Currency.from_json(j).to_json(); -}; - -Currency.from_json = function (j) { - return 'string' === typeof j - ? (new Currency()).parse_json(j) - : j.clone(); -}; - -Currency.is_valid = function (j) { - return currency.from_json(j).is_valid(); -}; - -Currency.prototype.clone = function() { - return this.copyTo(new Currency()); -}; - -// Returns copy. -Currency.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Currency.prototype.equals = function (d) { - return ('string' !== typeof this._value && isNaN(this._value)) - || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; -}; - -// this._value = NaN on error. -Currency.prototype.parse_json = function (j) { - if ("" === j || "0" === j || "XRP" === j) { - this._value = 0; - } - else if ('string' != typeof j || 3 !== j.length) { - this._value = NaN; - } - else { - this._value = j; - } - - return this; -}; - -Currency.prototype.is_native = function () { - return !isNaN(this._value) && !this._value; -}; - -Currency.prototype.is_valid = function () { - return !isNaN(this._value); -}; - -Currency.prototype.to_json = function () { - return this._value ? this._value : "XRP"; -}; - -Currency.prototype.to_human = function () { - return this._value ? this._value : "XRP"; -}; // // Amount class in the style of Java's BigInteger class @@ -505,9 +83,9 @@ Amount.is_valid_full = function (j) { Amount.NaN = function () { var result = new Amount(); - + result._value = NaN; - + return result; }; @@ -1305,10 +883,10 @@ Amount.prototype.not_equals_why = function (d) { }; exports.Amount = Amount; + +// DEPRECATED: Include the corresponding files instead. exports.Currency = Currency; exports.Seed = Seed; exports.UInt160 = UInt160; -exports.config = {}; - // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/base.js b/src/js/base.js new file mode 100644 index 000000000..47ef551ed --- /dev/null +++ b/src/js/base.js @@ -0,0 +1,136 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = {}; + +var alphabets = Base.alphabets = { + 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", + 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", + 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +}; + +extend(Base, { + 'VER_NONE' : 1, + 'VER_NODE_PUBLIC' : 28, + 'VER_NODE_PRIVATE' : 32, + 'VER_ACCOUNT_ID' : 0, + 'VER_ACCOUNT_PUBLIC' : 35, + 'VER_ACCOUNT_PRIVATE' : 34, + 'VER_FAMILY_GENERATOR' : 41, + 'VER_FAMILY_SEED' : 33 +}); + +var sha256 = function (bytes) { + return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); +}; + +var sha256hash = function (bytes) { + return sha256(sha256(bytes)); +}; + +// --> input: big-endian array of bytes. +// <-- string at least as long as input. +Base.encode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_q = nbi(); + var bi_r = nbi(); + var bi_value = new BigInteger(input); + var buffer = []; + + while (bi_value.compareTo(BigInteger.ZERO) > 0) + { + bi_value.divRemTo(bi_base, bi_q, bi_r); + bi_q.copyTo(bi_value); + + buffer.push(alphabet[bi_r.intValue()]); + } + + var i; + + for (i = 0; i != input.length && !input[i]; i += 1) { + buffer.push(alphabet[0]); + } + + return buffer.reverse().join(""); +}; + +// --> input: String +// <-- array of bytes or undefined. +Base.decode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_value = nbi(); + var i; + + for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) + ; + + for (; i != input.length; i += 1) { + var v = alphabet.indexOf(input[i]); + + if (v < 0) + return undefined; + + var r = nbi(); + + r.fromInt(v); + + bi_value = bi_value.multiply(bi_base).add(r); + } + + // toByteArray: + // - Returns leading zeros! + // - Returns signed bytes! + var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var extra = 0; + + while (extra != bytes.length && !bytes[extra]) + extra += 1; + + if (extra) + bytes = bytes.slice(extra); + + var zeros = 0; + + while (zeros !== input.length && input[zeros] === alphabet[0]) + zeros += 1; + + return [].concat(utils.arraySet(zeros, 0), bytes); +}; + +// --> input: Array +// <-- String +Base.encode_check = function (version, input, alphabet) { + var buffer = [].concat(version, input); + var check = sha256(sha256(buffer)).slice(0, 4); + + return Base.encode([].concat(buffer, check), alphabet); +} + +// --> input : String +// <-- NaN || BigInteger +Base.decode_check = function (version, input, alphabet) { + var buffer = Base.decode(input, alphabet); + + if (!buffer || buffer[0] !== version || buffer.length < 5) + return NaN; + + var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); + var checksum = buffer.slice(-4); + var i; + + for (i = 0; i != 4; i += 1) + if (computed[i] !== checksum[i]) + return NaN; + + return new BigInteger(buffer.slice(1, -4)); +} + +exports.Base = Base; diff --git a/src/js/config.js b/src/js/config.js new file mode 100644 index 000000000..372b7d261 --- /dev/null +++ b/src/js/config.js @@ -0,0 +1,3 @@ +// This object serves as a singleton to store config options + +module.exports = {}; diff --git a/src/js/currency.js b/src/js/currency.js new file mode 100644 index 000000000..14e902159 --- /dev/null +++ b/src/js/currency.js @@ -0,0 +1,81 @@ + +// +// Currency support +// + +// XXX Internal form should be UInt160. +var Currency = function () { + // Internal form: 0 = XRP. 3 letter-code. + // XXX Internal should be 0 or hex with three letter annotation when valid. + + // Json form: + // '', 'XRP', '0': 0 + // 3-letter code: ... + // XXX Should support hex, C++ doesn't currently allow it. + + this._value = NaN; +} + +// Given "USD" return the json. +Currency.json_rewrite = function (j) { + return Currency.from_json(j).to_json(); +}; + +Currency.from_json = function (j) { + return 'string' === typeof j + ? (new Currency()).parse_json(j) + : j.clone(); +}; + +Currency.is_valid = function (j) { + return Currency.from_json(j).is_valid(); +}; + +Currency.prototype.clone = function() { + return this.copyTo(new Currency()); +}; + +// Returns copy. +Currency.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Currency.prototype.equals = function (d) { + return ('string' !== typeof this._value && isNaN(this._value)) + || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; +}; + +// this._value = NaN on error. +Currency.prototype.parse_json = function (j) { + if ("" === j || "0" === j || "XRP" === j) { + this._value = 0; + } + else if ('string' != typeof j || 3 !== j.length) { + this._value = NaN; + } + else { + this._value = j; + } + + return this; +}; + +Currency.prototype.is_native = function () { + return !isNaN(this._value) && !this._value; +}; + +Currency.prototype.is_valid = function () { + return !isNaN(this._value); +}; + +Currency.prototype.to_json = function () { + return this._value ? this._value : "XRP"; +}; + +Currency.prototype.to_human = function () { + return this._value ? this._value : "XRP"; +}; + +exports.Currency = Currency; diff --git a/src/js/remote.js b/src/js/remote.js index 1b1f67591..23c2296e8 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -22,6 +22,7 @@ var UInt160 = require('./amount').UInt160; var Transaction = require('./transaction').Transaction; var utils = require('./utils'); +var config = require('./config'); // Request events emitted: // 'success' : Request successful. @@ -205,7 +206,7 @@ var Remote = function (opts, trace) { this._testnet = undefined; this._transaction_subs = 0; this.online_target = false; - this.online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' + this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' this.state = 'offline'; // 'online', 'offline' this.retry_timer = undefined; this.retry = undefined; @@ -245,7 +246,7 @@ var Remote = function (opts, trace) { this.on('newListener', function (type, listener) { if ('transaction' === type) { - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_subscribe([ 'transactions' ]) .request(); @@ -260,7 +261,7 @@ var Remote = function (opts, trace) { { self._transaction_subs -= 1; - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_unsubscribe([ 'transactions' ]) .request(); @@ -272,12 +273,12 @@ var Remote = function (opts, trace) { Remote.prototype = new EventEmitter; Remote.from_config = function (obj, trace) { - var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj; + var serverConfig = 'string' === typeof obj ? config.servers[obj] : obj; var remote = new Remote(serverConfig, trace); - for (var account in exports.config.accounts) { - var accountInfo = exports.config.accounts[account]; + for (var account in config.accounts) { + var accountInfo = config.accounts[account]; if ("object" === typeof accountInfo) { if (accountInfo.secret) { // Index by nickname ... @@ -327,12 +328,12 @@ Remote.prototype._set_state = function (state) { switch (state) { case 'online': - this.online_state = 'open'; + this._online_state = 'open'; this.emit('connected'); break; case 'offline': - this.online_state = 'closed'; + this._online_state = 'closed'; this.emit('disconnected'); break; } @@ -353,7 +354,7 @@ Remote.prototype.connect = function (online) { this.online_target = target; // If we were in a stable state, go dynamic. - switch (this.online_state) { + switch (this._online_state) { case 'open': if (!target) this._connect_stop(); break; @@ -390,9 +391,9 @@ Remote.prototype._connect_retry = function () { // Do not continue trying to connect. this._set_state('offline'); } - else if ('connecting' !== this.online_state) { + else if ('connecting' !== this._online_state) { // New to connecting state. - this.online_state = 'connecting'; + this._online_state = 'connecting'; this.retry = 0; this._set_state('offline'); // Report newly offline. @@ -850,6 +851,17 @@ Remote.prototype.request_wallet_accounts = function (seed) { return request; }; +Remote.prototype.request_sign = function (secret, tx_json) { + utils.assert(this.trusted); // Don't send secrets. + + var request = new Request(this, 'sign'); + + request.message.secret = secret; + request.message.tx_json = tx_json; + + return request; +}; + // Submit a transaction. Remote.prototype.submit = function (transaction) { var self = this; @@ -915,7 +927,12 @@ Remote.prototype.submit = function (transaction) { Remote.prototype._server_subscribe = function () { var self = this; - this.request_subscribe([ 'ledger', 'server' ]) + var feeds = [ 'ledger', 'server' ]; + + if (this._transaction_subs) + feeds.push('transactions'); + + this.request_subscribe(feeds) .on('success', function (message) { self._stand_alone = !!message.stand_alone; self._testnet = !!message.testnet; @@ -1128,7 +1145,9 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, request.message.source_account = UInt160.json_rewrite(src_account); request.message.destination_account = UInt160.json_rewrite(dst_account); request.message.destination_amount = Amount.json_rewrite(dst_amount); - request.message.source_currencies = source_currencies.map(function (ci) { + + if (source_currencies) { + request.message.source_currencies = source_currencies.map(function (ci) { var ci_new = {}; if ('issuer' in ci) @@ -1139,6 +1158,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, return ci_new; }); + } return request; }; @@ -1186,7 +1206,6 @@ Remote.prototype.transaction = function () { return new Transaction(this); }; -exports.config = {}; exports.Remote = Remote; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/seed.js b/src/js/seed.js new file mode 100644 index 000000000..ee519566c --- /dev/null +++ b/src/js/seed.js @@ -0,0 +1,129 @@ +// +// Seed support +// + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; + +var Base = require('./base').Base, + UInt256 = require('./uint256').UInt256; + +var Seed = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +Seed.json_rewrite = function (j) { + return Seed.from_json(j).to_json(); +}; + +// Return a new Seed from j. +Seed.from_json = function (j) { + return (j instanceof Seed) + ? j.clone() + : (new Seed()).parse_json(j); +}; + +Seed.is_valid = function (j) { + return Seed.from_json(j).is_valid(); +}; + +Seed.prototype.clone = function () { + return this.copyTo(new Seed()); +}; + +// Returns copy. +Seed.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Seed.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +Seed.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +// One day this will support rfc1751 too. +Seed.prototype.parse_json = function (j) { + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "s") { + this._value = Base.decode_check(Base.VER_FAMILY_SEED, j); + } + else if (16 === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 128); + } + else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +Seed.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var target = 20; + + // XXX Make sure only trim off leading zeros. + var array = bytes.length < target + ? bytes.length + ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) + : utils.arraySet(target, 0) + : bytes.slice(target - bytes.length); + var output = Base.encode_check(Base.VER_FAMILY_SEED, array); + + return output; +}; + +function append_int(a, i) { + return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff); +} + +function firstHalfOfSHA512(bytes) { + return sjcl.bitArray.bitSlice( + sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)), + 0, 256 + ); +} + +function SHA256_RIPEMD160(bits) { + return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); +} + +Seed.prototype.generate_private = function (account_id) { + // XXX If account_id is given, should loop over keys until we find the right one + + var seq = 0; + + var private_gen, public_gen, i = 0; + do { + private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen)); + + public_gen = sjcl.ecc.curves.c256.G.mult(private_gen); + + var sec; + i = 0; + do { + sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(sec)); + + return UInt256.from_bn(sec); +}; + +exports.Seed = Seed; diff --git a/src/js/serializedobject.js b/src/js/serializedobject.js new file mode 100644 index 000000000..e66a2001a --- /dev/null +++ b/src/js/serializedobject.js @@ -0,0 +1,144 @@ +var binformat = require('./binformat'), + sjcl = require('../../build/sjcl'), + extend = require('extend'), + stypes = require('./serializedtypes'); + +var UInt256 = require('./uint256').UInt256; + +var SerializedObject = function () { + this.buffer = []; + this.pointer = 0; +}; + +SerializedObject.from_json = function (obj) { + var typedef; + var so = new SerializedObject(); + + // Create a copy of the object so we don't modify it + obj = extend({}, obj); + + if ("number" === typeof obj.TransactionType) { + obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); + + if (!obj.TransactionType) { + throw new Error("Transaction type ID is invalid."); + } + } + + if ("string" === typeof obj.TransactionType) { + typedef = binformat.tx[obj.TransactionType].slice(); + + obj.TransactionType = typedef.shift(); + } else if ("undefined" !== typeof obj.LedgerEntryType) { + // XXX: TODO + throw new Error("Ledger entry binary format not yet implemented."); + } else throw new Error("Object to be serialized must contain either " + + "TransactionType or LedgerEntryType."); + + so.serialize(typedef, obj); + + return so; +}; + +SerializedObject.prototype.append = function (bytes) { + this.buffer = this.buffer.concat(bytes); + this.pointer += bytes.length; +}; + +SerializedObject.prototype.to_bits = function () +{ + return sjcl.codec.bytes.toBits(this.buffer); +}; + +SerializedObject.prototype.to_hex = function () { + return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase(); +}; + +SerializedObject.prototype.serialize = function (typedef, obj) +{ + // Ensure canonical order + typedef = SerializedObject._sort_typedef(typedef.slice()); + + // Serialize fields + for (var i = 0, l = typedef.length; i < l; i++) { + var spec = typedef[i]; + this.serialize_field(spec, obj); + } +}; + +SerializedObject.prototype.signing_hash = function (prefix) +{ + var sign_buffer = new SerializedObject(); + stypes.Int32.serialize(sign_buffer, prefix); + sign_buffer.append(this.buffer); + return sign_buffer.hash_sha512_half(); +}; + +SerializedObject.prototype.hash_sha512_half = function () +{ + var bits = sjcl.codec.bytes.toBits(this.buffer), + hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256); + + return UInt256.from_hex(sjcl.codec.hex.fromBits(hash)); +}; + +SerializedObject.prototype.serialize_field = function (spec, obj) +{ + spec = spec.slice(); + + var name = spec.shift(), + presence = spec.shift(), + field_id = spec.shift(), + Type = spec.shift(); + + if ("undefined" !== typeof obj[name]) { + console.log(name, Type.id, field_id); + this.append(SerializedObject.get_field_header(Type.id, field_id)); + + try { + Type.serialize(this, obj[name]); + } catch (e) { + // Add field name to message and rethrow + e.message = "Error serializing '"+name+"': "+e.message; + throw e; + } + } else if (presence === binformat.REQUIRED) { + throw new Error('Missing required field '+name); + } +}; + +SerializedObject.get_field_header = function (type_id, field_id) +{ + var buffer = [0]; + if (type_id > 0xf) buffer.push(type_id & 0xff); + else buffer[0] += (type_id & 0xf) << 4; + + if (field_id > 0xf) buffer.push(field_id & 0xff); + else buffer[0] += field_id & 0xf; + + return buffer; +}; + +function sort_field_compare(a, b) { + // Sort by type id first, then by field id + return a[3].id !== b[3].id ? + a[3].id - b[3].id : + a[2] - b[2]; +}; +SerializedObject._sort_typedef = function (typedef) { + return typedef.sort(sort_field_compare); +}; + +SerializedObject.lookup_type_tx = function (id) { + for (var i in binformat.tx) { + if (!binformat.tx.hasOwnProperty(i)) continue; + + if (binformat.tx[i][0] === id) { + return i; + } + } + + return null; +}; + +exports.SerializedObject = SerializedObject; diff --git a/src/js/serializedtypes.js b/src/js/serializedtypes.js index 11acd294f..2c61b64b3 100644 --- a/src/js/serializedtypes.js +++ b/src/js/serializedtypes.js @@ -1,10 +1,55 @@ -var SerializedType = function () { +/** + * Type definitions for binary format. + * + * This file should not be included directly. Instead, find the format you're + * trying to parse or serialize in binformat.js and pass that to + * SerializedObject.parse() or SerializedObject.serialize(). + */ +var extend = require('extend'), + utils = require('./utils'), + sjcl = require('../../build/sjcl'); + +var amount = require('./amount'), + UInt160 = amount.UInt160, + Amount = amount.Amount; + +// Shortcuts +var hex = sjcl.codec.hex, + bytes = sjcl.codec.bytes; + +var SerializedType = function (methods) { + extend(this, methods); +}; + +SerializedType.prototype.serialize_hex = function (so, hexData) { + var byteData = bytes.fromBits(hex.toBits(hexData)); + this.serialize_varint(so, byteData.length); + so.append(byteData); +}; + +SerializedType.prototype.serialize_varint = function (so, val) { + if (val < 0) { + throw new Error("Variable integers are unsigned."); + } + if (val <= 192) { + so.append([val]); + } else if (val <= 12,480) { + val -= 193; + so.append([193 + (val >>> 8), val & 0xff]); + } else if (val <= 918744) { + val -= 12481; + so.append([ + 241 + (val >>> 16), + val >>> 8 & 0xff, + val & 0xff + ]); + } else throw new Error("Variable integer overflow."); }; exports.Int8 = new SerializedType({ serialize: function (so, val) { - return so.append([val & 0xff]); + so.append([val & 0xff]); }, parse: function (so) { return so.read(1)[0]; @@ -13,7 +58,10 @@ exports.Int8 = new SerializedType({ exports.Int16 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -22,7 +70,12 @@ exports.Int16 = new SerializedType({ exports.Int32 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 24 & 0xff, + val >>> 16 & 0xff, + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -67,7 +120,62 @@ exports.Hash160 = new SerializedType({ exports.Amount = new SerializedType({ serialize: function (so, val) { - // XXX + var amount = Amount.from_json(val); + if (!amount.is_valid()) { + throw new Error("Not a valid Amount object."); + } + + // Amount (64-bit integer) + if (amount.is_native()) { + var valueHex = amount._value.toString(16); + + // Enforce correct length (64 bits) + if (valueHex.length > 16) { + throw new Error('Value out of bounds'); + } + while (valueHex.length < 16) { + valueHex = "0" + valueHex; + } + + var valueBytes = bytes.fromBits(hex.toBits(valueHex)); + // Clear most significant two bits - these bits should already be 0 if + // Amount enforces the range correctly, but we'll clear them anyway just + // so this code can make certain guarantees about the encoded value. + valueBytes[0] &= 0x3f; + if (!amount.is_negative()) valueBytes[0] |= 0x40; + + so.append(valueBytes); + } else { + // XXX + throw new Error("Non-native amounts not implemented!"); + } + + if (!amount.is_native()) { + // Currency (160-bit hash) + var currency = amount.currency().to_json(); + if ("string" === typeof currency && currency.length === 3) { + var currencyCode = currency.toUpperCase(), + currencyData = utils.arraySet(20, 0); + + if (!/^[A-Z]{3}$/.test(currencyCode)) { + throw new Error('Invalid currency code'); + } + + currencyData[12] = currencyCode.charCodeAt(0) & 0xff; + currencyData[13] = currencyCode.charCodeAt(1) & 0xff; + currencyData[14] = currencyCode.charCodeAt(2) & 0xff; + + var currencyBits = bytes.toBits(currencyData), + currencyHash = sjcl.hash.ripemd160.hash(currencyBits); + + so.append(bytes.fromBits(currencyHash)); + } else { + throw new Error('Tried to serialize invalid/unimplemented currency type.'); + } + + // Issuer (160-bit hash) + // XXX + } }, parse: function (so) { // XXX @@ -76,7 +184,8 @@ exports.Amount = new SerializedType({ exports.VariableLength = new SerializedType({ serialize: function (so, val) { - // XXX + if ("string" === typeof val) this.serialize_hex(so, val); + else throw new Error("Unknown datatype."); }, parse: function (so) { // XXX @@ -85,7 +194,8 @@ exports.VariableLength = new SerializedType({ exports.Account = new SerializedType({ serialize: function (so, val) { - // XXX + var account = UInt160.from_json(val); + this.serialize_hex(so, account.to_hex()); }, parse: function (so) { // XXX diff --git a/src/js/serializer.js b/src/js/serializer.js deleted file mode 100644 index 6a75e84ef..000000000 --- a/src/js/serializer.js +++ /dev/null @@ -1,44 +0,0 @@ -// - -var serializer = {}; - -serializer.addUInt16 = function(value) { - switch (typeof value) { - case 'string': - addUInt16(value.charCodeAt(0)); - break; - - case 'integer': - for (i = 16/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.addUInt160 = function(value) { - switch (typeof value) { - case 'array': - raw.concat(value); - break; - - case 'integer': - for (i = 160/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.getSHA512Half = function() { -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/js/sjcl-custom/sjcl-ecdsa-der.js b/src/js/sjcl-custom/sjcl-ecdsa-der.js new file mode 100644 index 000000000..befddbf08 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ecdsa-der.js @@ -0,0 +1,28 @@ +sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) { + return this.encodeDER(this.sign(hash, paranoia)); +}; + +sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) { + var w = sjcl.bitArray, + R = this._curve.r, + l = R.bitLength(), + r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)).toBits(), + s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)).toBits(); + + var rb = sjcl.codec.bytes.fromBits(r), + sb = sjcl.codec.bytes.fromBits(s); + + var buffer = [].concat( + 0x30, + 4 + rb.length + sb.length, + 0x02, + rb.length, + rb, + 0x02, + sb.length, + sb + ); + + return sjcl.codec.bytes.toBits(buffer); +}; + diff --git a/src/js/sjcl-custom/sjcl-extramath.js b/src/js/sjcl-custom/sjcl-extramath.js new file mode 100755 index 000000000..4dd5f326a --- /dev/null +++ b/src/js/sjcl-custom/sjcl-extramath.js @@ -0,0 +1,61 @@ +sjcl.bn.ZERO = new sjcl.bn(0); + +/** [ this / that , this % that ] */ +sjcl.bn.prototype.divRem = function (that) { + if (typeof(that) !== "object") { that = new this._class(that); } + var thisa = this.abs(), thata = that.abs(), quot = new this._class(0), + ci = 0; + if (!thisa.greaterEquals(thata)) { + this.initWith(0); + return this; + } else if (thisa.equals(thata)) { + this.initWith(sign); + return this; + } + + for (; thisa.greaterEquals(thata); ci++) { + thata.doubleM(); + } + for (; ci > 0; ci--) { + quot.doubleM(); + thata.halveM(); + if (thisa.greaterEquals(thata)) { + quot.addM(1); + thisa.subM(that).normalize(); + } + } + return [quot, thisa]; +}; + +/** this /= that (rounded to nearest int) */ +sjcl.bn.prototype.divRound = function (that) { + var dr = this.divRem(that), quot = dr[0], rem = dr[1]; + + if (rem.doubleM().greaterEquals(that)) { + quot.addM(1); + } + + return quot; +}; + +/** this /= that (rounded down) */ +sjcl.bn.prototype.div = function (that) { + var dr = this.divRem(that); + return dr[0]; +}; + +sjcl.bn.prototype.sign = function () { + return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1; + }; + +/** -this */ +sjcl.bn.prototype.neg = function () { + return sjcl.bn.ZERO.sub(this); +}; + +/** |this| */ +sjcl.bn.prototype.abs = function () { + if (this.sign() === -1) { + return this.neg(); + } else return this; +}; diff --git a/src/js/sjcl-custom/sjcl-ripemd160.js b/src/js/sjcl-custom/sjcl-ripemd160.js new file mode 100755 index 000000000..4ee7b9246 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ripemd160.js @@ -0,0 +1,207 @@ +/** @fileOverview Javascript RIPEMD-160 implementation. + * + * @author Artem S Vybornov + */ +(function() { + +/** + * Context for a RIPEMD-160 operation in progress. + * @constructor + * @class RIPEMD, 160 bits. + */ +sjcl.hash.ripemd160 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.ripemd160.hash = function (data) { + return (new sjcl.hash.ripemd160()).update(data).finalize(); +}; + +sjcl.hash.ripemd160.prototype = { + /** + * Reset the hash state. + * @return this + */ + reset: function () { + this._h = _h0.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Reset the hash state. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if ( typeof data === "string" ) + data = sjcl.codec.utf8String.toBits(data); + + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 512+ol & -512; i <= nl; i+= 512) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ + finalize: function () { + var b = sjcl.bitArray.concat( this._buffer, [ sjcl.bitArray.partial(1,1) ] ), + l = ( this._length + 1 ) % 512, + z = ( l > 448 ? 512 : 448 ) - l % 448, + zp = z % 32; + + if ( zp > 0 ) + b = sjcl.bitArray.concat( b, [ sjcl.bitArray.partial(zp,0) ] ) + for ( ; z >= 32; z -= 32 ) + b.push(0); + + b.push( _cvt( this._length | 0 ) ); + b.push( _cvt( Math.floor(this._length / 0x100000000) ) ); + + while ( b.length ) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + var h = this._h; + this.reset(); + + for ( var w = 0; w < 5; ++w ) + h[w] = _cvt(h[w]); + + return h; + } +}; + +var _h0 = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + +var _k1 = [ 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e ]; +var _k2 = [ 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000 ]; +for ( var i = 4; i >= 0; --i ) { + for ( var j = 1; j < 16; ++j ) { + _k1.splice(i,0,_k1[i]); + _k2.splice(i,0,_k2[i]); + } +} + +var _r1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 ]; +var _r2 = [ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 ]; + +var _s1 = [ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]; +var _s2 = [ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]; + +function _f0(x,y,z) { + return x ^ y ^ z; +}; + +function _f1(x,y,z) { + return (x & y) | (~x & z); +}; + +function _f2(x,y,z) { + return (x | ~y) ^ z; +}; + +function _f3(x,y,z) { + return (x & z) | (y & ~z); +}; + +function _f4(x,y,z) { + return x ^ (y | ~z); +}; + +function _rol(n,l) { + return (n << l) | (n >>> (32-l)); +} + +function _cvt(n) { + return ( (n & 0xff << 0) << 24 ) + | ( (n & 0xff << 8) << 8 ) + | ( (n & 0xff << 16) >>> 8 ) + | ( (n & 0xff << 24) >>> 24 ); +} + +function _block(X) { + var A1 = this._h[0], B1 = this._h[1], C1 = this._h[2], D1 = this._h[3], E1 = this._h[4], + A2 = this._h[0], B2 = this._h[1], C2 = this._h[2], D2 = this._h[3], E2 = this._h[4]; + + var j = 0, T; + + for ( ; j < 16; ++j ) { + T = _rol( A1 + _f0(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f4(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 32; ++j ) { + T = _rol( A1 + _f1(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f3(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 48; ++j ) { + T = _rol( A1 + _f2(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f2(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 64; ++j ) { + T = _rol( A1 + _f3(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f1(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 80; ++j ) { + T = _rol( A1 + _f4(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f0(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + + T = this._h[1] + C1 + D2; + this._h[1] = this._h[2] + D1 + E2; + this._h[2] = this._h[3] + E1 + A2; + this._h[3] = this._h[4] + A1 + B2; + this._h[4] = this._h[0] + B1 + C2; + this._h[0] = T; +} + +})(); diff --git a/src/js/sjcl-custom/sjcl-secp256k1.js b/src/js/sjcl-custom/sjcl-secp256k1.js new file mode 100755 index 000000000..4d34db7f1 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-secp256k1.js @@ -0,0 +1,72 @@ +// ----- for secp256k1 ------ + +// Overwrite NIST-P256 with secp256k1 so we're on even footing +sjcl.ecc.curves.c256 = new sjcl.ecc.curve( + sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]), + "0x14551231950b75fc4402da1722fc9baee", + 0, + 7, + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" +); + +// Replace point addition and doubling algorithms +// NIST-P256 is a=-3, we need algorithms for a=0 +sjcl.ecc.pointJac.prototype.add = function(T) { + var S = this; + if (S.curve !== T.curve) { + throw("sjcl.ecc.add(): Points must be on the same curve to add them!"); + } + + if (S.isIdentity) { + return T.toJac(); + } else if (T.isIdentity) { + return S; + } + + var z1z1 = S.z.square(); + var h = T.x.mul(z1z1).subM(S.x); + var s2 = T.y.mul(S.z).mul(z1z1); + + if (h.equals(0)) { + if (S.y.equals(T.y.mul(z1z1.mul(S.z)))) { + // same point + return S.doubl(); + } else { + // inverses + return new sjcl.ecc.pointJac(S.curve); + } + } + + var hh = h.square(); + var i = hh.copy().doubleM().doubleM(); + var j = h.mul(i); + var r = s2.sub(S.y).doubleM(); + var v = S.x.mul(i); + + var x = r.square().subM(j).subM(v.copy().doubleM()); + var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM()); + var z = S.z.add(h).square().subM(z1z1).subM(hh); + + return new sjcl.ecc.pointJac(this.curve,x,y,z); +}; + +sjcl.ecc.pointJac.prototype.doubl = function () { + if (this.isIdentity) { return this; } + + var a = this.x.square(); + var b = this.y.square(); + var c = b.square(); + var d = this.x.add(b).square().subM(a).subM(c).doubleM(); + var e = a.mul(3); + var f = e.square(); + var x = f.sub(d.copy().doubleM()); + var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM()); + var z = this.y.mul(this.z).doubleM(); + return new sjcl.ecc.pointJac(this.curve, x, y, z); +}; + +sjcl.ecc.point.prototype.toBytesCompressed = function () { + var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03; + return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits())) +}; diff --git a/src/js/transaction.js b/src/js/transaction.js index 3fedb3dca..a2ad368a3 100644 --- a/src/js/transaction.js +++ b/src/js/transaction.js @@ -43,10 +43,16 @@ // - may or may not forward. // -var Amount = require('./amount').Amount; -var Currency = require('./amount').Currency; -var UInt160 = require('./amount').UInt160; -var EventEmitter = require('events').EventEmitter; +var sjcl = require('../../build/sjcl'); + +var Amount = require('./amount').Amount; +var Currency = require('./amount').Currency; +var UInt160 = require('./amount').UInt160; +var Seed = require('./seed').Seed; +var EventEmitter = require('events').EventEmitter; +var SerializedObject = require('./serializedobject').SerializedObject; + +var config = require('./config'); var SUBMIT_MISSING = 4; // Report missing. var SUBMIT_LOST = 8; // Give up tracking. @@ -112,6 +118,11 @@ Transaction.flags = { }, }; +Transaction.formats = require('./binformat').tx; + +Transaction.HASH_SIGN = 0x53545800; +Transaction.HASH_SIGN_TESTNET = 0x73747800; + Transaction.prototype.consts = { 'telLOCAL_ERROR' : -399, 'temMALFORMED' : -299, @@ -156,6 +167,30 @@ Transaction.prototype.set_state = function (state) { } }; +Transaction.prototype.serialize = function () { + return SerializedObject.from_json(this.tx_json); +}; + +Transaction.prototype.signing_hash = function () { + var prefix = config.testnet + ? Transaction.HASH_SIGN_TESTNET + : Transaction.HASH_SIGN; + + return SerializedObject.from_json(this.tx_json).signing_hash(prefix); +}; + +Transaction.prototype.sign = function () { + var seed = Seed.from_json(this._secret), + priv = seed.generate_private(this.tx_json.Account), + hash = this.signing_hash(); + + var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()), + sig = key.signDER(hash.to_bits(), 0), + hex = sjcl.codec.hex.fromBits(sig).toUpperCase(); + + this.tx_json.TxnSignature = hex; +}; + // Submit a transaction to the network. // XXX Don't allow a submit without knowing ledger_index. // XXX Have a network canSubmit(), post events for following. @@ -355,21 +390,21 @@ Transaction.prototype.transfer_rate = function (rate) { // --> flags: undefined, _flag_, or [ _flags_ ] Transaction.prototype.set_flags = function (flags) { if (flags) { - var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; + var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction. this.tx_json.Flags = 0; - var flag_set = 'object' === typeof flags ? flags : [ flags ]; + var flag_set = 'object' === typeof flags ? flags : [ flags ]; - for (index in flag_set) { - var flag = flag_set[index]; + for (var index in flag_set) { + if (!flag_set.hasOwnProperty(index)) continue; - if (flag in transaction_flags) - { - this.tx_json.Flags += transaction_flags[flag]; - } - else { + var flag = flag_set[index]; + + if (flag in transaction_flags) { + this.tx_json.Flags += transaction_flags[flag]; + } else { // XXX Immediately report an error or mark it. } } diff --git a/src/js/uint.js b/src/js/uint.js new file mode 100644 index 000000000..b989b7e84 --- /dev/null +++ b/src/js/uint.js @@ -0,0 +1,221 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = require('./base').Base; + +// +// Abstract UInt class +// +// Base class for UInt??? classes +// + +var UInt = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +UInt.json_rewrite = function (j) { + return this.from_json(j).to_json(); +}; + +// Return a new UInt from j. +UInt.from_generic = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_generic(j); + } +}; + +// Return a new UInt from j. +UInt.from_hex = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_hex(j); + } +}; + +// Return a new UInt from j. +UInt.from_json = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_json(j); + } +}; + +// Return a new UInt from j. +UInt.from_bits = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bits(j); + } +}; + +// Return a new UInt from j. +UInt.from_bn = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bn(j); + } +}; + +UInt.is_valid = function (j) { + return this.from_json(j).is_valid(); +}; + +UInt.prototype.clone = function () { + return this.copyTo(new this.constructor()); +}; + +// Returns copy. +UInt.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +UInt.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +UInt.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +UInt.prototype.parse_generic = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + switch (j) { + case undefined: + case "0": + case this.constructor.STR_ZERO: + case this.constructor.ADDRESS_ZERO: + case this.constructor.HEX_ZERO: + this._value = nbi(); + break; + + case "1": + case this.constructor.STR_ONE: + case this.constructor.ADDRESS_ONE: + case this.constructor.HEX_ONE: + this._value = new BigInteger([1]); + + break; + + default: + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else if (this.constructor.width === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 256); + } + else if ((this.constructor.width*2) === j.length) { + // XXX Check char set! + this._value = new BigInteger(j, 16); + } + else { + this._value = NaN; + } + } + + return this; +}; + +UInt.prototype.parse_hex = function (j) { + if ('string' === typeof j && + j.length === (this.constructor.width * 2)) { + this._value = new BigInteger(j, 16); + } else { + this._value = NaN; + } + + return this; +}; + +UInt.prototype.parse_bits = function (j) { + if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) { + this._value = NaN; + } else { + var bytes = sjcl.codec.bytes.fromBits(j); + this._value = new BigInteger(bytes, 256); + } + + return this; +}; + +UInt.prototype.parse_json = UInt.prototype.parse_hex; + +UInt.prototype.parse_bn = function (j) { + if (j instanceof sjcl.bn && + j.bitLength() <= this.constructor.width * 8) { + var bytes = sjcl.codec.bytes.fromBits(j.toBits()); + this._value = new BigInteger(bytes, 256); + } else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +UInt.prototype.to_bytes = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this._value.toByteArray(); + bytes = bytes.map(function (b) { return (b+256) % 256; }); + var target = this.constructor.width; + + // XXX Make sure only trim off leading zeros. + bytes = bytes.slice(-target); + while (bytes.length < target) bytes.unshift(0); + + return bytes; +}; + +UInt.prototype.to_hex = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase(); +}; + +UInt.prototype.to_json = UInt.prototype.to_hex; + +UInt.prototype.to_bits = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.bytes.toBits(bytes); +}; + +UInt.prototype.to_bn = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bits = this.to_bits(); + + return sjcl.bn.fromBits(bits); +}; + +exports.UInt = UInt; diff --git a/src/js/uint160.js b/src/js/uint160.js new file mode 100644 index 000000000..b9165e34c --- /dev/null +++ b/src/js/uint160.js @@ -0,0 +1,63 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt160 support +// + +var UInt160 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt160.width = 20; +UInt160.prototype = extend({}, UInt.prototype); +UInt160.prototype.constructor = UInt160; + +var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; +var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; +var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000"; +var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001"; +var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE); + +// value = NaN on error. +UInt160.prototype.parse_json = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else { + this._value = NaN; + } + + return this; +}; + +// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. +UInt160.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes()); + + return output; +}; + +exports.UInt160 = UInt160; diff --git a/src/js/uint256.js b/src/js/uint256.js new file mode 100644 index 000000000..ab5b6f70b --- /dev/null +++ b/src/js/uint256.js @@ -0,0 +1,37 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt256 support +// + +var UInt256 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt256.width = 32; +UInt256.prototype = extend({}, UInt.prototype); +UInt256.prototype.constructor = UInt256; + +// XXX Generate these constants (or remove them) +var ADDRESS_ZERO = UInt256.ADDRESS_ZERO = "XXX"; +var ADDRESS_ONE = UInt256.ADDRESS_ONE = "XXX"; +var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" + + "00000000000000000000000000000000"; +var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" + + "00000000000000000000000000000001"; +var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE); + +exports.UInt256 = UInt256; diff --git a/src/js/utils.js b/src/js/utils.js index d337763a6..d1779faf9 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -27,11 +27,11 @@ var trace = function(comment, func) { }; var arraySet = function (count, value) { - var a = new Array(count); - var i; + var i, a = new Array(count); - for (i = 0; i != count; i += 1) + for (i = 0; i < count; i++) { a[i] = value; + } return a; }; diff --git a/test/amount-test.js b/test/amount-test.js index 71279cbee..e5bec8e00 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -8,7 +8,8 @@ var amount = require("../src/js/amount.js"); var Amount = require("../src/js/amount.js").Amount; var UInt160 = require("../src/js/amount.js").UInt160; -require("../src/js/amount.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require('./config.js'); @@ -20,16 +21,16 @@ buster.testCase("Amount", { buster.assert.equals(nbi(), UInt160.from_generic("0")._value); }, "Parse 0 export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_generic("0").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_generic("0").to_json()); }, "Parse 1" : function () { buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value); }, "Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); }, "Parse rrrrrrrrrrrrrrrrrrrrBZbvji export" : function () { - buster.assert.equals(amount.consts.address_one, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); + buster.assert.equals(UInt160.ADDRESS_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); }, "Parse mtgox export" : function () { buster.assert.equals(config.accounts["mtgox"].account, UInt160.from_json("mtgox").to_json()); diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index ffad70ba6..c1569ba5d 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -12,8 +12,8 @@ var testutils = require("./testutils.js"); var config = require("./config.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/monitor-test.js b/test/monitor-test.js index daef55a65..861f4c15d 100644 --- a/test/monitor-test.js +++ b/test/monitor-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/offer-test.js b/test/offer-test.js index de87c7d10..07122d1d8 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -9,8 +9,8 @@ var Server = require("./server").Server; var testutils = require("./testutils"); -require("../src/js/amount").config = require("./config"); -require("../src/js/remote").config = require("./config"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/path-test.js b/test/path-test.js index 9cc0d7f82..dbc72ef89 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/remote-test.js b/test/remote-test.js index 75ade8be3..e54afde22 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -6,8 +6,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; // XXX Not implemented. diff --git a/test/send-test.js b/test/send-test.js index d2fe9c7f3..524841e86 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/testutils.js b/test/testutils.js index deddc3009..3ef6d6354 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -4,8 +4,8 @@ var Amount = require("../src/js/amount.js").Amount; var Remote = require("../src/js/remote.js").Remote; var Server = require("./server.js").Server; -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require("./config.js"); diff --git a/test/websocket-test.js b/test/websocket-test.js index 02d7e59f9..5f15135cc 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -4,7 +4,8 @@ var Server = require("./server.js").Server; var Remote = require("../src/js/remote.js").Remote; var config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000;