黒猫P@受験生's avatar
黒猫P@受験生
kur0nekop0315@yabu.me
npub1tcqt...q0l9
hello 最近欲しいものは、 自由で旧武蔵野国在住 中学生です。 信条: 1.完全に安全なシステムはない。 2.不可能に挑め。 3.現実と電子世界を楽しめ
無になろうとしても頭は、色々湧いてくるとか 自分の意志とかいうのは曖昧である
今の世界から救われたくてキリストにすがるくらいなら 自分の力で変えてみせろよとか思う
p2p形式のゲームマーケット考えてたけど、自分のウォレットの秘密鍵をキーに指定してそこからの送信チェーンで検証すればいい事に気づいた。 秘密鍵なら予よき
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Nostr Full Client (NIP-19)</title> <style> body { font-family: sans-serif; display:flex; margin:20px; } #main { width:70%; } #side { width:30%; padding-left:20px; } .post { border:1px solid #999; padding:10px; margin-bottom:10px; background:#fafafa; } textarea { width:100%; height:100px; resize:vertical; } button { margin:5px 0; } input { width:100%; margin-top:5px; } </style> </head> <body> <div id="main"> <h2>Nostr Client (NIP-19 Full)</h2> <button id="setKey">秘密鍵(nsec)設定</button> <h3>投稿</h3> <textarea id="text"></textarea> <button id="send">投稿</button> <h3>Timeline</h3> <div id="timeline"></div> <button id="more">もっと読む</button> </div> <div id="side"> <h3>Relays</h3> <input id="relayInput" placeholder="wss://relay"> <button id="addRelay">追加</button> <div id="relayList"></div> </div> <script> // ======================================================= // 1. NIP-19 (完全内蔵 bech32) // ======================================================= const ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; function polymod(values){ const GEN=[0x3b6a57b2,0x26508e6d,0x1ea119fa,0x3d4233dd,0x2a1462b3]; let chk=1; for(let v of values){ let top=chk>>25; chk=(chk&0x1ffffff)<<5 ^ v; for(let i=0;i<5;i++){ if((top>>i)&1) chk^=GEN[i]; } } return chk; } function hrpExpand(hrp){ let out=[]; for(let i=0;i<hrp.length;i++) out.push(hrp.charCodeAt(i)>>5); out.push(0); for(let i=0;i<hrp.length;i++) out.push(hrp.charCodeAt(i)&31); return out; } function verify(hrp,data){ return polymod(hrpExpand(hrp).concat(data))===1; } function decodeBech32(str){ str=str.toLowerCase(); let pos=str.lastIndexOf("1"); let hrp=str.slice(0,pos); let data=str.slice(pos+1); let words=[]; for(let c of data){ let v=ALPHABET.indexOf(c); if(v===-1) throw "invalid char"; words.push(v); } if(!verify(hrp,words)) throw "checksum error"; return {hrp, words:words.slice(0,-6)}; } function fromWords(words){ let bits=0,val=0,out=[]; for(let w of words){ val=(val<<5)|w; bits+=5; while(bits>=8){ out.push((val>>(bits-8))&255); bits-=8; } } return out; } // ======================================================= // 2. secp256k1(署名) // ======================================================= import('https://cdn.jsdelivr.net/npm/@noble/secp256k1@2.0.0/+esm').then(secp=>window.secp=secp); // ======================================================= // 3. 状態 // ======================================================= let priv=null; let pub=null; let relays=["wss://yabu.me"]; let cache=[]; let shown=0; let wsMap={}; // ======================================================= // 4. utils // ======================================================= function hex(b){ return [...b].map(x=>x.toString(16).padStart(2,"0")).join(""); } function sha256(data){ return secp.utils.sha256(data); } function escape(s){ return s.replace(/[&<>"']/g,c=>({ "&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;" }[c])); } // ======================================================= // 5. NIP-19 decode // ======================================================= function decodeNsec(nsec){ let {hrp,words}=decodeBech32(nsec.trim()); if(hrp!=="nsec") throw "not nsec"; let data=fromWords(words); if(data.length!==32) throw "bad key"; return hex(data); } function decodeNpub(npub){ let {hrp,words}=decodeBech32(npub.trim()); if(hrp!=="npub") throw "not npub"; return hex(fromWords(words)); } // ======================================================= // 6. event id // ======================================================= async function makeId(ev){ let arr=[ 0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content ]; let enc=new TextEncoder().encode(JSON.stringify(arr)); let hash=await sha256(enc); return hex(hash); } // ======================================================= // 7. connect relay // ======================================================= function connect(r){ if(wsMap[r]) return; let ws=new WebSocket(r); wsMap[r]=ws; ws.onopen=()=>{ ws.send(JSON.stringify(["REQ","sub",{kinds:[1],limit:100}])); }; ws.onmessage=(e)=>{ let d=JSON.parse(e.data); if(d[0]==="EVENT"){ let ev=d[2]; ev.content=escape(ev.content); if(!cache.find(x=>x.id===ev.id)){ cache.push(ev); draw(); } } }; } // ======================================================= // 8. draw // ======================================================= function draw(){ let div=document.getElementById("timeline"); div.innerHTML=""; let slice=cache.slice().reverse().slice(0,shown+100); for(let ev of slice){ let d=document.createElement("div"); d.className="post"; d.innerHTML=` <b>${ev.pubkey.slice(0,16)}...</b><br> ${ev.created_at}<br><br> ${ev.content} `; div.appendChild(d); } shown+=100; } // ======================================================= // 9. send // ======================================================= async function send(){ if(!priv) return alert("no key"); let text=document.getElementById("text").value; if(!text.trim()) return alert("empty"); let ev={ kind:1, created_at:Math.floor(Date.now()/1000), tags:[], content:text, pubkey:pub }; ev.id=await makeId(ev); ev.sig=await secp.schnorr.sign(ev.id,priv); for(let r of relays){ let ws=new WebSocket(r); ws.onopen=()=>ws.send(JSON.stringify(["EVENT",ev])); } } // ======================================================= // 10. UI // ======================================================= document.getElementById("setKey").onclick=()=>{ let nsec=prompt("nsec"); try{ priv=decodeNsec(nsec); pub=hex(secp.getPublicKey(priv,true).slice(1)); alert("OK"); }catch(e){ alert("invalid key"); } }; document.getElementById("send").onclick=send; document.getElementById("addRelay").onclick=()=>{ let v=document.getElementById("relayInput").value; if(v && !relays.includes(v)){ relays.push(v); connect(v); } }; // init relays.forEach(connect); </script> </body> </html>