Fixed Nip19 connection
This commit is contained in:
parent
f70e0135ec
commit
3c80a24300
@ -1,71 +0,0 @@
|
|||||||
class ProfilePointer {
|
|
||||||
final String pubkey;
|
|
||||||
final List<String>? relays;
|
|
||||||
|
|
||||||
ProfilePointer({required this.pubkey, this.relays});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// EventPointer is a class that represents a pointer to an event in the Nostr protocol.
|
|
||||||
class EventPointer {
|
|
||||||
/// The unique identifier of the event.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// A list of relays to use to reach the event.
|
|
||||||
final List<String>? relays;
|
|
||||||
|
|
||||||
/// The author of the event.
|
|
||||||
final String? author;
|
|
||||||
|
|
||||||
/// Constructs an EventPointer object with the given properties.
|
|
||||||
///
|
|
||||||
/// The [id] parameter is required and represents the unique identifier of the event.
|
|
||||||
/// The [relays] parameter is optional and represents a list of relays to use to reach the event.
|
|
||||||
/// The [author] parameter is optional and represents the author of the event.
|
|
||||||
EventPointer({required this.id, this.relays, this.author});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pointer to a Nostr address.
|
|
||||||
///
|
|
||||||
/// The [AddressPointer] class is used to store information about a Nostr address
|
|
||||||
/// including an identifier, a public key, a kind, and a list of relays.
|
|
||||||
class AddressPointer {
|
|
||||||
/// The identifier for the address.
|
|
||||||
final String identifier;
|
|
||||||
|
|
||||||
/// The public key associated with the address.
|
|
||||||
final String pubkey;
|
|
||||||
|
|
||||||
/// The kind of the address.
|
|
||||||
final int kind;
|
|
||||||
|
|
||||||
/// The list of relays associated with the address.
|
|
||||||
final List<String>? relays;
|
|
||||||
|
|
||||||
/// Creates a new [AddressPointer] instance.
|
|
||||||
///
|
|
||||||
/// The [identifier], [pubkey], and [kind] parameters are required.
|
|
||||||
/// The [relays] parameter is optional and can be `null`.
|
|
||||||
AddressPointer({
|
|
||||||
required this.identifier,
|
|
||||||
required this.pubkey,
|
|
||||||
required this.kind,
|
|
||||||
this.relays,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignatureVerificationException implements Exception {
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
SignatureVerificationException(this.message);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'SignatureVerificationException: $message';
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChecksumVerificationException implements Exception {
|
|
||||||
final String message;
|
|
||||||
ChecksumVerificationException(this.message);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'ChecksumVerificationException: $message';
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
// import '../nip19/models.dart';
|
|
||||||
// import '../nip19/nip19_api.dart';
|
|
||||||
// import '../nip19/nip19_impl.dart';
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
|||||||
/// This library provides the interface for NIP-19 encoding and decoding.
|
|
||||||
library api.nip19;
|
|
||||||
|
|
||||||
export 'nip19_impl.dart';
|
|
||||||
|
|
||||||
import 'models.dart';
|
|
||||||
import 'nip19_impl.dart';
|
|
||||||
|
|
||||||
/// The abstract class [Nip19] is the public API for encoding and decoding NIP-19 codes.
|
|
||||||
abstract class Nip19 {
|
|
||||||
/// Creates a [Nip19Impl] instance.
|
|
||||||
factory Nip19() => Nip19Impl();
|
|
||||||
|
|
||||||
/// Encodes a given hexadecimal string into a NIP-19 'nsec' string.
|
|
||||||
String nsecEncode(String hex);
|
|
||||||
|
|
||||||
/// Encodes a given hexadecimal string into a NIP-19 'npub' string.
|
|
||||||
String npubEncode(String hex);
|
|
||||||
|
|
||||||
/// Encodes a given hexadecimal string into a NIP-19 'note' string.
|
|
||||||
String noteEncode(String hex);
|
|
||||||
|
|
||||||
/// Encodes a given [ProfilePointer] object into a NIP-19 'nprofile' string.
|
|
||||||
String nprofileEncode(ProfilePointer profile);
|
|
||||||
|
|
||||||
/// Encodes a given [EventPointer] object into a NIP-19 'nevent' string.
|
|
||||||
String neventEncode(EventPointer event);
|
|
||||||
|
|
||||||
/// Encodes a given [AddressPointer] object into a NIP-19 'naddr' string.
|
|
||||||
String naddrEncode(AddressPointer addr);
|
|
||||||
|
|
||||||
/// Decodes a given NIP-19 code into a [Map] of type and data.
|
|
||||||
Map<String, dynamic> decode(String nip19);
|
|
||||||
}
|
|
@ -1,306 +0,0 @@
|
|||||||
library impl.nip19;
|
|
||||||
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:convert/convert.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:bip340/bip340.dart' as bip340;
|
|
||||||
|
|
||||||
import 'package:bech32/bech32.dart';
|
|
||||||
|
|
||||||
import 'models.dart';
|
|
||||||
import 'nip19_api.dart';
|
|
||||||
|
|
||||||
typedef TLV = Map<int, List<Uint8List>>;
|
|
||||||
|
|
||||||
class Nip19Impl implements Nip19 {
|
|
||||||
static const _bech32MaxSize = 5000;
|
|
||||||
|
|
||||||
List<int> _convertBits(List<int> data, int fromBits, int toBits, bool pad) {
|
|
||||||
int acc = 0;
|
|
||||||
int bits = 0;
|
|
||||||
List<int> ret = [];
|
|
||||||
for (int value in data) {
|
|
||||||
acc = (acc << fromBits) | value;
|
|
||||||
bits += fromBits;
|
|
||||||
while (bits >= toBits) {
|
|
||||||
bits -= toBits;
|
|
||||||
ret.add((acc >> bits) & ((1 << toBits) - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pad) {
|
|
||||||
if (bits > 0) {
|
|
||||||
ret.add((acc << (toBits - bits)) & ((1 << toBits) - 1));
|
|
||||||
}
|
|
||||||
} else if (bits >= fromBits || (acc & ((1 << bits) - 1)) != 0) {
|
|
||||||
throw Exception('[!] Invalid padding');
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _encodeBytes(String prefix, String hex) {
|
|
||||||
final bytes = HexUtil.decode(hex);
|
|
||||||
List<int> fiveBitWords = _convertBits(bytes, 8, 5, true);
|
|
||||||
var bech32String = const Bech32Codec().encode(Bech32(prefix, fiveBitWords));
|
|
||||||
return bech32String;
|
|
||||||
}
|
|
||||||
|
|
||||||
TLV _parseTLV(Uint8List data) {
|
|
||||||
TLV result = {};
|
|
||||||
Uint8List rest = data;
|
|
||||||
while (rest.isNotEmpty) {
|
|
||||||
int t = rest[0];
|
|
||||||
int l = rest[1];
|
|
||||||
Uint8List v = rest.sublist(2, 2 + l);
|
|
||||||
rest = rest.sublist(2 + l);
|
|
||||||
if (v.length < l) continue;
|
|
||||||
result[t] = result[t] ?? [];
|
|
||||||
result[t]?.add(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _concatBytes(List<Uint8List> bytesList) {
|
|
||||||
int length = bytesList.fold(0, (sum, bytes) => sum + bytes.length);
|
|
||||||
Uint8List result = Uint8List(length);
|
|
||||||
int offset = 0;
|
|
||||||
for (Uint8List bytes in bytesList) {
|
|
||||||
result.setRange(offset, offset + bytes.length, bytes);
|
|
||||||
offset += bytes.length;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _encodeTLV(TLV tlv) {
|
|
||||||
List<Uint8List> entries = [];
|
|
||||||
for (var entry in tlv.entries) {
|
|
||||||
for (var v in entry.value) {
|
|
||||||
Uint8List bytes = Uint8List(v.length + 2);
|
|
||||||
bytes.setRange(0, 1, [entry.key]);
|
|
||||||
bytes.setRange(1, 2, [v.length]);
|
|
||||||
bytes.setRange(2, v.length + 2, v);
|
|
||||||
entries.add(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _concatBytes(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String nsecEncode(String hex) {
|
|
||||||
return _encodeBytes('nsec', hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String npubEncode(String hex) {
|
|
||||||
return _encodeBytes('npub', hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String noteEncode(String hex) {
|
|
||||||
return _encodeBytes('note', hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String naddrEncode(AddressPointer addr) {
|
|
||||||
Uint8List kind = Uint8List(4)
|
|
||||||
..buffer.asByteData().setUint32(0, addr.kind, Endian.big);
|
|
||||||
|
|
||||||
var identifier = utf8.encode(addr.identifier);
|
|
||||||
List<Uint8List> relays = (addr.relays ?? [])
|
|
||||||
.map((url) => utf8.encode(url))
|
|
||||||
.toList()
|
|
||||||
.cast<Uint8List>();
|
|
||||||
Uint8List pubkeyBytes = Uint8List.fromList(HexUtil.decode(addr.pubkey));
|
|
||||||
|
|
||||||
TLV tlv = {
|
|
||||||
0: [identifier].cast<Uint8List>(),
|
|
||||||
1: relays,
|
|
||||||
2: [pubkeyBytes].cast<Uint8List>(),
|
|
||||||
3: [kind].cast<Uint8List>(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Uint8List data = _encodeTLV(tlv);
|
|
||||||
List<int> fiveBitWords = _convertBits(data, 8, 5, true);
|
|
||||||
var bech32String = const Bech32Codec()
|
|
||||||
.encode(Bech32('naddr', fiveBitWords), _bech32MaxSize);
|
|
||||||
return bech32String;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String neventEncode(EventPointer event) {
|
|
||||||
Uint8List id = Uint8List.fromList(HexUtil.decode(event.id));
|
|
||||||
List<Uint8List> relayUrls =
|
|
||||||
(event.relays ?? []).map(utf8.encode).toList().cast<Uint8List>();
|
|
||||||
List<Uint8List> author = event.author != null
|
|
||||||
? Uint8List.fromList(HexUtil.decode(event.author!))
|
|
||||||
.toList()
|
|
||||||
.cast<Uint8List>()
|
|
||||||
: [];
|
|
||||||
|
|
||||||
TLV tlv = {
|
|
||||||
0: [id],
|
|
||||||
1: relayUrls,
|
|
||||||
2: author,
|
|
||||||
};
|
|
||||||
|
|
||||||
Uint8List data = _encodeTLV(tlv);
|
|
||||||
|
|
||||||
List<int> fiveBitWords = _convertBits(data, 8, 5, true);
|
|
||||||
|
|
||||||
var bech32String = const Bech32Codec()
|
|
||||||
.encode(Bech32('nevent', fiveBitWords), _bech32MaxSize);
|
|
||||||
return bech32String;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String nprofileEncode(ProfilePointer profile) {
|
|
||||||
Uint8List pubkeyBytes = Uint8List.fromList(HexUtil.decode(profile.pubkey));
|
|
||||||
|
|
||||||
List<Uint8List> relayUrls =
|
|
||||||
(profile.relays ?? []).map(utf8.encode).toList().cast<Uint8List>();
|
|
||||||
|
|
||||||
TLV tlv = {
|
|
||||||
0: [pubkeyBytes],
|
|
||||||
1: relayUrls,
|
|
||||||
};
|
|
||||||
|
|
||||||
Uint8List data = _encodeTLV(tlv);
|
|
||||||
|
|
||||||
List<int> fiveBitWords = _convertBits(data, 8, 5, true);
|
|
||||||
|
|
||||||
var bech32String = const Bech32Codec()
|
|
||||||
.encode(Bech32('nprofile', fiveBitWords), _bech32MaxSize);
|
|
||||||
return bech32String;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> decode(String nip19) {
|
|
||||||
Bech32 bech32;
|
|
||||||
try {
|
|
||||||
bech32 = const Bech32Codec().decode(nip19, _bech32MaxSize);
|
|
||||||
} catch (e) {
|
|
||||||
throw ChecksumVerificationException('Checksum verification failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> data = _convertBits(bech32.data, 5, 8, false);
|
|
||||||
final prefix = bech32.hrp;
|
|
||||||
|
|
||||||
switch (prefix) {
|
|
||||||
case 'nprofile':
|
|
||||||
TLV tlv = _parseTLV(Uint8List.fromList(data));
|
|
||||||
if (tlv[0]?.isEmpty ?? true) {
|
|
||||||
throw Exception('missing TLV 0 for nprofile');
|
|
||||||
}
|
|
||||||
if (tlv[0]?.isNotEmpty ?? false) {
|
|
||||||
if (tlv[0]![0].length != 32) {
|
|
||||||
throw Exception('TLV 0 should be 32 bytes');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Exception('missing TLV 0 for nprofile');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'type': 'nprofile',
|
|
||||||
'data': {
|
|
||||||
'pubkey': HexUtil.encode(tlv[0]![0]),
|
|
||||||
'relays': tlv[1]?.map((d) => utf8.decode(d)).toList() ?? [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'nevent':
|
|
||||||
TLV tlv = _parseTLV(Uint8List.fromList(data));
|
|
||||||
if (tlv[0] == null) {
|
|
||||||
throw Exception('missing TLV 0 for nevent');
|
|
||||||
}
|
|
||||||
if (tlv[0]![0].length != 32) {
|
|
||||||
throw Exception('TLV 0 should be 32 bytes');
|
|
||||||
}
|
|
||||||
if (tlv[2] != null && tlv[2]![0].length != 32) {
|
|
||||||
throw Exception('TLV 2 should be 32 bytes');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'type': 'nevent',
|
|
||||||
'data': {
|
|
||||||
'id': HexUtil.encode(tlv[0]![0]),
|
|
||||||
'relays': tlv[1] != null
|
|
||||||
? tlv[1]!.map((e) => utf8.decode(e)).toList()
|
|
||||||
: [],
|
|
||||||
'author': tlv[2] != null ? HexUtil.encode(tlv[2]![0]) : null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'naddr':
|
|
||||||
TLV tlv = _parseTLV(Uint8List.fromList(data));
|
|
||||||
if (tlv[0] == null) {
|
|
||||||
throw Exception('missing TLV 0 for naddr');
|
|
||||||
}
|
|
||||||
if (tlv[0] == null) {
|
|
||||||
throw Exception('missing TLV 0 for naddr');
|
|
||||||
}
|
|
||||||
if (tlv[0] == null) {
|
|
||||||
throw Exception('missing TLV 0 for naddr');
|
|
||||||
}
|
|
||||||
if (tlv[3] == null) {
|
|
||||||
throw Exception('missing TLV 3 for naddr');
|
|
||||||
}
|
|
||||||
if (tlv[3]![0].length != 4) {
|
|
||||||
throw Exception('TLV 3 should be 4 bytes');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'type': 'naddr',
|
|
||||||
'data': {
|
|
||||||
'identifier': utf8.decode(tlv[0]![0]),
|
|
||||||
'pubkey': HexUtil.encode(tlv[2]![0]),
|
|
||||||
'kind': int.parse(HexUtil.encode(tlv[3]![0]), radix: 16),
|
|
||||||
'relays': tlv[1] != null
|
|
||||||
? tlv[1]!.map((d) => utf8.decode(d)).toList()
|
|
||||||
: [],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
case 'nsec':
|
|
||||||
case 'npub':
|
|
||||||
case 'note':
|
|
||||||
return {'type': prefix, 'data': HexUtil.encode(data)};
|
|
||||||
default:
|
|
||||||
throw Exception('unknown prefix $prefix');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Bip340Util {
|
|
||||||
static String getPublicKey(
|
|
||||||
String privateKey,
|
|
||||||
) =>
|
|
||||||
bip340.getPublicKey(
|
|
||||||
privateKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
static String sign(String privateKey, String id, String aux) => bip340.sign(
|
|
||||||
privateKey,
|
|
||||||
id,
|
|
||||||
aux,
|
|
||||||
);
|
|
||||||
|
|
||||||
static bool verify(String publicKey, String id, String signature) =>
|
|
||||||
bip340.verify(publicKey, id, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
class HexUtil {
|
|
||||||
static String encode(List<int> bytes) {
|
|
||||||
return hex.encode(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<int> decode(String str) {
|
|
||||||
return hex.decode(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String generate64RandomHexChars() {
|
|
||||||
final random = Random.secure();
|
|
||||||
final randomBytes = List<int>.generate(32, (i) => random.nextInt(256));
|
|
||||||
return encode(randomBytes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,7 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _selectedTap,
|
index: _selectedTap,
|
||||||
children: [
|
children: [
|
||||||
HomeScreen(),
|
// HomeScreen(),
|
||||||
MessageScreen(),
|
MessageScreen(),
|
||||||
ProfileScreen(),
|
ProfileScreen(),
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:drifter/models/nip19/nip19_api.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:drifter/models/keys.dart';
|
import 'package:drifter/models/keys.dart';
|
||||||
@ -26,7 +25,7 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
TextEditingController publicKeyInput = TextEditingController();
|
TextEditingController publicKeyInput = TextEditingController();
|
||||||
|
|
||||||
// final keyGenerator = KeyApi();
|
// final keyGenerator = KeyApi();
|
||||||
final nip19 = Nip19();
|
// final nip19 = Nip19();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -37,18 +36,21 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Future<bool> generateNewKeys() async {
|
Future<bool> generateNewKeys() async {
|
||||||
final newPrivateKey = await Nostr.instance.keysService.generatePrivateKey();
|
final newPrivateKey = await Nostr.instance.keysService.generatePrivateKey();
|
||||||
// final newPrivateKey = keyGenerator.generatePrivateKey();
|
// final newPrivateKey = keyGenerator.generatePrivateKey();
|
||||||
final nsec = nip19.nsecEncode(newPrivateKey);
|
final nsec =
|
||||||
final nsecDecoded = nip19.decode(nsec);
|
Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey);
|
||||||
assert(nsecDecoded['type'] == 'nsec');
|
final nsecDecoded =
|
||||||
assert(nsecDecoded['data'] == newPrivateKey);
|
Nostr.instance.keysService.decodeNsecKeyToPrivateKey(nsec);
|
||||||
|
// assert(nsecDecoded['type'] == 'nsec');
|
||||||
|
// assert(nsecDecoded['data'] == newPrivateKey);
|
||||||
|
|
||||||
final newPublicKey = await Nostr.instance.keysService
|
final newPublicKey = await Nostr.instance.keysService
|
||||||
.derivePublicKey(privateKey: newPrivateKey);
|
.derivePublicKey(privateKey: newPrivateKey);
|
||||||
// final newPublicKey = keyGenerator.getPublicKey(newPrivateKey);
|
// final newPublicKey = keyGenerator.getPublicKey(newPrivateKey);
|
||||||
final npub = nip19.npubEncode(newPublicKey);
|
final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey);
|
||||||
final npubDecoded = nip19.decode(npub);
|
final npubDecoded =
|
||||||
assert(npubDecoded['type'] == 'npub');
|
Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub);
|
||||||
assert(npubDecoded['data'] == newPublicKey);
|
// assert(npubDecoded['type'] == 'npub');
|
||||||
|
// assert(npubDecoded['data'] == newPublicKey);
|
||||||
return await _addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub);
|
return await _addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,8 +151,10 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
AppColors.mainDarkBlue)),
|
AppColors.mainDarkBlue)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
keysExistDialog(
|
keysExistDialog(
|
||||||
nip19.npubEncode(Keys.publicKey),
|
Nostr.instance.keysService
|
||||||
nip19.nsecEncode(Keys.privateKey),
|
.encodePublicKeyToNpub(Keys.publicKey),
|
||||||
|
Nostr.instance.keysService
|
||||||
|
.encodePrivateKeyToNsec(Keys.privateKey),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
Loading…
Reference in New Issue
Block a user