anproto -- authenticated non-networked protocol or another proto sha256 blobs signed with ed25519 keypairs anproto.com
ed25519 social protocols

interactive aproto generator

Ev Bogue a736d976 69105f31

+1 -2
a.js
··· 19 19 ); 20 20 }; 21 21 22 - a.sign = async (d, k) => { 22 + a.sign = async (h, k) => { 23 23 const ts = Date.now(); 24 - const h = await a.hash(d); 25 24 const s = encode( 26 25 nacl.sign(new TextEncoder().encode(ts + h), decode(k.substring(44))), 27 26 );
+146
example.js
··· 1 + import { Hono } from "jsr:@hono/hono"; 2 + import { serveStatic } from 'jsr:@hono/hono/deno' 3 + 4 + import { foot, head } from "./template.js"; 5 + 6 + const app = new Hono(); 7 + 8 + app.get('/', async (c) => { 9 + const content = ` 10 + <div id="scroller"> 11 + <div class='message'> 12 + <h1>AProto</h1> 13 + <p><a href="./try">Try AProto</a></p> 14 + <p><a href="https://wiredove.net/">Wiredove</a></p> 15 + </div> 16 + </div> 17 + ` 18 + 19 + const html = await head('Index') + content + await foot() 20 + return await c.html(html) 21 + }) 22 + 23 + app.get('/try', async (c) => { 24 + 25 + const body = `<body> 26 + <div id='scroller'> 27 + <div class='message'> 28 + <h1>Try AProto</h1> 29 + 30 + <p><em>An Interactive Demonstration</em></p> 31 + 32 + <p><strong>Step 1.</strong> Generate an ed25519 keypair</p> 33 + 34 + <code>const kp = await a.gen()</code> 35 + 36 + <input style='width: 100%;' id='key' placeholder='Make a keypair'></input> 37 + 38 + <button id='but'>Generate keypair</button> 39 + 40 + </div> 41 + 42 + <div class='message'> 43 + 44 + <p><strong>Step 2.</strong> Hash your blob with sha256</p> 45 + 46 + <code>const hash = await a.hash(content)</code> 47 + 48 + <input style='width: 100%;' id='content' placeholder='Write a message'></input> 49 + 50 + <button id='hash'>Generate hash</button> 51 + 52 + <span id='sha256'></span> 53 + 54 + </div> 55 + 56 + <div class='message'> 57 + 58 + <p><strong>Step 3.</strong> Sign an AProto message</p> 59 + 60 + <code>const sig = await a.sign(hash, keypair)</code> 61 + 62 + <input style='width: 100%' id='sig'></input> 63 + 64 + <button id='sign'>Sign message</button> 65 + 66 + </div> 67 + <div class="message"> 68 + 69 + <p><strong>Step 4.</strong> Open the AProto message</p> 70 + 71 + <code>const opened = await a.open(msg)</code> 72 + 73 + <input style='width: 100%;' id='openen'></input> 74 + 75 + <button id='open'>Open</button> 76 + 77 + </div> 78 + <div class="message"> 79 + 80 + <p><strong>Step 5.</strong> Retrieve the blob</p> 81 + 82 + <span id='msg'></span> 83 + 84 + <button id='get'>Get</button> 85 + 86 + </div> 87 + </div> 88 + </body> 89 + 90 + <script type='module'> 91 + import { a } from './a.js' 92 + 93 + const key = document.getElementById('key') 94 + const button = document.getElementById('but') 95 + 96 + button.onclick = async () => { 97 + key.value = await a.gen() 98 + } 99 + 100 + const content = document.getElementById('content') 101 + const hashbutton = document.getElementById('hash') 102 + const sha = document.getElementById('sha256') 103 + 104 + let blobs = [] 105 + 106 + hashbutton.onclick = async () => { 107 + sha.textContent = await a.hash(content.value) 108 + blobs[sha.textContent] = content.value 109 + } 110 + 111 + const siginput = document.getElementById('sig') 112 + const signbutton = document.getElementById('sign') 113 + 114 + signbutton.onclick = async () => { 115 + siginput.value = await a.sign(sha.textContent, key.value) 116 + } 117 + 118 + const openbutton = document.getElementById('open') 119 + 120 + const openen = document.getElementById('openen') 121 + 122 + openbutton.onclick = async () => { 123 + openen.value = await a.open(siginput.value) 124 + } 125 + 126 + const msgspan = document.getElementById('msg') 127 + const getbutton = document.getElementById('get') 128 + 129 + getbutton.onclick = async () => { 130 + msgspan.textContent = blobs[openen.value.substring(13)] 131 + if (openen.value.substring(13) == sha.textContent) { 132 + msgspan.textContent = msgspan.textContent + ' ✅' 133 + } else { 134 + msgspan.textContent = msgspan.textContent + ' ❌' 135 + } 136 + } 137 + 138 + </script>` 139 + 140 + const html = await head('Try it') + body + await foot() 141 + return await c.html(html) 142 + }) 143 + 144 + app.use('*', serveStatic({ root: './' })) 145 + 146 + export default app
+189
style.css
··· 1 + body { 2 + background-color: #f2f2f2; 3 + color: #444; 4 + font-family: "Source Sans 3", sans-serif; 5 + max-width: 100%; 6 + margin-top: 45px; 7 + margin-bottom: 10em; 8 + } 9 + 10 + #scroller {max-width: 680px; margin-left: auto; margin-right: auto;} 11 + 12 + blockquote { border-left: 5px solid #f5f5f5; margin-left: none; padding-left: 10px; color: #777; } 13 + 14 + p, h1, h2, h3, h4, h5, h6 { margin-top: 0px; margin-bottom: 2px; } 15 + 16 + pre { 17 + //color: #dd1144; 18 + background: #f5f5f5; 19 + width: 100%; 20 + display: block; 21 + } 22 + 23 + code { 24 + background: #f5f5f5; 25 + padding: 5px; 26 + border-radius: 5px; 27 + display: inline-block; 28 + vertical-align: bottom; 29 + } 30 + 31 + code, pre { 32 + font-family: "Roboto Mono", monospace; 33 + font-size: .9em; 34 + overflow: auto; 35 + word-break: break-all; 36 + word-wrap: break-word; 37 + white-space: pre; 38 + white-space: -moz-pre-wrap; 39 + white-space: pre-wrap; 40 + white-space: pre\9; 41 + } 42 + 43 + button { 44 + font-size: .85em; 45 + background: #fff; 46 + background-image: linear-gradient(to bottom, #ffffff, #f2f2f2); 47 + border: 1px solid #e4e4e4; 48 + padding: 5px 10px 5px 10px; 49 + border-radius: 5px; 50 + } 51 + 52 + hr { border: 1px solid #e4e4e4;} 53 + 54 + button:hover { 55 + background: #f2f2f2; 56 + cursor: pointer; 57 + } 58 + 59 + textarea, input { 60 + font-size: 1em; 61 + font-family: "Source Sans 3", sans-serif; 62 + border: 1px solid #f8f8f8; 63 + border-radius: 5px; 64 + background: #f8f8f8; 65 + color: #555; 66 + padding: 5px; 67 + //box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 68 + } 69 + 70 + .composer { margin-left: 37px; margin-top: 0;} 71 + 72 + textarea:hover, textarea:focus, input:hover, input:focus, { 73 + background: transparent; 74 + } 75 + 76 + textarea:focus, input:focus { 77 + outline: none !important; 78 + } 79 + 80 + textarea { 81 + margin-top: 5px; 82 + margin-bottom: 5px; 83 + width: 99%; 84 + height: 150px; 85 + } 86 + 87 + a { 88 + color: #045fd0; 89 + text-decoration: none; 90 + } 91 + 92 + a:hover { 93 + color: #8d82fe; 94 + } 95 + 96 + img {width: 95%; margin: 1em;} 97 + 98 + .material-symbols-outlined { color: #666; vertical-align: middle; font-size: 18px; cursor: pointer;} 99 + 100 + iframe { 101 + width: 100%; 102 + border: 1px solid #e4e4e4; 103 + border-radius: 5px; 104 + margin-top: 5px; 105 + height: 275px; 106 + } 107 + 108 + #navbar { 109 + padding-top: .5em; 110 + padding-left: 1em; 111 + padding-bottom: .5em; 112 + position: fixed; 113 + width: 100%; 114 + z-index: 1; 115 + top: 0; 116 + left: 0; 117 + background-color: rgba(242,242,242,0.5); 118 + backdrop-filter: blur(10px); 119 + border-bottom: 1px solid #333; 120 + } 121 + 122 + .message { 123 + padding: .75em; 124 + margin-top: 5px; 125 + background: #f8f8f8; 126 + border: 1px solid #f5f5f5; 127 + min-height: 35px; 128 + border-radius: 5px; 129 + overflow: hidden; 130 + } 131 + 132 + .message:hover { 133 + border: 1px solid #eee; 134 + } 135 + 136 + @media (prefers-color-scheme: dark) { 137 + body { 138 + background-color: #181818; 139 + color: #ccc; 140 + } 141 + #navbar { background-color: rgba(24,24,24,0.2); } 142 + .message { background-color: #222; border: 1px solid #1e1e1e;} 143 + .message:hover { border: 1px solid #333;} 144 + 145 + textarea, input, iframe { background: #222; color: #f5f5f5; border: 1px solid #222;} 146 + 147 + button { color: #ccc; background: #333; border: 1px solid #444;} 148 + button:hover { background: #222;} 149 + hr { border: 1px solid #333;} 150 + pre, code { background: #333; color: #f5f5f5;} 151 + a {color: #50afe4;} 152 + } 153 + 154 + .content {margin-top: 5px;} 155 + 156 + .message, .message > * { 157 + animation: fadein .5s; 158 + } 159 + 160 + @keyframes fadein { 161 + from { opacity: 0; } 162 + to { opacity: 1; } 163 + } 164 + 165 + .pubkey { 166 + color: #9da0a4; 167 + font-family: monospace; 168 + } 169 + 170 + .avatar, .avatar_small { 171 + border-radius: 100%; 172 + margin: 0px; 173 + margin-right: 10px; 174 + object-fit: cover; 175 + vertical-align: top; 176 + } 177 + 178 + .avatar { 179 + height: 33px; 180 + width: 33px; 181 + } 182 + 183 + .avatar_small { height: 25px; width: 25px;} 184 + 185 + .breadcrumbs { font-size: 1em; } 186 + 187 + .avatarlink { font-weight: 600;} 188 + .unstyled { color: #ccc;} 189 + .hljs { padding: 10px; border-radius: 5px; background: #555; color: #f2f2f2;}
+29
template.js
··· 1 + export const head = async (title) => { 2 + return await ` 3 + <!doctype html> 4 + <html> 5 + <head> 6 + <title>A Protocol | ${title}</title> 7 + <link rel='stylesheet' href='./style.css' type='text/css' /> 8 + <meta name='viewport' content='width=device-width initial-scale=1' /> 9 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> 10 + <link rel="preconnect" href="https://fonts.googleapis.com"> 11 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 12 + <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" /> 13 + <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet"> 14 + 15 + </head> 16 + <body> 17 + <div id='navbar'> 18 + <img src='https://wiredove.net/doveorange_sm.png' class='avatar_small' style='vertical-align: middle;'></strong> 19 + <strong><span style="color: #fe7a00;">A</span>Proto</strong> 20 + </div> 21 + `; 22 + }; 23 + 24 + export const foot = async () => { 25 + return await ` 26 + </body> 27 + </html> 28 + `; 29 + };