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 6427f60
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 168 deletions.
38 changes: 4 additions & 34 deletions lib/src/crypto/kepler.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// credit: https://github.com/tjcampanella/kepler/blob/master/lib/src/kepler.dart

import 'package:convert/convert.dart';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'operator.dart';

///
/// From archive repo: https://github.com/tjcampanella/kepler.git
///
class Kepler {
/// return a Bytes data secret
static List<List<int>> byteSecret(String privateString, String publicString) {
Expand All @@ -17,23 +15,15 @@ class Kepler {
final hexX = leftPadding(xs, 64);
final hexY = leftPadding(ys, 64);
final secretBytes = Uint8List.fromList(hex.decode('$hexX$hexY'));
final pair = [
secretBytes.sublist(0, 32),
secretBytes.sublist(32, 40),
];
return pair;
return [secretBytes.sublist(0, 32), secretBytes.sublist(32, 40)];
}

/// return a ECPoint data secret
static ECPoint rawSecret(String privateString, String publicString) {
final privateKey = loadPrivateKey(privateString);
final publicKey = loadPublicKey(publicString);
assert(privateKey.d != null && publicKey.Q != null);
final secret = scalarMultiple(
privateKey.d!,
publicKey.Q!,
);
return secret;
return scalarMultiple(privateKey.d!, publicKey.Q!);
}

static String leftPadding(String s, int width) {
Expand All @@ -45,26 +35,6 @@ class Kepler {
return "${paddingData.substring(0, paddingWidth)}$s";
}

static ECPoint scalarMultiple(BigInt k, ECPoint point) {
assert(isOnCurve(point));
assert((k % theN).compareTo(BigInt.zero) != 0);
assert(point.x != null && point.y != null);
if (k < BigInt.from(0)) {
return scalarMultiple(-k, pointNeg(point));
}
ECPoint? result;
ECPoint addend = point;
while (k > BigInt.from(0)) {
if (k & BigInt.from(1) > BigInt.from(0)) {
result = pointAdd(result, addend);
}
addend = pointAdd(addend, addend);
k >>= 1;
}
assert(isOnCurve(result!));
return result!;
}

/// return a privateKey from hex string
static ECPrivateKey loadPrivateKey(String storedkey) {
final d = BigInt.parse(storedkey, radix: 16);
Expand Down
10 changes: 2 additions & 8 deletions lib/src/crypto/operator.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'package:pointycastle/export.dart';
// credit: https://github.com/tjcampanella/kepler/blob/master/lib/src/operator.dart

///
/// From archive repo: https://github.com/tjcampanella/kepler.git
///
import 'package:pointycastle/export.dart';

BigInt theP = BigInt.parse(
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
Expand Down Expand Up @@ -80,10 +78,6 @@ ECPoint pointAdd(ECPoint? point1, ECPoint? point2) {
final x2 = point2.x!.toBigInteger();
final y2 = point2.y!.toBigInteger();

// assert(x1 != x2 && y1 == y2);
// if (x1 == x2 && y1 != y2) {
// return null;
// }
BigInt m;
if (x1 == x2) {
m = (BigInt.from(3) * x1! * x1 + point1.curve.a!.toBigInteger()!) *
Expand Down
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 6427f60

Please sign in to comment.