-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added encrypted dm and NIP-19 (npub and nsec parser) support.
- Loading branch information
Showing
9 changed files
with
1,076 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,18 @@ | ||
import { Nostr, Relay, NostrKind } from 'https://deno.land/x/[email protected]/mod.ts'; | ||
// import { Nostr, NostrKind } from "../nostr.ts"; | ||
// import { Nostr, Relay, NostrKind } from 'https://deno.land/x/[email protected]/mod.ts'; | ||
import { Nostr, Relay, NostrKind } from "../nostr.ts"; | ||
|
||
const nostr = new Nostr(); | ||
|
||
nostr.relayList.push({ | ||
name: 'Semisol', | ||
url: 'wss://nostr-pub.semisol.dev' | ||
}); | ||
|
||
nostr.relayList.push({ | ||
name: 'Wellorder', | ||
url: 'wss://nostr-pub.wellorder.net' | ||
name: 'Nostrprotocol', | ||
url: 'wss://relay.nostrprotocol.net' | ||
}); | ||
|
||
nostr.on('relayConnected', (relay: Relay) => console.log('Relay connected.', relay.name)); | ||
nostr.on('relayError', (err: Error) => console.log('Relay error;', err)); | ||
nostr.on('relayNotice', (notice: string[]) => console.log('Notice', notice)); | ||
|
||
//nostr.debugMode = true; | ||
nostr.debugMode = true; | ||
|
||
await nostr.connect(); | ||
|
||
|
@@ -59,4 +54,4 @@ const feeds = await nostr.globalFeed({ | |
}); | ||
console.log('Feeds', feeds); | ||
|
||
console.log('Finish'); | ||
console.log('Finish'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Nostr, Relay } from "../nostr.ts"; | ||
|
||
const nostr = new Nostr(); | ||
|
||
nostr.relayList.push({ | ||
name: 'Nostrprotocol', | ||
url: 'wss://relay.nostrprotocol.net' | ||
}); | ||
|
||
nostr.on('relayConnected', (relay: Relay) => console.log('Relay connected.', relay.name)); | ||
nostr.on('relayError', (err: Error) => console.log('Relay error;', err)); | ||
nostr.on('relayNotice', (notice: string[]) => console.log('Notice', notice)); | ||
|
||
await nostr.connect(); | ||
|
||
nostr.privateKey = 'nsec***********************'; | ||
|
||
await nostr.sendMessage('npub*******************', 'Nostr Deno'); | ||
console.log(await nostr.getMessages()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import * as secp from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { base64 } from 'https://raw.githubusercontent.com/paulmillr/scure-base/main/mod.ts'; | ||
|
||
export default class Message { | ||
|
||
private static getNormalizedX(key: Uint8Array): Uint8Array { | ||
return key.slice(1, 33) | ||
} | ||
|
||
private static randomBytes(bytesLength: number = 32) { | ||
return crypto.getRandomValues(new Uint8Array(bytesLength)); | ||
} | ||
|
||
public static async encryptMessage(to: string, message: string, _privateKey: string): Promise<string> { | ||
const key = secp.getSharedSecret(_privateKey, '02' + to); | ||
const normalizedKey = this.getNormalizedX(key); | ||
const encoder = new TextEncoder(); | ||
const iv = Uint8Array.from(this.randomBytes(16)); | ||
const plaintext = encoder.encode(message); | ||
const cryptoKey = await crypto.subtle.importKey( | ||
'raw', | ||
normalizedKey, | ||
{name: 'AES-CBC'}, | ||
false, | ||
['encrypt'] | ||
); | ||
const ciphertext = await crypto.subtle.encrypt( | ||
{name: 'AES-CBC', iv}, | ||
cryptoKey, | ||
plaintext | ||
); | ||
const toBase64 = (uInt8Array: Uint8Array) => btoa(String.fromCharCode(...uInt8Array)); | ||
const ctb64 = toBase64(new Uint8Array(ciphertext)); | ||
const ivb64 = toBase64(new Uint8Array(iv.buffer)); | ||
return `${ctb64}?iv=${ivb64}`; | ||
} | ||
|
||
private static stringToBuffer(value: string): ArrayBuffer { | ||
let buffer = new ArrayBuffer(value.length * 2); // 2 bytes per char | ||
let view = new Uint16Array(buffer); | ||
for (let i = 0, length = value.length; i < length; i++) { | ||
view[i] = value.charCodeAt(i); | ||
} | ||
return buffer; | ||
} | ||
|
||
public static async decrypt(privkey: string, pubkey: string, data: string): Promise<string> { | ||
const [ ctb64, ivb64 ] = data.split('?iv=') | ||
const key = secp.getSharedSecret(privkey, '02' + pubkey) | ||
const normalizedKey = this.getNormalizedX(key) | ||
|
||
const cryptoKey = await crypto.subtle.importKey( | ||
'raw', | ||
normalizedKey, | ||
{name: 'AES-CBC'}, | ||
false, | ||
['decrypt'] | ||
); | ||
const ciphertext = base64.decode(ctb64); | ||
const iv = base64.decode(ivb64); | ||
|
||
const plaintext = await crypto.subtle.decrypt( | ||
{ name: 'AES-CBC', iv }, | ||
cryptoKey, | ||
ciphertext | ||
); | ||
|
||
const txtDecode = new TextDecoder(); | ||
const text = txtDecode.decode(plaintext) | ||
return text; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,15 @@ import EventEmitter from "https://deno.land/x/[email protected]/mod.ts"; | |
import Relay, { NostrEvent } from "./relay.ts"; | ||
import * as secp from "https://deno.land/x/[email protected]/mod.ts"; | ||
import * as mod from "https://deno.land/[email protected]/encoding/hex.ts"; | ||
import Message from "./message.ts"; | ||
import { bech32 } from 'https://raw.githubusercontent.com/paulmillr/scure-base/main/mod.ts'; | ||
|
||
export enum NostrKind { | ||
META_DATA = 0, | ||
TEXT_NOTE = 1, | ||
RECOMMED_SERVER = 2, | ||
CONTACTS = 3 | ||
CONTACTS = 3, | ||
DIRECT_MESSAGE = 4 | ||
} | ||
|
||
export interface RelayList { | ||
|
@@ -53,6 +56,13 @@ interface NostrEvents { | |
'relayPost': (id: string, status: boolean, errorMessage: string, relay: Relay) => void; | ||
} | ||
|
||
export interface NostrMessage { | ||
content: string; | ||
sender: string; | ||
receiver: string; | ||
createdAt: number; | ||
} | ||
|
||
declare interface Nostr { | ||
on<U extends keyof NostrEvents>( | ||
event: U, listener: NostrEvents[U] | ||
|
@@ -67,19 +77,35 @@ class Nostr extends EventEmitter { | |
public relayList: Array<RelayList> = []; | ||
private relayInstances: Array<Relay> = []; | ||
private _privateKey: any; | ||
public publicKey: any; | ||
private _publicKey: any; | ||
public debugMode = false; | ||
|
||
constructor() { | ||
super(); | ||
} | ||
|
||
public set privateKey(value: any) { | ||
private getKeyFromNip19(key: string) { | ||
const code = bech32.decode(key, 1500); | ||
const data = new Uint8Array(bech32.fromWords(code.words)); | ||
return secp.utils.bytesToHex(data); | ||
} | ||
|
||
public set privateKey(value: string) { | ||
if (value.substring(0, 4) === 'nsec') { | ||
value = this.getKeyFromNip19(value); | ||
} | ||
const decoder = new TextDecoder(); | ||
if (value) { | ||
this._privateKey = value; | ||
this.publicKey = decoder.decode(mod.encode(secp.schnorr.getPublicKey(this._privateKey))); | ||
this._publicKey = decoder.decode(mod.encode(secp.schnorr.getPublicKey(this._privateKey))); | ||
} | ||
} | ||
|
||
public set publicKey(value: string) { | ||
if (value.substring(0, 4) === 'npub') { | ||
value = this.getKeyFromNip19(value); | ||
} | ||
this._publicKey = value; | ||
} | ||
|
||
async connect() { | ||
|
@@ -178,7 +204,7 @@ class Nostr extends EventEmitter { | |
} | ||
|
||
async getMyProfile(): Promise<ProfileInfo> { | ||
return await this.getProfile(this.publicKey); | ||
return await this.getProfile(this._publicKey); | ||
} | ||
|
||
async getOtherProfile(publicKey: string): Promise<ProfileInfo> { | ||
|
@@ -284,12 +310,12 @@ class Nostr extends EventEmitter { | |
} | ||
|
||
async getPosts() { | ||
if (!this.publicKey) { | ||
if (!this._publicKey) { | ||
throw new Error('You must set a public key for getting your posts.'); | ||
} | ||
const filters = { | ||
kinds: [NostrKind.TEXT_NOTE], | ||
authors: [this.publicKey] | ||
authors: [this._publicKey] | ||
} as NostrFilters; | ||
const events = await this.filter(filters).collect(); | ||
const posts = [] as Array<NostrPost>; | ||
|
@@ -374,7 +400,7 @@ class Nostr extends EventEmitter { | |
created_at: Math.floor(Date.now() / 1000), | ||
id: '', | ||
kind: NostrKind.TEXT_NOTE, | ||
pubkey: this.publicKey, | ||
pubkey: this._publicKey, | ||
sig: '', | ||
tags: [] | ||
}; | ||
|
@@ -420,6 +446,62 @@ class Nostr extends EventEmitter { | |
console.log('Debug:', ...args); | ||
} | ||
} | ||
|
||
public async sendMessage(to: string, message: any) { | ||
if (!this._privateKey) { | ||
throw new Error('You must set a private key send to the message.'); | ||
} | ||
if (to.substring(0, 4) === 'npub') { | ||
to = this.getKeyFromNip19(to); | ||
} | ||
const encrypted = await Message.encryptMessage(to, message, this._privateKey); | ||
const event: NostrEvent = { | ||
id: '', | ||
created_at: Math.floor(Date.now() / 1000), | ||
kind: NostrKind.DIRECT_MESSAGE, | ||
pubkey: this._publicKey, | ||
tags: [['p', to]], | ||
content: encrypted, | ||
sig: '' | ||
}; | ||
event.id = await this.calculateId(event); | ||
event.sig = new TextDecoder().decode(mod.encode(await this.signId(event.id))); | ||
for (const relay of this.relayInstances) { | ||
try { | ||
await relay.sendEvent(event); | ||
} catch (err) { | ||
console.error(`Send direct message event error; ${err.message} Relay name; ${relay.name}`); | ||
} | ||
} | ||
} | ||
|
||
public async getMessages(): Promise<NostrMessage[]> { | ||
if (!this._privateKey) { | ||
throw new Error('You must set a private key send to the message.'); | ||
} | ||
const events = await this.filter({ | ||
kinds: [NostrKind.DIRECT_MESSAGE], | ||
"#p": this._publicKey | ||
}).collect(); | ||
const messages: NostrMessage[] = []; | ||
for (const event of events) { | ||
const sender= event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1]; | ||
try { | ||
const pubKey = sender === this._publicKey ? event.pubkey : sender; | ||
const msg = await Message.decrypt(this._privateKey, pubKey, event.content); | ||
messages.push({ | ||
content: msg, | ||
sender, | ||
receiver: event.pubkey, | ||
createdAt: event.created_at | ||
}); | ||
} catch (err) { | ||
this.log('Decrypt error;', err.message); | ||
} | ||
} | ||
return messages; | ||
} | ||
|
||
} | ||
|
||
export { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,6 +208,7 @@ class Relay { | |
this.ws?.send(message); | ||
}); | ||
} | ||
|
||
} | ||
|
||
export default Relay; |
Oops, something went wrong.