Skip to content

Commit

Permalink
feat: NIPS 1, 5, 10, 19, 20, 28, 51
Browse files Browse the repository at this point in the history
  • Loading branch information
water783 committed May 24, 2023
1 parent 75a9731 commit 6474fd3
Show file tree
Hide file tree
Showing 15 changed files with 970 additions and 0 deletions.
8 changes: 8 additions & 0 deletions lib/nostr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,13 @@ export 'src/filter.dart';
export 'src/close.dart';
export 'src/message.dart';
export 'src/utils.dart';
export 'src/nips/nip_001.dart';
export 'src/nips/nip_002.dart';
export 'src/nips/nip_004.dart';
export 'src/nips/nip_005.dart';
export 'src/nips/nip_010.dart';
export 'src/nips/nip_019.dart';
export 'src/nips/nip_020.dart';
export 'src/nips/nip_028.dart';
export 'src/nips/nip_051.dart';

19 changes: 19 additions & 0 deletions lib/src/nips/nip_001.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:nostr/nostr.dart';

/// Basic Event Kinds
/// 0: set_metadata: the content is set to a stringified JSON object {name: <username>, about: <string>, picture: <url, string>} describing the user who created the event. A relay may delete past set_metadata events once it gets a new one for the same pubkey.
/// 1: text_note: the content is set to the plaintext content of a note (anything the user wants to say). Do not use Markdown! Clients should not have to guess how to interpret content like [](). Use different event kinds for parsable content.
/// 2: recommend_server: the content is set to the URL (e.g., wss://somerelay.com) of a relay the event creator wants to recommend to its followers.
class Nip1 {
static Event setMetadata(String content, String privkey) {
return Event.from(kind: 0, tags: [], content: content, privkey: privkey);
}

static Event textNote(String content, String privkey) {
return Event.from(kind: 1, tags: [], content: content, privkey: privkey);
}

static Event recommendServer(String content, String privkey) {
return Event.from(kind: 2, tags: [], content: content, privkey: privkey);
}
}
78 changes: 78 additions & 0 deletions lib/src/nips/nip_005.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:convert';
import 'package:nostr/nostr.dart';

/// Mapping Nostr keys to DNS-based internet identifiers
class Nip5 {
/// decode setmetadata event
/// {
/// "pubkey": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9",
/// "kind": 0,
/// "content": "{\"name\": \"bob\", \"nip05\": \"[email protected]\"}"
/// }
static Future<DNS?> decode(Event event) async {
if (event.kind == 0) {
try {
Map map = jsonDecode(event.content);
String dns = map['nip05'];
List<dynamic> relays = map['relays'];
if (dns.isNotEmpty) {
List<dynamic> parts = dns.split('@');
String name = parts[0];
String domain = parts[1];
return DNS(name, domain, event.pubkey, relays.map((e) => e.toString()).toList());
}
} catch (e) {
throw Exception(e.toString());
}
}
throw Exception("${event.kind} is not nip1 compatible");
}

/// encode set metadata event
static Event encode(
String name, String domain, List<String> relays, String privkey) {
if (isValidName(name) && isValidDomain(domain)) {
String content = generateContent(name, domain, relays);
return Nip1.setMetadata(content, privkey);
} else {
throw Exception("not a valid name or domain!");
}
}

static bool isValidName(String input) {
RegExp regExp = RegExp(r'^[a-z0-9_]+$');
return regExp.hasMatch(input);
}

static bool isValidDomain(String domain) {
RegExp regExp = RegExp(
r'^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$',
caseSensitive: false,
);
return regExp.hasMatch(domain);
}

static String generateContent(
String name, String domain, List<String> relays) {
Map<String, dynamic> map = {
'name': name,
'nip05': '$name@$domain',
'relays': relays,
};
return jsonEncode(map);
}
}

///
class DNS {
String name;

String domain;

String pubkey;

List<String> relays;

/// Default constructor
DNS(this.name, this.domain, this.pubkey, this.relays);
}
70 changes: 70 additions & 0 deletions lib/src/nips/nip_010.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
///This NIP describes how to use "e" and "p" tags in text events,
///especially those that are replies to other text events.
///It helps clients thread the replies into a tree rooted at the original event.
class Nip10 {
///{
/// "tags": [
/// ["e", <kind_40_event_id>, <relay-url>, "root"],
/// ["e", <kind_42_event_id>, <relay-url>, "reply"],
/// ["p", <pubkey>, <relay-url>],
/// ...
/// ],
/// ...
/// }
static Thread fromTags(List<List<String>> tags) {
ETag root = ETag('', '', '');
List<ETag> etags = [];
List<PTag> ptags = [];
for (var tag in tags) {
if (tag[0] == "p") ptags.add(PTag(tag[1], tag[2]));
if (tag[0] == "e") {
if (tag[3] == 'root') {
root = ETag(tag[1], tag[2], tag[3]);
} else {
etags.add(ETag(tag[1], tag[2], tag[3]));
}
}
}
return Thread(root, etags, ptags);
}

static ETag rootTag(String eventId, String relay) {
return ETag(eventId, relay, 'root');
}

static List<List<String>> toTags(Thread thread) {
List<List<String>> result = [];
result.add(
["e", thread.root.eventId, thread.root.relayURL, thread.root.marker]);
for (var etag in thread.etags) {
result.add(["e", etag.eventId, etag.relayURL, etag.marker]);
}
for (var ptag in thread.ptags) {
result.add(["p", ptag.pubkey, ptag.relayURL]);
}
return result;
}
}

class ETag {
String eventId;
String relayURL;
String marker; // root, reply, mention

ETag(this.eventId, this.relayURL, this.marker);
}

class PTag {
String pubkey;
String relayURL;

PTag(this.pubkey, this.relayURL);
}

class Thread {
ETag root;
List<ETag> etags;
List<PTag> ptags;
Thread(this.root, this.etags, this.ptags);
}
91 changes: 91 additions & 0 deletions lib/src/nips/nip_019.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'package:bech32/bech32.dart';
import 'package:convert/convert.dart';

/// bech32-encoded entities
class Nip19 {
static encodePubkey(String pubkey) {
return bech32Encode("npub", pubkey);
}

static encodePrivkey(String privkey) {
return bech32Encode("nsec", privkey);
}

static encodeNote(String noteid) {
return bech32Encode("note", noteid);
}

static String decodePubkey(String data) {
Map map = bech32Decode(data);
if (map["prefix"] == "npub") {
return map["data"];
} else {
return "";
}
}

static String decodePrivkey(String data) {
Map map = bech32Decode(data);
if (map["prefix"] == "nsec") {
return map["data"];
} else {
return "";
}
}

static String decodeNote(String data) {
Map map = bech32Decode(data);
if (map["prefix"] == "note") {
return map["data"];
} else {
return "";
}
}
}

/// help functions
String bech32Encode(String prefix, String hexData) {
final data = hex.decode(hexData);
final convertedData = convertBits(data, 8, 5, true);
final bech32Data = Bech32(prefix, convertedData);
return bech32.encode(bech32Data);
}

Map<String, String> bech32Decode(String bech32Data) {
final decodedData = bech32.decode(bech32Data);
final convertedData = convertBits(decodedData.data, 5, 8, false);
final hexData = hex.encode(convertedData);

return {'prefix': decodedData.hrp, 'data': hexData};
}

List<int> convertBits(List<int> data, int fromBits, int toBits, bool pad) {
var acc = 0;
var bits = 0;
final maxv = (1 << toBits) - 1;
final result = <int>[];

for (final value in data) {
if (value < 0 || value >> fromBits != 0) {
throw Exception('Invalid value: $value');
}
acc = (acc << fromBits) | value;
bits += fromBits;

while (bits >= toBits) {
bits -= toBits;
result.add((acc >> bits) & maxv);
}
}

if (pad) {
if (bits > 0) {
result.add((acc << (toBits - bits)) & maxv);
}
} else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) != 0) {
throw Exception('Invalid data');
}

return result;
}
20 changes: 20 additions & 0 deletions lib/src/nips/nip_020.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:nostr/nostr.dart';

class Nip20 {
static OKEvent? getOk(String okPayload) {
var ok = Message.deserialize(okPayload);
if(ok.type == 'OK'){
var object = jsonDecode(ok.message);
return OKEvent(object[0], object[1], object[2]);
}
}
}

class OKEvent {
String eventId;
bool status;
String message;

OKEvent(this.eventId, this.status, this.message);
}
Loading

0 comments on commit 6474fd3

Please sign in to comment.