diff --git a/node/Defaults.cpp b/node/Defaults.cpp
index 9176a414e..8144034c8 100644
--- a/node/Defaults.cpp
+++ b/node/Defaults.cpp
@@ -33,6 +33,9 @@
 #include "Defaults.hpp"
 #include "Utils.hpp"
 
+// bin2c'd signed default root topology dictionary
+#include "../root-topology/ZT_DEFAULT_ROOT_TOPOLOGY.c"
+
 #ifdef __WINDOWS__
 #include <WinSock2.h>
 #include <Windows.h>
@@ -43,58 +46,6 @@ namespace ZeroTier {
 
 const Defaults ZT_DEFAULTS;
 
-static inline std::map< Identity,std::vector< std::pair<InetAddress,bool> > > _mkSupernodeMap()
-{
-	std::map< Identity,std::vector< std::pair<InetAddress,bool> > > sn;
-	Identity id;
-	std::vector< std::pair<InetAddress,bool> > addrs;
-
-	// Nothing special about a supernode... except that they are
-	// designated as such and trusted to provide WHOIS lookup.
-
-	// cthulhu.zerotier.com - New York, New York, USA
-	addrs.clear();
-	if (!id.fromString("8acf059fe3:0:482f6ee5dfe902319b419de5bdc765209c0ecda38c4d6e4fcf0d33658398b4527dcd22f93112fb9befd02fd78bf7261b333fc105d192a623ca9e50fc60b374a5"))
-		throw std::runtime_error("invalid identity in Defaults");
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("162.243.77.111",ZT_DEFAULT_UDP_PORT),false));
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("162.243.77.111",443),true));
-	sn[id] = addrs;
-
-	// nyarlathotep.zerotier.com - San Francisco, California, USA
-	addrs.clear();
-	if (!id.fromString("7e19876aba:0:2a6e2b2318930f60eb097f70d0f4b028b2cd6d3d0c63c014b9039ff35390e41181f216fb2e6fa8d95c1ee9667156411905c3dccfea78d8c6dfafba688170b3fa"))
-		throw std::runtime_error("invalid identity in Defaults");
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("198.199.97.220",ZT_DEFAULT_UDP_PORT),false));
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("198.199.97.220",443),true));
-	sn[id] = addrs;
-
-	// shub-niggurath.zerotier.com - Amsterdam, Netherlands
-	addrs.clear();
-	if (!id.fromString("36f63d6574:0:67a776487a1a99b32f413329f2b67c43fbf6152e42c6b66e89043e69d93e48314c7d709b58a83016bd2612dd89400b856e18c553da94892f7d3ca16bf2c92c24"))
-		throw std::runtime_error("invalid identity in Defaults");
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("198.211.127.172",ZT_DEFAULT_UDP_PORT),false));
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("198.211.127.172",443),true));
-	sn[id] = addrs;
-
-	// yig.zerotier.com - Sydney, Australia
-	addrs.clear();
-	if (!id.fromString("275f0151f6:0:58716258283f7e14a2f999875d9cc681c1f0ca8403dce38ec354ceaf284a555f36402e79a32d03b8c0963245b7f1af61a1ad3519d90e05bc3ce591034f6a1c9c"))
-		throw std::runtime_error("invalid identity in Defaults");
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("108.61.212.61",ZT_DEFAULT_UDP_PORT),false));
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("108.61.212.61",443),true));
-	sn[id] = addrs;
-
-	// shoggoth.zerotier.com - Tokyo, Japan
-	addrs.clear();
-	if (!id.fromString("48e8f875cb:0:5ca54f55e1094f65589f3e6d74158b6964d418ddac3570757128f1c6a2498322d92fcdcd47de459f4d1f9b38df2afd0c7b3fc247ba3d773c38ba35288f24988e"))
-		throw std::runtime_error("invalid identity in Defaults");
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("108.61.200.101",ZT_DEFAULT_UDP_PORT),false));
-	addrs.push_back(std::pair<InetAddress,bool>(InetAddress("108.61.200.101",443),true));
-	sn[id] = addrs;
-
-	return sn;
-}
-
 static inline std::string _mkDefaultHomePath()
 {
 #ifdef __UNIX_LIKE__
@@ -113,12 +64,36 @@ static inline std::string _mkDefaultHomePath()
 		return (std::string(buf) + "\\ZeroTier\\One");
 	else return std::string("C:\\ZeroTier\\One");
 #else
-	// unknown platform
+#error Unknown platform, please define a default home path!
 #endif
 
 #endif // __UNIX_LIKE__ or not...
 }
 
+static inline std::map< Address,Identity > _mkRootTopologyAuth()
+{
+	std::map< Address,Identity > ua;
+
+	{ // 0001
+		Identity id("77792b1c02:0:b5c361e8e9c2154e82c3e902fdfc337468b092a7c4d8dc685c37eb10ee4f3c17cc0bb1d024167e8cb0824d12263428373582da3d0a9a14b36e4546c317e811e6");
+		ua[id.address()] = id;
+	}
+	{ // 0002
+		Identity id("86921e6de1:0:9ba04f9f12ed54ef567f548cb69d31e404537d7b0ee000c63f3d7c8d490a1a47a5a5b2af0cbe12d23f9194270593f298d936d7c872612ea509ef1c67ce2c7fc1");
+		ua[id.address()] = id;
+	}
+	{ // 0003
+		Identity id("90302b7025:0:358154a57af1b7afa07d0d91b69b92eaad2f11ade7f02343861f0c1b757d15626e8cb7f08fc52993d2202a39cbf5128c5647ee8c63d27d92db5a1d0fbe1eba19");
+		ua[id.address()] = id;
+	}
+	{ // 0004
+		Identity id("e5174078ee:0:c3f90daa834a74ee47105f5726ae2e29fc8ae0e939c9326788b52b16d847354de8de3b13a81896bbb509b91e1da21763073a30bbfb2b8e994550798d30a2d709");
+		ua[id.address()] = id;
+	}
+
+	return ua;
+}
+
 static inline std::map< Address,Identity > _mkUpdateAuth()
 {
 	std::map< Address,Identity > ua;
@@ -172,9 +147,11 @@ Defaults::Defaults() :
 	multicastTraceWatcher(ZT_TRACE_MULTICAST),
 #endif
 	defaultHomePath(_mkDefaultHomePath()),
-	supernodes(_mkSupernodeMap()),
+	defaultRootTopology((const char *)ZT_DEFAULT_ROOT_TOPOLOGY,ZT_DEFAULT_ROOT_TOPOLOGY_LEN),
+	rootTopologyAuthorities(_mkRootTopologyAuth()),
 	updateAuthorities(_mkUpdateAuth()),
 	updateLatestNfoURL(_mkUpdateUrl()),
+	rootTopologyUpdateURL("http://download.zerotier.com/net/topology/ROOT"),
 	v4Broadcast(((uint32_t)0xffffffff),ZT_DEFAULT_UDP_PORT)
 {
 }
diff --git a/node/Defaults.hpp b/node/Defaults.hpp
index 98110ac7b..869707b47 100644
--- a/node/Defaults.hpp
+++ b/node/Defaults.hpp
@@ -64,9 +64,14 @@ public:
 	const std::string defaultHomePath;
 
 	/**
-	 * Supernodes on the ZeroTier network (identity, address/tcp?)
+	 * Default root topology dictionary
 	 */
-	const std::map< Identity,std::vector< std::pair<InetAddress,bool> > > supernodes;
+	const std::string defaultRootTopology;
+
+	/**
+	 * Identities permitted to sign root topology dictionaries
+	 */
+	const std::map< Address,Identity > rootTopologyAuthorities;
 
 	/**
 	 * Identities permitted to sign software updates
@@ -84,6 +89,11 @@ public:
 	 */
 	const std::string updateLatestNfoURL;
 
+	/**
+	 * URL to check for updates to root topology
+	 */
+	const std::string rootTopologyUpdateURL;
+
 	/**
 	 * Address for IPv4 LAN auto-location broadcasts: 255.255.255.255:9993
 	 */
diff --git a/node/Dictionary.cpp b/node/Dictionary.cpp
index 50440e5b1..6c47b0eae 100644
--- a/node/Dictionary.cpp
+++ b/node/Dictionary.cpp
@@ -116,6 +116,14 @@ bool Dictionary::verify(const Identity &id) const
 	}
 }
 
+uint64_t Dictionary::signatureTimestamp() const
+{
+	const_iterator ts(find(ZT_DICTIONARY_SIGNATURE_TIMESTAMP));
+	if (ts == end())
+		return 0;
+	return Utils::hexStrToU64(ts->second.c_str());
+}
+
 void Dictionary::_mkSigBuf(std::string &buf) const
 {
 	unsigned long pairs = 0;
diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp
index 22eb8a7eb..35e93e43d 100644
--- a/node/Dictionary.hpp
+++ b/node/Dictionary.hpp
@@ -28,6 +28,8 @@
 #ifndef ZT_DICTIONARY_HPP
 #define ZT_DICTIONARY_HPP
 
+#include <stdint.h>
+
 #include <string>
 #include <map>
 #include <stdexcept>
@@ -140,6 +142,16 @@ public:
 	 */
 	inline bool hasSignature() const { return (find(ZT_DICTIONARY_SIGNATURE) != end()); }
 
+	/**
+	 * @return Signing identity in string-serialized format or empty string if none
+	 */
+	inline std::string signingIdentity() const { return get(ZT_DICTIONARY_SIGNATURE_IDENTITY,std::string()); }
+
+	/**
+	 * @return Signature timestamp in milliseconds since epoch or 0 if none
+	 */
+	uint64_t signatureTimestamp() const;
+
 	/**
 	 * Remove any signature from this dictionary
 	 */
diff --git a/node/Node.cpp b/node/Node.cpp
index 7500e7364..e031dbdbc 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -466,7 +466,6 @@ Node::ReasonForTermination Node::run()
 #endif
 		}
 
-		// Load or generate config authentication secret
 		std::string configAuthTokenPath(_r->homePath + ZT_PATH_SEPARATOR_S + "authtoken.secret");
 		std::string configAuthToken;
 		if (!Utils::readFile(configAuthTokenPath.c_str(),configAuthToken)) {
@@ -501,8 +500,19 @@ Node::ReasonForTermination Node::run()
 		}
 #endif
 
-		// Set initial supernode list
-		_r->topology->setSupernodes(ZT_DEFAULTS.supernodes);
+		std::string rootTopologyPath(_r->homePath + ZT_PATH_SEPARATOR_S + "root-topology");
+		std::string rootTopology;
+		if (!Utils::readFile(rootTopologyPath.c_str(),rootTopology))
+			rootTopology = ZT_DEFAULTS.defaultRootTopology;
+		try {
+			Dictionary rt(rootTopology);
+			if (!Topology::authenticateRootTopology(rt))
+				return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"root-topology failed signature verification check");
+			Dictionary supernodes(rt.get("supernodes"));
+			_r->topology->setSupernodes(supernodes);
+		} catch ( ... ) {
+			return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"invalid root-topology format");
+		}
 	} catch (std::bad_alloc &exc) {
 		return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"memory allocation failure");
 	} catch (std::runtime_error &exc) {
diff --git a/node/Peer.hpp b/node/Peer.hpp
index ce0b79a60..d3c3669ba 100644
--- a/node/Peer.hpp
+++ b/node/Peer.hpp
@@ -50,7 +50,7 @@
 #include "NonCopyable.hpp"
 #include "Mutex.hpp"
 
-#define ZT_PEER_SERIALIZATION_VERSION 9
+#define ZT_PEER_SERIALIZATION_VERSION 10
 
 namespace ZeroTier {
 
diff --git a/node/SoftwareUpdater.cpp b/node/SoftwareUpdater.cpp
index 8a9714fc3..e34fd3ca4 100644
--- a/node/SoftwareUpdater.cpp
+++ b/node/SoftwareUpdater.cpp
@@ -165,7 +165,7 @@ void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std
 
 #ifndef ZT_ALWAYS_UPDATE /* for testing */
 		if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) {
-			LOG("software update check complete: version on update site is not newer than my version, no update necessary");
+			TRACE("software update check complete: version on update site is not newer than my version, no update necessary");
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 		}
diff --git a/node/Topology.cpp b/node/Topology.cpp
index 4fae1372e..6fcb17afb 100644
--- a/node/Topology.cpp
+++ b/node/Topology.cpp
@@ -27,6 +27,8 @@
 
 #include <algorithm>
 
+#include "Constants.hpp"
+#include "Defaults.hpp"
 #include "Topology.hpp"
 #include "NodeConfig.hpp"
 #include "CMWC4096.hpp"
@@ -239,6 +241,24 @@ void Topology::clean()
 	}
 }
 
+bool Topology::authenticateRootTopology(const Dictionary &rt)
+{
+	try {
+		std::string signer(rt.signingIdentity());
+		if (!signer.length())
+			return false;
+		Identity signerId(signer);
+		std::map< Address,Identity >::const_iterator authority(ZT_DEFAULTS.rootTopologyAuthorities.find(signerId.address()));
+		if (authority == ZT_DEFAULTS.rootTopologyAuthorities.end())
+			return false;
+		if (signerId != authority->second)
+			return false;
+		return rt.verify(authority->second);
+	} catch ( ... ) {
+		return false;
+	}
+}
+
 void Topology::_dumpPeers()
 {
 	Buffer<ZT_PEER_WRITE_BUF_SIZE> buf;
diff --git a/node/Topology.hpp b/node/Topology.hpp
index 375112084..478b4fc32 100644
--- a/node/Topology.hpp
+++ b/node/Topology.hpp
@@ -37,18 +37,20 @@
 #include <stdexcept>
 
 #include "Constants.hpp"
+
 #include "Address.hpp"
+#include "Identity.hpp"
 #include "Peer.hpp"
 #include "Mutex.hpp"
 #include "InetAddress.hpp"
 #include "Utils.hpp"
 #include "Packet.hpp"
 #include "Logger.hpp"
+#include "Dictionary.hpp"
 
 namespace ZeroTier {
 
 class RuntimeEnvironment;
-class Dictionary;
 
 /**
  * Database of network topology
@@ -370,6 +372,14 @@ public:
 		std::vector< SharedPtr<Peer> > &_v;
 	};
 
+	/**
+	 * Validate a root topology dictionary against the identities specified in Defaults
+	 *
+	 * @param rt Root topology dictionary
+	 * @return True if dictionary signature is valid
+	 */
+	static bool authenticateRootTopology(const Dictionary &rt);
+
 private:
 	const RuntimeEnvironment *const _r;
 
diff --git a/root-topology/root-topology-authority.public b/root-topology/root-topology-authority.public
deleted file mode 100644
index 7897b6168..000000000
--- a/root-topology/root-topology-authority.public
+++ /dev/null
@@ -1 +0,0 @@
-77792b1c02:0:b5c361e8e9c2154e82c3e902fdfc337468b092a7c4d8dc685c37eb10ee4f3c17cc0bb1d024167e8cb0824d12263428373582da3d0a9a14b36e4546c317e811e6
\ No newline at end of file