Skip to content

Commit

Permalink
refactor: PR #26
Browse files Browse the repository at this point in the history
  • Loading branch information
ethicnology committed Apr 20, 2023
1 parent cc429de commit e6add66
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 126 deletions.
23 changes: 4 additions & 19 deletions lib/src/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,27 +128,26 @@ class Event {
);
}

/// Instantiate Event object from the minimum available data
/// Instantiate Event object from the minimum needed data
///
/// ```dart
///Event event = Event.from(
/// kind: 1,
/// tags: [],
/// content: "",
/// privkey:
/// "5ee1c8000ab28edd64d74a7d951ac2dd559814887b1b9e1ac7c5f89e96125c12",
///);
///```
factory Event.from({
int createdAt = 0,
int? createdAt,
required int kind,
required List<List<String>> tags,
List<List<String>> tags = const [],
required String content,
required String privkey,
String? subscriptionId,
bool verify = false,
}) {
if (createdAt == 0) createdAt = currentUnixTimestampSeconds();
createdAt ??= currentUnixTimestampSeconds();
final pubkey = bip340.getPublicKey(privkey).toLowerCase();

final id = _processEventId(
Expand Down Expand Up @@ -271,20 +270,6 @@ class Event {
);
}

factory Event.quick(
String content,
String privkey,
) {
Event event = Event.partial();
event.kind = 1;
event.content = content;
event.createdAt = currentUnixTimestampSeconds();
event.pubkey = bip340.getPublicKey(privkey).toLowerCase();
event.id = event.getEventId();
event.sig = event.getSignature(privkey);
return event;
}

/// To obtain the event.id, we sha256 the serialized event.
/// The serialization is done over the UTF-8 JSON-serialized string (with no white space or line breaks) of the following structure:
///
Expand Down
135 changes: 48 additions & 87 deletions lib/src/nips/nip_004/crypto.dart
Original file line number Diff line number Diff line change
@@ -1,105 +1,66 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:nostr/src/crypto/kepler.dart';
import 'package:pointycastle/export.dart';

import '../../crypto/kepler.dart';

class Nip4 {
static Map<String, List<List<int>>> gMapByteSecret = {};
// pointy castle source https://github.com/PointyCastle/pointycastle/blob/master/tutorials/aes-cbc.md
// https://github.com/bcgit/pc-dart/blob/master/tutorials/aes-cbc.md
// 3 https://github.com/Dhuliang/flutter-bsv/blob/42a2d92ec6bb9ee3231878ffe684e1b7940c7d49/lib/src/aescbc.dart

// Encrypt data using self private key in nostr format ( with trailing ?iv=)
static String cipher(
String privkey,
String pubkey,
String plaintext,
) {
Uint8List uintInputText = Utf8Encoder().convert(plaintext);
final secretIV = Kepler.byteSecret(privkey, pubkey);
final key = Uint8List.fromList(
secretIV[0],
static Uint8List generateNonce() {
final random = Random.secure();
return Uint8List.fromList(
List<int>.generate(16, (i) => random.nextInt(256)),
);

// generate iv https://stackoverflow.com/questions/63630661/aes-engine-not-initialised-with-pointycastle-securerandom
FortunaRandom fr = FortunaRandom();
final sGen = Random.secure();
fr.seed(KeyParameter(
Uint8List.fromList(List.generate(32, (_) => sGen.nextInt(255)))));
final iv = fr.nextBytes(16);

CipherParameters params = PaddedBlockCipherParameters(
ParametersWithIV(KeyParameter(key), iv), null);

PaddedBlockCipherImpl cipherImpl =
PaddedBlockCipherImpl(PKCS7Padding(), CBCBlockCipher(AESEngine()));

cipherImpl.init(
true, // means to encrypt
params as PaddedBlockCipherParameters<CipherParameters?,
CipherParameters?>);

// allocate space
final Uint8List outputEncodedText = Uint8List(uintInputText.length + 16);

var offset = 0;
while (offset < uintInputText.length - 16) {
offset += cipherImpl.processBlock(
uintInputText, offset, outputEncodedText, offset);
}

//add padding
offset +=
cipherImpl.doFinal(uintInputText, offset, outputEncodedText, offset);
final Uint8List finalEncodedText = outputEncodedText.sublist(0, offset);

String stringIv = base64.encode(iv);
String outputPlainText = base64.encode(finalEncodedText);
outputPlainText = "$outputPlainText?iv=$stringIv";
return outputPlainText;
}

static String decipher(
static String cipher(
String privkey,
String pubkey,
String ciphertext, [
String nonce = "",
]) {
Uint8List cipherText = base64.decode(ciphertext);
List<List<int>> byteSecret = gMapByteSecret[pubkey] ?? [];
if (byteSecret.isEmpty) {
byteSecret = Kepler.byteSecret(privkey, pubkey);
gMapByteSecret[pubkey] = byteSecret;
String payload,
bool cipher, {
String? nonce,
}) {
// if cipher=false –> decipher –> nonce needed
if (!cipher && nonce == null) throw Exception("missing nonce");

// init variables
Uint8List input, output, iv;
if (!cipher && nonce != null) {
input = base64.decode(payload);
output = Uint8List(input.length);
iv = base64.decode(nonce);
} else {
input = Utf8Encoder().convert(payload);
output = Uint8List(input.length + 16);
iv = generateNonce();
}
final secretIV = byteSecret;
final key = Uint8List.fromList(secretIV[0]);
final iv = nonce.length > 6
? base64.decode(nonce)
: Uint8List.fromList(secretIV[1]);

CipherParameters params = PaddedBlockCipherParameters(
ParametersWithIV(KeyParameter(key), iv), null);

PaddedBlockCipherImpl cipherImpl =
PaddedBlockCipherImpl(PKCS7Padding(), CBCBlockCipher(AESEngine()));

cipherImpl.init(
false,
params as PaddedBlockCipherParameters<CipherParameters?,
CipherParameters?>);
final Uint8List finalPlainText =
Uint8List(cipherText.length); // allocate space
// params
List<List<int>> keplerSecret = Kepler.byteSecret(privkey, pubkey);
var key = Uint8List.fromList(keplerSecret[0]);
var params = PaddedBlockCipherParameters(
ParametersWithIV(KeyParameter(key), iv),
null,
);
var algo = PaddedBlockCipherImpl(
PKCS7Padding(),
CBCBlockCipher(AESEngine()),
);

// processing
algo.init(cipher, params);
var offset = 0;
while (offset < cipherText.length - 16) {
offset +=
cipherImpl.processBlock(cipherText, offset, finalPlainText, offset);
while (offset < input.length - 16) {
offset += algo.processBlock(input, offset, output, offset);
}
offset += algo.doFinal(input, offset, output, offset);
Uint8List result = output.sublist(0, offset);

if (cipher) {
String stringIv = base64.encode(iv);
String plaintext = base64.encode(result);
return "$plaintext?iv=$stringIv";
} else {
return Utf8Decoder().convert(result);
}
//remove padding
offset += cipherImpl.doFinal(cipherText, offset, finalPlainText, offset);
Uint8List result = finalPlainText.sublist(0, offset);
return Utf8Decoder().convert(result);
}
}
44 changes: 24 additions & 20 deletions lib/src/nips/nip_004/event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import 'package:nostr/src/nips/nip_004/crypto.dart';
import 'package:nostr/src/utils.dart';

class EncryptedDirectMessage extends Event {
static Map<String, List<List<int>>> gMapByteSecret = {};

EncryptedDirectMessage(Event event)
: super(
event.id,
Expand All @@ -31,36 +29,42 @@ class EncryptedDirectMessage extends Event {
event.tags = [
['p', receiverPubkey]
];
event.content = Nip4.cipher(senderPrivkey, '02$receiverPubkey', message);
event.content = Nip4.cipher(
senderPrivkey,
'02$receiverPubkey',
message,
true,
);
event.id = event.getEventId();
event.sig = event.getSignature(senderPrivkey);
return EncryptedDirectMessage(event);
}

String? get receiverPubkey => findPubkey();

String get nonce {
List<String> split = content.split("?iv=");
if (split.length != 2) throw Exception("invalid nip4 content");
return split[1];
}

String getCiphertext(String senderPrivkey, String receiverPubkey) {
String ciphertext =
Nip4.cipher(senderPrivkey, '02$receiverPubkey', content);
return ciphertext;
return Nip4.cipher(senderPrivkey, '02$receiverPubkey', content, true);
}

String getPlaintext(String receiverPrivkey, [String senderPubkey=""]) {
if (senderPubkey.length == 0) {
senderPubkey = pubkey;
}
String getPlaintext(String receiverPrivkey) {
String ciphertext = content.split("?iv=")[0];
String plaintext = "";
int ivIndex = content.indexOf("?iv=");
if( ivIndex <= 0) {
print("Invalid content for dm, could not get ivIndex: $content");
return plaintext;
}
String iv = content.substring(ivIndex + "?iv=".length, content.length);
String ciphertext = content.substring(0, ivIndex);
try {
plaintext = Nip4.decipher(receiverPrivkey, "02$senderPubkey", ciphertext, iv);
} catch(e) {
print("Fail to decrypt: ${e}");
plaintext = Nip4.cipher(
receiverPrivkey,
"02$pubkey",
ciphertext,
false,
nonce: nonce,
);
} catch (e) {
throw Exception("Fail to decipher: $e");
}
return plaintext;
}
Expand Down

0 comments on commit e6add66

Please sign in to comment.