├── README.md ├── assets ├── 404.html ├── home.html └── index.html ├── federalist.png ├── include ├── hresolver.js ├── preload.js └── renderer.js ├── main.js ├── package-lock.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # federalist 2 | ## Access a new kind of decentralized website on the DHT 3 | 4 | ## Learn more by joining the [Handshake Discord Community](https://discord.gg/tXJ2UdGuda) 5 | 6 | ## Extension 7 | 8 | An extension was made by the [Kyokan](https://kyokan.io) team. [Bob Extension](https://chrome.google.com/webstore/detail/bob-extension/ogcmjchbmdichlfelhmceldndgmgpcem?hl=en-US) supports the federalist protocol and a plethora of other features including Handshake wallet support! This is highly recommended to use moving forward. This repo will be updated soon to support webrtc. 9 | 10 | ### Short Description 11 | 12 | federalist uses webtorrent with BEP 46 and Handshake domain resolution to access truly decentralized, unblockable swarm websites. 13 | 14 | ### Long Description 15 | 16 | federalist is a proof of concept to show that it's possible, today, to create a decentralized, uncensorable and generally unblockable, distributed high performance 17 | viewable page. federalist achieves this by using several amazing technologies and weaves them together: 18 | 19 | - [WebTorrent](https://github.com/webtorrent) - Provides torrenting capabilities and the bulk of the system. federalist pages are torrents that are 20 | distributed via magnet links and DHT is used to find peers. 21 | 22 | - [DMT](https://github.com/lmatteis/dmt) - Reference implementation of decentralized mutable torrents, [BEP 46](https://github.com/lmatteis/bittorrent.org/blob/master/beps/bep_0046.rst). 23 | 24 | - [Handshake](https://github.com/handshake-org/) - Provides decentralized DNS 25 | 26 | - [nodeJS](https://github.com/nodejs) - Provides the Javascript engine built on V8 by Chrome 27 | 28 | - [Electron](https://github.com/electron) - Provides the GUI 29 | 30 | - [Chromium](https://github.com/chromium/chromium) - Electron uses this really cool software to deliver GUI applications that can be made with HTML, CSS and Javascript. 31 | 32 | ### Features 33 | 34 | - Update your unblockable swarm site without updating your DNS, as often as you'd like. 35 | 36 | - Use HTML, CSS and Javascript 37 | 38 | - Unblockable 39 | 40 | ### Status 41 | 42 | This software is a POC and is in version 0.1a. This is an upgrade from the previous version which worked with markdown files and only immutable destinations. 43 | 44 | ### Screenshot 45 | 46 | ![Federalist Screenshot](https://github.com/publiusfederalist/federalist/blob/master/federalist.png?raw=true) 47 | 48 | 49 | ## Use Cases 50 | 51 | - Create an unblockable blog 52 | 53 | - Whistleblow (must use an anonymizer) 54 | 55 | - Share information 56 | 57 | - Share meta information about other torrents 58 | 59 | - Create an unblockable personal link site 60 | 61 | - Save bandwidth and CDN costs 62 | 63 | ## federalist 64 | 65 | The federalist app is used to browse. You can use the [publius](https://github.com/publiusfederalist/publius) app to share/seed your page and get the infohash to which an on-chain Handshake name will be resolved. 66 | 67 | ## Installation 68 | 69 | Required 70 | ``` 71 | npm 72 | nodejs 73 | ``` 74 | 75 | 1. Clone this repo 76 | ``` 77 | git clone https://github.com/publiusfederalist/federalist 78 | cd federalist 79 | ``` 80 | 81 | 2. Install npm modules 82 | ``` 83 | npm install 84 | ``` 85 | 86 | 3. Run federalist 87 | ``` 88 | npm start 89 | ``` 90 | 91 | 4. Access a decentralized, unblockable swarm site. 92 | ``` 93 | federalist://federalistpapers 94 | ``` 95 | 96 | which points to: 97 | 98 | ``` 99 | magnet:?xs=urn:btpk:e239849106256aad20b0ddadd9f2cb013910dab3207f3b200fbe2e76899cb6c2 100 | ``` 101 | 102 | ## Discussion 103 | 104 | Please make an issue on github for any bugs or feature requests. To discuss more, 105 | join #scarywater on irc.freenode.net. 106 | 107 | 108 | ## Copyright and License 109 | 110 | (c) 2021 Publius 111 | 112 | MIT LICENSED 113 | -------------------------------------------------------------------------------- /assets/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 404 Not Found 4 | 9 | 10 | 11 |

federalist 0.1a

12 |

Thanks for trying out federalist 0.1a.

13 |

Check out federalist://federalistpapers.

14 |

Please donate HNS to hs1qxgz3w2ex9y4v4x37v5jmgmv0m9nsultdala654

. 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /federalist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/publiusfederalist/federalist/f75246c96b34d92721890d6fd8983ab77138dc12/federalist.png -------------------------------------------------------------------------------- /include/hresolver.js: -------------------------------------------------------------------------------- 1 | const doh = require('dohjs'); 2 | const resolver = new doh.DohResolver('https://query.hdns.io/dns-query'); 3 | 4 | let hresolver = async function(name) { 5 | let response = await resolver.query(name,'TXT'); 6 | if(response.answers.length>0) { 7 | let returned=null; 8 | response.answers.forEach(ans=>{ 9 | returned = ans.data.toString(); 10 | }); 11 | return (returned?returned:null); 12 | } 13 | else 14 | return null; 15 | } 16 | 17 | module.exports = hresolver; 18 | -------------------------------------------------------------------------------- /include/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require("electron"); 2 | contextBridge.exposeInMainWorld( 3 | "api", { 4 | send: (channel, data) => { 5 | let validChannels = ["go","external","title"]; 6 | if (validChannels.includes(channel)) { 7 | ipcRenderer.send(channel, data); 8 | } 9 | }, 10 | receive: (channel, func) => { 11 | let validChannels = ["show","notfound","updateAddress","disableAddress","enableAddress"]; 12 | if (validChannels.includes(channel)) { 13 | ipcRenderer.on(channel, (event, ...args) => func(...args)); 14 | } 15 | } 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /include/renderer.js: -------------------------------------------------------------------------------- 1 | // webview Items 2 | const webview = document.querySelector('webview'); 3 | 4 | var buttonForward = document.getElementById('forward'); 5 | var buttonBack = document.getElementById('back'); 6 | var buttonGo = document.getElementById('go'); 7 | var addressBar = document.getElementById('address'); 8 | 9 | var rapidFire=''; 10 | 11 | var hUrls = []; 12 | hUrls[0]="federalist://"; 13 | var curPos = 0; 14 | 15 | webview.addEventListener('dom-ready',()=>{ 16 | // webview Actions 17 | buttonGo.onclick = function() { 18 | window.api.send("go", document.getElementById('address').value); 19 | buttonGo.disabled = true; 20 | addressBar.disabled = true; 21 | } 22 | addressBar.onkeyup = function(e) { 23 | if(e.key === 'Enter' || e.keyCode === 13) 24 | buttonGo.click(); 25 | } 26 | buttonBack.onclick = function() { 27 | if(webview.canGoBack()) { 28 | curPos--; 29 | addressBar.value=hUrls[curPos]; 30 | webview.goBack(); 31 | } 32 | toggleButtons(); 33 | } 34 | buttonForward.onclick = function() { 35 | if(webview.canGoForward()) { 36 | curPos++; 37 | addressBar.value=hUrls[curPos]; 38 | webview.goForward(); 39 | } 40 | toggleButtons(); 41 | } 42 | toggleButtons(); 43 | webview.addEventListener('did-finish-load',(e) => { 44 | window.api.send("title",webview.getTitle() + " - federalist"); 45 | }); 46 | webview.addEventListener('will-navigate',(e)=>{ 47 | webview.stop(); 48 | if(rapidFire!=e.url) { 49 | rapidFire=e.url; 50 | setTimeout(()=>{ 51 | rapidFire=""; 52 | },1000); 53 | if(e.url.substr(0,4)=="http") 54 | window.api.send("external", e.url); 55 | else if(e.url.substr(0,4)=="file" || e.url.substr(0,4)=="magn" || e.url.substr(0,4)=="fede") 56 | window.api.send("go",e.url); 57 | else 58 | doLoadURL(e.url); 59 | } 60 | }); 61 | }); 62 | 63 | 64 | // IPC 65 | window.api.receive("notfound", (data) => { 66 | webview.src="404.html"; 67 | curPos++; 68 | hUrls[curPos]=data; 69 | }); 70 | window.api.receive("show", (data) => { 71 | doLoadURL(data); 72 | toggleButtons(); 73 | }); 74 | window.api.receive("updateAddress", (data) => { 75 | curPos++; 76 | hUrls[curPos]=data; 77 | addressBar.value=data; 78 | }); 79 | window.api.receive("enableAddress",() => { 80 | buttonGo.disabled = false; 81 | addressBar.disabled = false; 82 | }); 83 | window.api.receive("disableAddress",() => { 84 | buttonGo.disabled = true; 85 | addressBar.disabled = true; 86 | }); 87 | 88 | 89 | function doLoadURL(url) { 90 | webview.loadURL(url); 91 | } 92 | function toggleButtons() { 93 | if(webview.canGoBack()) 94 | buttonBack.disabled=false; 95 | else 96 | buttonBack.disabled=true; 97 | if(webview.canGoForward()) 98 | buttonForward.disabled=false; 99 | else 100 | buttonForward.disabled=true; 101 | } 102 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow, shell} = require('electron'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const fse = require('fs-extra'); 5 | const ipcMain = require('electron').ipcMain; 6 | const WebTorrent = require('webtorrent'); 7 | const hresolver = require("./include/hresolver.js"); 8 | const crypto = require('crypto'); 9 | const ed = require('supercop.js'); 10 | const url = require('url'); 11 | 12 | const client = new WebTorrent(); 13 | var mainWindow; 14 | 15 | function createWindow () { 16 | mainWindow = new BrowserWindow({ 17 | width: 800, 18 | height: 600, 19 | minWidth: 400, 20 | minHeight:600, 21 | webPreferences: { 22 | webviewTag: true, 23 | nodeIntegration: false, 24 | contextIsolation: true, 25 | enableRemoteModule: false, 26 | preload: path.join(__dirname,"include/preload.js") 27 | } 28 | }) 29 | mainWindow.on('ready-to-show', function() { 30 | mainWindow.show(); 31 | }); 32 | mainWindow.loadFile('assets/index.html') 33 | } 34 | app.whenReady().then(() => { 35 | createWindow(); 36 | app.on('activate', function () { 37 | if (BrowserWindow.getAllWindows().length === 0) createWindow(); 38 | }); 39 | }); 40 | app.on('window-all-closed', function () { 41 | if (process.platform !== 'darwin') app.quit(); 42 | }) 43 | 44 | 45 | var TEMP_FOLDER = app.getPath('appData')+"/federalist/"; 46 | if(!fs.existsSync(TEMP_FOLDER)) 47 | fs.mkdirSync(TEMP_FOLDER); 48 | TEMP_FOLDER+="torrents/"; 49 | if(!fs.existsSync(TEMP_FOLDER)) 50 | fs.mkdirSync(TEMP_FOLDER); 51 | 52 | 53 | var current = { 54 | host: "", 55 | name: "", 56 | pos: -1 57 | }; 58 | function go(hash,path,name) { 59 | let files = getFiles(TEMP_FOLDER+hash+'/web3root'); 60 | let displayed = false; 61 | let directory = "# Directory listing"; 62 | 63 | if(!path || path=="" || path=="/") 64 | path="/index.html"; 65 | 66 | current.host = hash; 67 | current.name = name; 68 | 69 | files.forEach((file)=> { 70 | if(file.substr((TEMP_FOLDER+hash+'/web3root/').length)==path.substr(1)) { 71 | displayed=true; 72 | mainWindow.webContents.send('show','file:///'+TEMP_FOLDER+hash+'/web3root'+path); 73 | } 74 | directory+="- "+file; 75 | }); 76 | if(!displayed) { 77 | if(path != "/") { 78 | mainWindow.webContents.send('notfound','404'); 79 | mainWindow.webContents.send('updateAddress','404 Not Found'); 80 | mainWindow.webContents.send('enableAddress'); 81 | return; 82 | } 83 | else 84 | mainWindow.webContents.send('show','file:///'+TEMP_FOLDER+current.host+'/web3root'+path); 85 | } 86 | if(name=='') 87 | mainWindow.webContents.send('updateAddress',"magnet:?xt=urn:btih:"+hash+path); 88 | else 89 | mainWindow.webContents.send('updateAddress',"federalist://"+name+path); 90 | mainWindow.webContents.send('enableAddress'); 91 | } 92 | ipcMain.on('title',(event,arg) => { 93 | mainWindow.setTitle(arg); 94 | }); 95 | ipcMain.on('external',(event,arg) => { shell.openExternal(arg); }); 96 | ipcMain.on('go',async (event,arg) => { 97 | mainWindow.webContents.send('disableAddress'); 98 | var federalistData={}; 99 | var name=''; 100 | var target={}; 101 | 102 | if(arg.startsWith('federalist')) { 103 | _tmp = await parseFederalist(arg); 104 | if(_tmp.err) { 105 | mainWindow.webContents.send('enableAddress'); 106 | mainWindow.webContents.send('notfound','DNS Error'); 107 | mainWindow.webContents.send('updateAddress','DNS Error'); 108 | return; 109 | } 110 | arg = _tmp.url; 111 | name = _tmp.name; 112 | } 113 | if(decodeURI(arg).startsWith('file://'+TEMP_FOLDER)) { 114 | arg=decodeURI(arg).substr(('file://'+TEMP_FOLDER).length); 115 | target.hash=arg.substr(0,40); 116 | target.path=arg.substr(49); 117 | target.name=current.name; 118 | } 119 | else { 120 | target = await parseAddress(arg,name); 121 | mainWindow.webContents.send('enableAddress'); 122 | mainWindow.webContents.send('notfound','DHT Error'); 123 | mainWindow.webContents.send('updateAddress','DHT Error'); 124 | } 125 | if(!fs.existsSync(TEMP_FOLDER+target.hash)) { 126 | if(!client.get(target.hash)) { 127 | client.add("magnet:?xt=urn:btih:"+target.hash,{path:TEMP_FOLDER+target.hash},(torrent) => { 128 | if(torrent.length>10000000) { 129 | client.remove(torrent.infoHash); 130 | mainWindow.webContents.send('show','Over 10MB'); 131 | return; 132 | } 133 | torrent.on('done',()=> { 134 | go(target.hash,target.path,target.name); 135 | }); 136 | }); 137 | } 138 | } 139 | else { 140 | let seeding=false; 141 | if (client.get(target.hash)) 142 | seeding=true; 143 | if(!seeding) { 144 | client.add("magnet:?xt=urn:btih:"+target.hash,{path:TEMP_FOLDER+target.hash}); 145 | } 146 | go(target.hash,target.path,target.name); 147 | } 148 | }); 149 | 150 | async function parseAddress(arg,name) { 151 | var target = {}; 152 | 153 | if(!arg || arg.length==0) 154 | target.error = true; 155 | else if (arg.startsWith('file://')) { 156 | target.path = arg.substr(("file://"+__dirname).length); 157 | target.hash = current.host; 158 | target.name = current.name; 159 | } 160 | else if (arg.startsWith('magnet:?xt=urn:btih:')) { 161 | target.hash = arg.substr(20,40); 162 | target.path = arg.substr(60); 163 | target.name = name; 164 | } 165 | else if (arg.startsWith('magnet:?xs=urn:btpk:')) { 166 | let tmp = arg.substr(20); 167 | if (tmp.indexOf('/')>0) { 168 | target.hash = await runConsume(arg.substr(20,arg.indexOf('/'))); 169 | if(target.hash==="error") 170 | target.err = true; 171 | else { 172 | target.path = tmp.substr(tmp.indexOf('/')); 173 | target.name = name; 174 | } 175 | } 176 | else { 177 | target.hash = await runConsume(arg.substr(20)); 178 | if(target.hash==="error") 179 | target.err = true; 180 | else { 181 | target.path = ''; 182 | target.name = name; 183 | } 184 | } 185 | } 186 | else 187 | target.err = true; 188 | 189 | return target; 190 | } 191 | 192 | async function parseFederalist(arg) { 193 | let err=false; 194 | let name,path,destination; 195 | if(arg.startsWith('federalist://')) { 196 | let tmp = arg.substr(13); 197 | if(tmp.indexOf('/')>0) { 198 | name = tmp.substr(0,tmp.indexOf('/')); 199 | path = tmp.substr(tmp.indexOf('/')); 200 | } 201 | else { 202 | name = tmp; 203 | path = ''; 204 | } 205 | 206 | destination = (await hresolver(name)); 207 | 208 | if(!destination || destination == "") 209 | err = true; 210 | else 211 | destination=destination.toString().trim(); 212 | 213 | } 214 | else 215 | err = true; 216 | 217 | if(err) 218 | return {err:err}; 219 | else 220 | return {url:destination + path, name:name}; 221 | } 222 | 223 | async function runConsume(publicKey) { 224 | var buffPubKey = Buffer.from(publicKey, 'hex') 225 | var targetID = crypto.createHash('sha1').update(buffPubKey).digest('hex') // XXX missing salt 226 | var client = new WebTorrent({ dht: {verify: ed.verify }}) 227 | client.on('error', () => { 228 | return "error"; 229 | }); 230 | 231 | var dht = client.dht 232 | return new Promise((resolve,reject) => { 233 | dht.on('ready', async function () { 234 | dht.get(targetID, async function (err, res) { 235 | if (err || !res) { 236 | client.destroy(); 237 | reject("error"); 238 | } 239 | else { 240 | client.destroy(); 241 | resolve(res.v.ih.toString('hex')); 242 | } 243 | }); 244 | }); 245 | }); 246 | } 247 | function getFiles(mpath, mfiles) { 248 | let files = fs.readdirSync(mpath); 249 | mfiles = mfiles || []; 250 | files.forEach((file)=> { 251 | if(fs.statSync(mpath + "/" + file).isDirectory()) 252 | mfiles = getFiles(mpath + "/" + file,mfiles); 253 | else 254 | mfiles.push(path.join(mpath,"/",file)); 255 | }); 256 | return mfiles; 257 | } 258 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "federalist", 3 | "version": "1.0.0", 4 | "description": "Access the federalist, a form of web3", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "keywords": [ 10 | "web3", 11 | "federalist", 12 | "handshake", 13 | "bittorrent" 14 | ], 15 | "author": "root", 16 | "license": "MIT", 17 | "dependencies": { 18 | "dohjs": "^0.3.2", 19 | "electron": "^16.0.3", 20 | "marked": "^4.0.5", 21 | "supercop.js": "^2.0.1", 22 | "webtorrent": "^1.5.8" 23 | } 24 | } 25 | --------------------------------------------------------------------------------