GithubHelp home page GithubHelp logo

mixinnetwork / libsignal_protocol_dart Goto Github PK

View Code? Open in Web Editor NEW
156.0 10.0 43.0 5.73 MB

Signal Protocol library for Dart/Flutter

Home Page: https://pub.dev/packages/libsignal_protocol_dart

License: GNU General Public License v3.0

Dart 100.00%

libsignal_protocol_dart's Introduction

libsignal_protocol_dart

pub package Dart CI

libsignal_protocol_dart is a pure Dart/Flutter implementation of the Signal Protocol.

Documentation

For more information on how the Signal Protocol works:

Usage

Install time

At install time, a signal client needs to generate its identity keys, registration id, and prekeys.

Future<void> install() async {
  final identityKeyPair = generateIdentityKeyPair();
  final registrationId = generateRegistrationId(false);

  final preKeys = generatePreKeys(0, 110);

  final signedPreKey = generateSignedPreKey(identityKeyPair, 0);

  final sessionStore = InMemorySessionStore();
  final preKeyStore = InMemoryPreKeyStore();
  final signedPreKeyStore = InMemorySignedPreKeyStore();
  final identityStore =
  InMemoryIdentityKeyStore(identityKeyPair, registrationId);

  for (var p in preKeys) {
    await preKeyStore.storePreKey(p.id, p);
  }
  await signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey);
}

Building a session

A signal client needs to implement four interfaces: IdentityKeyStore, PreKeyStore, SignedPreKeyStore, and SessionStore. These will manage loading and storing of identity, prekeys, signed prekeys, and session state.

Once those are implemented, you can build a session in this way:

  final remoteAddress = SignalProtocolAddress("remote", 1);
  final sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
      signedPreKeyStore, identityStore, remoteAddress);

  sessionBuilder.processPreKeyBundle(retrievedPreKey);

  final sessionCipher = SessionCipher(sessionStore, preKeyStore,
      signedPreKeyStore, identityStore, remoteAddress);
  final ciphertext = sessionCipher.encrypt(utf8.encode("Hello Mixin"));

  deliver(ciphertext);

Building a group session

If you wanna send message to a group, send a SenderKeyDistributionMessage to all members of the group.

  const alice = SignalProtocolAddress('+00000000001', 1);
  const groupSender = SenderKeyName('Private group', alice);
  final aliceStore = InMemorySenderKeyStore();
  final bobStore = InMemorySenderKeyStore();

  final aliceSessionBuilder = GroupSessionBuilder(aliceStore);
  final bobSessionBuilder = GroupSessionBuilder(bobStore);

  final aliceGroupCipher = GroupCipher(aliceStore, groupSender);
  final bobGroupCipher = GroupCipher(bobStore, groupSender);

  final sentAliceDistributionMessage =
      await aliceSessionBuilder.create(groupSender);
  final receivedAliceDistributionMessage =
      SenderKeyDistributionMessageWrapper.fromSerialized(
          sentAliceDistributionMessage.serialize());
  await bobSessionBuilder.process(
      groupSender, receivedAliceDistributionMessage);

  final ciphertextFromAlice = await aliceGroupCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin')));
  final plaintextFromAlice = await bobGroupCipher.decrypt(ciphertextFromAlice);
  // ignore: avoid_print
  print(utf8.decode(plaintextFromAlice));

libsignal_protocol_dart's People

Contributors

abhay-s-rawat avatar bcattaneo avatar boyan01 avatar crossle avatar fonkamloic avatar lemoony avatar rlch avatar robojones avatar tougee avatar yeungkc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libsignal_protocol_dart's Issues

Use Dart instead of Go implement the XEdDSA signature

Now we use Dart ffi call Go for XEdDSA signature, we should use Dart implement it

Uint8List sha512HashFunc(Uint8List m) {
  var digest = sha512.convert(m).bytes;
  digest[0] &= 248;
  digest[31] &= 127;
  digest[31] |= 64;
  return digest;
}

Uint8List signature(Uint8List privateKey, Uint8List message, Uint8List random) {
  var pk = Ed25519.publickey(sha512HashFunc, privateKey);
  // Calculate r
  var diversifier = Uint8List.fromList([
    0xFE,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF,
    0xFF
  ]);

  var output = new AccumulatorSink<Digest>();
  var input = sha512.startChunkedConversion(output);
  input.add(diversifier);
  input.add(privateKey);
  input.add(message);
  input.add(random);
  input.close();
  var r = output.events.single.bytes;

Key serialization issue: InvalidKeyException - Invalid signature on device key!

Hey,

What's the best way of serializing each key consistently, so that they are properly stored in a database?
I've found that due to combining these bytes,

class DjbECPublicKey extends ECPublicKey {
  final Uint8List _publicKey;

  DjbECPublicKey(this._publicKey);

  @override
  int getType() {
    return Curve.djbType;
  }

  @override
  Uint8List serialize() {
    return ByteUtil.combine([
      Uint8List.fromList([Curve.djbType]),
      _publicKey
    ]);
  }

you actually end up with 0x05 prepended to the Uint8List when .serialize() is called on the key (given that it's a DjbECPublicKey). This is causing the InvalidKeyException whenever processPreKeyBundle() is called.

InvalidMessageException

I got the error

InvalidMessageException - No valid sessions. [InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!, InvalidMessageException - Bad Mac!][0]

First time installing the app it works fine and second time when I reopens the app the message got encrypted but not decrypted and giving this above error.

[REQUEST] Remove print to stdout "We've already setup a session for this V3 message, letting bundled message fall through..."

Hi,

Would it be possible to remove, or at least make it conditional in some way, this "log message"?

It is found in lib\src\session_builder.dart, specifically this section:

class SessionBuilder {
  ...

  Future<Optional<int>> processV3(
      SessionRecord sessionRecord, PreKeySignalMessage message) async {
    if (sessionRecord.hasSessionState(
        message.getMessageVersion(), message.getBaseKey().serialize())) {
      // ignore: avoid_print
      print(
          "We've already setup a session for this V3 message, letting bundled message fall through...");
      return const Optional.empty();
    }

    ...
  }

  ...
}

It is an undesired output that messes up my module's console output.

If you think it's a good idea, I could submit a PR and discuss there the best behavior for such logs

Distributing DistributionMessage

Hi!

I've been studying this package, examples and reading other issues for a few days now. What I fail to get my head around is how to distribute the DistributionMessage to other members in a group chat. Examples run fine but then all members (Alice, Bob & Dean) all are in the same function with direct access to all variables.

Currently I have written a small test program that stores the chat messages (cipher text) in a Firestore database. It works fine to "chat" between two different devices, but only because I am storing the DistributionMessage along with the cipherText. This is obviously not how it should be done. Should the DistriubutionMessage be sent to all members using the normal 1-to-1 session or some other mechanism?

Furthermore, should I every time I send a message go through every member of the group and .process their DistributionMessage before encrypting the message to be sent? Is this how one achieve the behaviour that new members cannot see old messages, because their distribution message was not included when the message was encrypted?

for each member in chat {
   await bobGroupSessionBuilder.process(
      member[GroupSender], member[DistributionMessage]);
}

Thankful for any guidance!

/Nik

Any examples of projects using this?

I am interested in using this library however I am not sure how exactly this is to be used. Is there any more examples or projects using this I can look at?

InvalidKeyException - Invalid signature on device key! on processPreKeyBundle

when FIRST TIME I send a message for encryption, its return a encrypted message.
but SECOND TIME it throw an error "Invalid signature on device key!"

MyCode:

>   InMemoryPreKeyStore preKeyStore;
>   InMemorySignedPreKeyStore signedPreKeyStore;
>   InMemoryIdentityKeyStore identityStore;
>   generateData(identityKeyPair, registerationId, preKeys) {
>     signedPreKeyStore = InMemorySignedPreKeyStore();
>     preKeyStore = InMemoryPreKeyStore();
>     SignedPreKeyRecord signedPreKey =
>         KeyHelper.generateSignedPreKey(identityKeyPair, 0);
>     identityStore = InMemoryIdentityKeyStore(identityKeyPair, registerationId);
>     for (var p in preKeys) {
>       preKeyStore.storePreKey(p.id, p);
>     }
>     signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey);
>   }
> 
>   Uint8List encryption(String text, UserModel conv, id) {
>     InMemorySessionStore sessionStore = InMemorySessionStore();
>     SignalProtocolAddress bobAddress = SignalProtocolAddress(conv.name, 1);
>     UserModel me = Get.find<DataService>().user;
>     List list = reConversion(
>       me.identityKeyPair,
>       me.preKeys,
>       me.signedPreKeyRecord,
>       conv.identityPublicKeyPair,
>       conv.preKeysIdPublicKeyPair,
>       conv.signedPreKeyPublicPair,
>     );
> 
>     generateData(
>       list[0],
>       me.registerationId,
>       list[1],
>     );
>     SessionBuilder sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
>         signedPreKeyStore, identityStore, bobAddress);
> 
>     PreKeyBundle retrievedPreKey = PreKeyBundle(
>       conv.registerationId,
>       1,
>       conv.preKeysId,
>       list[4],
>       conv.signedPreKeyId,
>       list[5],
>       conv.signedPreKeySignature,
>       list[3],
>     );
> 
>     sessionBuilder.processPreKeyBundle(retrievedPreKey);
> 
>     Get.find<DataService>()
>             .converstation
>             .firstWhere((CoversationModel element) => element.id == id)
>             .sessionCipher =
>         SessionCipher(sessionStore, preKeyStore, signedPreKeyStore,
>             identityStore, bobAddress);
> 
>     CiphertextMessage ciphertext = Get.find<DataService>()
>         .converstation
>         .firstWhere((CoversationModel element) => element.id == id)
>         .sessionCipher
>         .encrypt(Uint8List.fromList(utf8.encode(text)));
> 
>     sessionStore.deleteSession(bobAddress);
>     return ciphertext.serialize();
>   }
> 

That chunk of code throw an exception in file 'SessionBuilder.dart'


>   if (preKey.getSignedPreKey() != null &&
>         !Curve.verifySignature(
>             preKey.getIdentityKey().publicKey,
>             preKey.getSignedPreKey().serialize(),
>             preKey.getSignedPreKeySignature())) {
>       throw InvalidKeyException('Invalid signature on device key!');
>     }

file system relative paths not allowed in hardened programs

When running pub run test, libgodart.socannot be found and the stacktrace contains file system relative paths not allowed in hardened programs

git log --oneline
11fa11c (HEAD, origin/master, origin/HEAD, master) Update group session
dart --version
Dart VM version: 2.8.3 (stable) (Tue May 26 18:39:38 2020 +0200) on "macos_x64"

Expected a value of type 'int', but got one of type 'double' flutter web

I'm trying integrate Signal protocol in flutter web .

I get this error
Expected a value of type 'int', but got one of type 'double'

When i call this method
//3.0.0 nullsafety version
KeyHelper.generateSignedPreKey(identityKeyPair, signedPreKeyId);

//0.5.3 version
generateSignedPreKey(identityKeyPair, signedPreKeyId);

on both version I get same issue.

I was able to trace the issue. Issue is in edwards25519.dart.

RangeError (index): Index out of range: no indices are valid: 0

Hello,
I am getting Index out of range exception while encrypting message with bobGroupCipher in group session.

below is code i am using:

void TestGroup() async {

final senderAddress = SignalProtocolAddress('+00000000001', 1);
final groupSender = SenderKeyName('Private group', senderAddress);

final aliceStore = InMemorySenderKeyStore();
final bobStore = InMemorySenderKeyStore();

final aliceSessionBuilder = GroupSessionBuilder(aliceStore);
final bobSessionBuilder = GroupSessionBuilder(bobStore);

final aliceGroupCipher = GroupCipher(aliceStore, groupSender);
final bobGroupCipher = GroupCipher(bobStore, groupSender);

final sentAliceDistributionMessage =
    await aliceSessionBuilder.create(groupSender);
final receivedAliceDistributionMessage =
    SenderKeyDistributionMessageWrapper.fromSerialized(
        sentAliceDistributionMessage.serialize());
bobSessionBuilder.process(groupSender, receivedAliceDistributionMessage);

var ciphertextFromAlice = await aliceGroupCipher
    .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin')));
var plaintextFromAlice = await bobGroupCipher.decrypt(ciphertextFromAlice);
print("Alice msg to Bob : ${plaintextFromAlice}");

ciphertextFromAlice = await bobGroupCipher
    .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin')));
plaintextFromAlice = await aliceGroupCipher.decrypt(ciphertextFromAlice);
print("Bob msg to Alice  : ${plaintextFromAlice}");

   }

stacks trace
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: RangeError (index): Index out of range: no indices are valid: 0 [ ] #0 Uint8List.[] (dart:typed_data-patch/typed_data_patch.dart:2223:7) [ ] #1 load3 (package:ed25519_edwards/src/edwards25519.dart:95:12) [ ] #2 ScMulAdd (package:ed25519_edwards/src/edwards25519.dart:1273:22) [ ] #3 sign (package:libsignal_protocol_dart/src/ecc/ed25519.dart:81:3) [ ] #4 Curve.calculateSignature (package:libsignal_protocol_dart/src/ecc/curve.dart:125:14) [ ] #5 SenderKeyMessage._getSignature (package:libsignal_protocol_dart/src/protocol/sender_key_message.dart:79:20) [ ] #6 new SenderKeyMessage (package:libsignal_protocol_dart/src/protocol/sender_key_message.dart:27:9) [ ] #7 GroupCipher.encrypt (package:libsignal_protocol_dart/src/groups/group_cipher.dart:29:32) [ ] <asynchronous suspension> [ ] #8 _TestingScreenState.TestGroup (package:chillflix/screens/testingScreen.dart:209:27) [ ] <asynchronous suspension>

Use other cryptographic libraries?

Dear Mixin,
would it be possible to use other libraries like libsodium or Dart's cryptography package for the ed25510 or x25519 algorithms? Why did you choose to create your own dart ports of the respective Go libraries?

Thanks a lot!

PreKeyBundle to Server

Can anyone tell about how can i send PreKeyBundle to server.
I have implemented with some approach but theres a problem in converting Uint8List PreKey to ECPublicKey Class Object on getting PreKeyBundleModel back from the server.

I have created this PreKeyBundleModel which is coming from server after deserializing but i have to convert this PreKeyBundleModel to PreKeyBundle. Problem occurs when i want to convert Uinit8List PreKey to ECPublicKey as PreKeyBundle requires ECPublicKey Object as parameters. Is there any method to convert Uini8List to ECPublicKey.

`
@JsonSerializable()
class PreKeyBundleModel {
int? registrationId;
int? deviceId;
List? preKeys;
SignedPreKeyModel? signedPreKey;
String? identityKey;

PreKeyBundleModel({this.registrationId, this.deviceId, this.preKeys, this.signedPreKey, this.identityKey});

factory PreKeyBundleModel.fromJson(Map<String, dynamic> json) => _$PreKeyBundleModelFromJson(json);

Map<String, dynamic> toJson() => _$PreKeyBundleModelToJson(this);
}

@JsonSerializable()
class PreKeyModel {
int? id;
String? key;

PreKeyModel({this.id, this.key});

factory PreKeyModel.fromJson(Map<String, dynamic> json) => _$PreKeyModelFromJson(json);

Map<String, dynamic> toJson() => _$PreKeyModelToJson(this);
}

@JsonSerializable()
class SignedPreKeyModel {
int? id;
String? key;
String? signature;

SignedPreKeyModel({this.id, this.key, this.signature});

factory SignedPreKeyModel.fromJson(Map<String, dynamic> json) => _$SignedPreKeyModelFromJson(json);

Map<String, dynamic> toJson() => _$SignedPreKeyModelToJson(this);
}
`

Deserialize IdentityKey

I'm trying to deserialize an identity key that I stored in a file. For this, I used .serialize the key and then, to load it again, I use the IdentityKey.fromBytes method.

Here is a parameter required called offset. Is this always 0 or does it have a different meaning?

Thanks a lot!

Return nullables rather than throwing errors

abstract class PreKeyStore {
  Future<PreKeyRecord> loadPreKey(int preKeyId); //  throws InvalidKeyIdException; <=== cant we change this to nullable return value rather than throwing error ?

  Future<void> storePreKey(int preKeyId, PreKeyRecord record);

  Future<bool> containsPreKey(int preKeyId);

  Future<void> removePreKey(int preKeyId);
}

It is present in stores, if you allow I can submit a pull request with new version as this might be bad for previous code users.

Example code does not work

While not an issue for me personally, the example does not work:

MacBook:example $ dart main.dart 
Unhandled exception:
Assertion failed: Instance of 'InvalidKeyException'
#0      SessionState.getSenderRatchetKey (package:libsignal_protocol_dart/src/state/SessionState.dart:104:7)
#1      SessionCipher.encrypt (package:libsignal_protocol_dart/src/SessionCipher.dart:59:40)
#2      install (file:///Users/rhisiart/git/libsignal_protocol_dart/example/main.dart:38:34)
#3      main (file:///Users/rhisiart/git/libsignal_protocol_dart/example/main.dart:6:3)
#4      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#5      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

whisperType CiphertextMessage

I am but confused with the following types of CiphertextMessage.

When we will get whisperType type message ? I guess it will be message when reciever bundle had no 1 time key present , right ? In that case when creating PreKeyBundle the int _preKeyId, ECPublicKey _preKeyPublic should be nullable , right ? as stated in https://signal.org/docs/specifications/x3dh/

If the bundle does not contain a one-time prekey, she calculates:

    DH1 = DH(IKA, SPKB)
    DH2 = DH(EKA, IKB)
    DH3 = DH(EKA, SPKB)
    SK = KDF(DH1 || DH2 || DH3)

If the bundle does contain a one-time prekey, the calculation is modified to include an additional DH:

    DH4 = DH(EKA, OPKB)
    SK = KDF(DH1 || DH2 || DH3 || DH4)
  static const int whisperType = 2; //=> When we will get this ?
  static const int prekeyType = 3; //  When sender had one time pre key of receiver
  static const int senderKeyType = 4; // group msg type, I guess
  static const int senderKeyDistributionType = 5; // This is initial kdm msg , used in groups

bundle implementation as below

PreKeyBundle getPreKeyBundle(Map<String, dynamic> remoteBundle) {
    ECPublicKey tempPrePublicKey = Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["preKeys"].first['key']))
            .serialize(),
        1);
    int tempPreKeyId = remoteBundle["preKeys"].first['id'];
    int tempSignedPreKeyId = remoteBundle["signedPreKey"]['id'];
    ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["signedPreKey"]['key']))
            .serialize(),
        1);
    Uint8List? tempSignedPreKeySignature =
        base64Decode(remoteBundle["signedPreKey"]['signature']);
    IdentityKey tempIdentityKey = IdentityKey(Curve.decodePoint(
        DjbECPublicKey(base64Decode(remoteBundle["identityKey"])).serialize(),
        1));
    return PreKeyBundle(
      remoteBundle['registrationId'],
      1,
      tempPreKeyId,
      tempPrePublicKey,
      tempSignedPreKeyId,
      tempSignedPreKeyPublic,
      tempSignedPreKeySignature,
      tempIdentityKey,
    );
  }

GPL-License and its implications

I want to use this library in an open-source application. However, I don't want to release my work under GPL-3 because I don't like the license.

Does an application using your library constitute a derivative work and can it be released under a different open-source license?
Namely, I would prefer the MIT or Apache-2 license.

UntrustedIdentityException

I want to create an encrypted chat app. In the example below which reproduces the error, Bob and Jean want to send a message to Alice. The first message (in this case from Bob) can be decrypted without any problems, but the second message (from Jean) fails with the following UntrustedIdentityException exception:

Unhandled exception:
Instance of 'UntrustedIdentityException'
#0      SessionBuilder.process (package:libsignal_protocol_dart/src/session_builder.dart:42:7)
<asynchronous suspension>
#1      SessionCipher.decryptWithCallback (package:libsignal_protocol_dart/src/session_cipher.dart:108:9)
<asynchronous suspension>
#2      main (package:project/test.dart:171:5)
<asynchronous suspension>

This is the code I used to reproduces the error:

import 'dart:convert';
import 'dart:typed_data';

import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';

void main() async {
  //
  // BOB SETUP
  //
  final bobAddress = SignalProtocolAddress('bob', 1);
  final bobIdentityKeyPair = generateIdentityKeyPair();
  final bobRegistrationId = generateRegistrationId(false);

  final bobPreKeys = generatePreKeys(0, 110);
  final bobSignedPreKey = generateSignedPreKey(bobIdentityKeyPair, 0);

  final bobPreKeyStore = InMemoryPreKeyStore();
  final bobSessionStore = InMemorySessionStore();
  final bobSignedPreKeyStore = InMemorySignedPreKeyStore();
  final bobIdentityKeyStore =
      InMemoryIdentityKeyStore(bobIdentityKeyPair, bobRegistrationId);

  for (var p in bobPreKeys) {
    await bobPreKeyStore.storePreKey(p.id, p);
  }
  await bobSignedPreKeyStore.storeSignedPreKey(
    bobSignedPreKey.id,
    bobSignedPreKey,
  );

  //
  // JEAN SETUP
  //
  final jeanRegistrationId = generateRegistrationId(false);
  final jeanIdentityKeyPair = generateIdentityKeyPair();
  final jeanAddress = SignalProtocolAddress('jean', 1);

  final jeanPreKeys = generatePreKeys(0, 110);
  final jeanSignedPreKey = generateSignedPreKey(jeanIdentityKeyPair, 0);

  final jeanPreKeyStore = InMemoryPreKeyStore();
  final jeanSessionStore = InMemorySessionStore();
  final jeanSignedPreKeyStore = InMemorySignedPreKeyStore();
  final jeanIdentityKeyStore =
      InMemoryIdentityKeyStore(jeanIdentityKeyPair, jeanRegistrationId);

  final jeanSignalProtocolStore =
      InMemorySignalProtocolStore(jeanIdentityKeyPair, 1);

  for (var p in jeanPreKeys) {
    await jeanSignalProtocolStore.storePreKey(p.id, p);
  }
  await jeanSignalProtocolStore.storeSignedPreKey(
      jeanSignedPreKey.id, jeanSignedPreKey);

  //
  // ALICE SETUP
  //
  // Should get remote from the server
  final aliceRegistrationId = generateRegistrationId(false);
  final aliceIdentityKeyPair = generateIdentityKeyPair();
  final aliceAddress = SignalProtocolAddress('alice', 1);

  final alicePreKeys = generatePreKeys(0, 110);
  final aliceSignedPreKey = generateSignedPreKey(aliceIdentityKeyPair, 0);
  final aliceIdentityKeyStore =
      InMemoryIdentityKeyStore(aliceIdentityKeyPair, aliceRegistrationId);

  final aliceSignalProtocolStore =
      InMemorySignalProtocolStore(aliceIdentityKeyPair, 1);

  for (var p in alicePreKeys) {
    await aliceSignalProtocolStore.storePreKey(p.id, p);
  }
  await aliceSignalProtocolStore.storeSignedPreKey(
      aliceSignedPreKey.id, aliceSignedPreKey);

  //
  // Preparing connection
  //
  final bobSessionBuilder = SessionBuilder(
    bobSessionStore,
    bobPreKeyStore,
    bobSignedPreKeyStore,
    bobIdentityKeyStore,
    bobAddress,
  );
  await bobSessionBuilder.processPreKeyBundle(
    PreKeyBundle(
      aliceRegistrationId,
      1,
      alicePreKeys[0].id,
      alicePreKeys[0].getKeyPair().publicKey,
      aliceSignedPreKey.id,
      aliceSignedPreKey.getKeyPair().publicKey,
      aliceSignedPreKey.signature,
      aliceIdentityKeyPair.getPublicKey(),
    ),
  );

  final jeanSessionBuilder = SessionBuilder(
    jeanSessionStore,
    jeanPreKeyStore,
    jeanSignedPreKeyStore,
    jeanIdentityKeyStore,
    jeanAddress,
  );

  await jeanSessionBuilder.processPreKeyBundle(
    PreKeyBundle(
      aliceRegistrationId,
      1,
      alicePreKeys[0].id,
      alicePreKeys[0].getKeyPair().publicKey,
      aliceSignedPreKey.id,
      aliceSignedPreKey.getKeyPair().publicKey,
      aliceSignedPreKey.signature,
      aliceIdentityKeyPair.getPublicKey(),
    ),
  );

  //
  // ENCRYPTION
  //
  final bobSessionCipher = SessionCipher(
    bobSessionStore,
    bobPreKeyStore,
    bobSignedPreKeyStore,
    bobIdentityKeyStore,
    bobAddress,
  );
  final ciphertextFromBob = await bobSessionCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello from Bob')));

  final jeanSessionCipher = SessionCipher(
    jeanSessionStore,
    jeanPreKeyStore,
    jeanSignedPreKeyStore,
    jeanIdentityKeyStore,
    jeanAddress,
  );
  final ciphertextFromJean = await jeanSessionCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello from Jean')));

  //
  // DECRYPTION
  //
  final aliceSessionCipher = SessionCipher(
    aliceSignalProtocolStore.sessionStore,
    aliceSignalProtocolStore.preKeyStore,
    aliceSignalProtocolStore.signedPreKeyStore,
    aliceIdentityKeyStore,
    aliceAddress,
  );

  if (ciphertextFromBob.getType() == CiphertextMessage.prekeyType) {
    await aliceSessionCipher.decryptWithCallback(
        ciphertextFromBob as PreKeySignalMessage, (plaintext) {
      // ignore: avoid_print
      print(utf8.decode(plaintext));
    });
  }

  if (ciphertextFromJean.getType() == CiphertextMessage.prekeyType) {
    await aliceSessionCipher.decryptWithCallback(
        ciphertextFromJean as PreKeySignalMessage, (plaintext) {
      // ignore: avoid_print
      print(utf8.decode(plaintext));
    });
  }
}

How can I resolve this issue? What have I done/understood wrong?

Thanks in advance

Production usage?

Dear Mixin,

thanks a lot for providing such a great library - I'm thinking of using it in one of my production apps.
However, I have two questions regarding this:

  • Do you consider this library already stable? Is it used somewhere in production?
  • In your ratchet code (especially the ratchet session code) there is always hardcoded "Alice's session" and "Bob's session". Is this intended and real production code?

Thanks for your answer and effort!

RangeError - Group session with multiple participants

Can you provide and example with 3 group participants all sending a message? I can't get it to work for me. This is my code right now:

  final groupSenderAddress = SignalProtocolAddress('+00000000001', 1);
  final groupSender = SenderKeyName('Private group', groupSenderAddress);
  final aliceStore = InMemorySenderKeyStore();
  final bobStore = InMemorySenderKeyStore();
  final deanStore = InMemorySenderKeyStore();

  final aliceGroupSessionBuilder = GroupSessionBuilder(aliceStore);
  final bobGroupSessionBuilder = GroupSessionBuilder(bobStore);
  final deanGroupSessionBuilder = GroupSessionBuilder(deanStore);

  final aliceGroupCipher = GroupCipher(aliceStore, groupSender);
  final bobGroupCipher = GroupCipher(bobStore, groupSender);
  final deanGroupCipher = GroupCipher(deanStore, groupSender);

  await bobGroupSessionBuilder.process(
      groupSender, await aliceGroupSessionBuilder.create(groupSender));
  await deanGroupSessionBuilder.process(
      groupSender, await bobGroupSessionBuilder.create(groupSender));

  final ciphertextFromAlice = await aliceGroupCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello from Alice')));

  final ciphertextFromBob = await bobGroupCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello from Bob')));

  final ciphertextFromDean = await deanGroupCipher
      .encrypt(Uint8List.fromList(utf8.encode('Hello from Dean')));

  print(utf8.decode(await bobGroupCipher.decrypt(ciphertextFromAlice)));
  print(utf8.decode(await deanGroupCipher.decrypt(ciphertextFromBob)));
  print(utf8.decode(await aliceGroupCipher.decrypt(ciphertextFromDean)));

Which throws the following exception:

RangeError (index): Index out of range: no indices are valid: 0
#0      Uint8List.[] (dart:typed_data-patch/typed_data_patch.dart:2223:7)
#1      load3 (package:ed25519_edwards/src/edwards25519.dart:95:12)
#2      ScMulAdd (package:ed25519_edwards/src/edwards25519.dart:1273:22)
#3      sign (package:libsignal_protocol_dart/src/ecc/ed25519.dart:81:3)
#4      Curve.calculateSignature (package:libsignal_protocol_dart/src/ecc/curve.dart:125:14)
#5      SenderKeyMessage._getSignature (package:libsignal_protocol_dart/src/protocol/sender_key_message.dart:79:20)
#6      new SenderKeyMessage (package:libsignal_protocol_dart/src/protocol/sender_key_message.dart:27:9)
#7      GroupCipher.encrypt (package:libsignal_protocol_dart/src/groups/group_cipher.dart:29:32)
<asynchronous suspension>
#8      main (package:project/group.dart:34:29)
<asynchronous suspension>

How to share public keys

I am trying to use libsignal in my flutter web app, and currently facing this problem. I need to save the public keys and share them with a different user. My current approach is to serialize the key, convert to string, put it in a map<string, string>, jsonEncode the string and finally save it on server. Problem is when retrieving the key, i get this error Error: Expected a value of type 'ECPublicKey', but got one of type 'NativeUint8List'. The code I am using when retrieving is as follows:

var bundle = json.decode(otherUser.publicKey);
ECPublicKey preKeyPublic = Uint8List.fromList(jsonDecode(bundle['preKey']).cast<int>()) as ECPublicKey;
ECPublicKey signedPreKeyPublic = Uint8List.fromList(jsonDecode(bundle['signedPreKey']).cast<int>()) as ECPublicKey;
Uint8List signedPreKeySignature = Uint8List.fromList(jsonDecode(bundle['signature']).cast<int>());
IdentityKey identityKey = IdentityKey.fromBytes(Uint8List.fromList(jsonDecode(bundle['identityKeyPair']).cast<int>()), 0) ;

var recieverBundle = PreKeyBundle(registrationId, 0, 0, preKeyPublic, 0, signedPreKeyPublic, signedPreKeySignature, identityKey);

recieverBundle goes into the sessionBuilder. Due to the errors, I am now saving the entire keypair for prekey and signedprekey, since they have the fromBuffer function and making it available publicly. But I dont know if that would be a security issue, since common sense dictates private key should never be shared.

I am new to encryption in general, and a beginner in flutter web. I cant seem to figure out how else to save the public keys and retrieve them. Any help would be appreciated.

Requirement of integer registration id

For the Signal protocol to work, each USER needs to have a unique USER-ID together with a DEVICE-ID unique to the user. A user address is represented using the SignalProtocolAddress class in the package. This requires a NAME for the user provided as a string (e.g. a UUID, phone number or username) and a DEVICE-ID provided as an integer.

But the PreKeyBundle requires a registrationId of the user as an integer, making it impossible to use anything else than an integer as a unique user identifier. Using a username as a USER-ID is only possible when two unique user identifiers are keept (the username and the integer id).

Are those assumptions correct?

If so, would it be possible to remove the requirement for a integer registrationId in the PreKeyBundle class and change the type to a string to allow for an easier/possible use of usernames/UUIDs etc. as USER-IDs?
I could't find any mention of this requirement in the Signal docs. If I missed them, could you please give me a hint as to where to look?

group session unhandled exception

while running groupSession() function complier five error:

Unhandled exception:
NoSessionException - No key state in record!
#0      GroupCipher.encrypt (package:libsignal_protocol_dart/src/groups/GroupCipher.dart:37:7)
#1      groupSessioin (package:signaltest/main.dart:92:16)
#2      main (package:signaltest/main.dart:8:3)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

please help!!!

Failed to load dynamic library (Flutter app)

By cut'n pasting your example code into a default Flutter app, it fails as it is unable to load the dynamic library. I would appreciate it if you could advise as to what additional steps I should take beyond what is stated in https://pub.dev/packages/libsignal_protocol_dart/install

Thanks.

flutter run
Launching lib/main.dart on Pixel in debug mode...
Running Gradle task 'assembleDebug'...                                  
Running Gradle task 'assembleDebug'... Done                        30.4s
✓ Built build/app/outputs/apk/debug/app-debug.apk.
Installing build/app/outputs/apk/app.apk...                        25.1s
I/flutter (30950): install
E/flutter (30950): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Invalid argument(s): Failed to load dynamic library (dlopen failed: library "/main.dartlibgodart.so" not found)
E/flutter (30950): #0      _open (dart:ffi-patch/ffi_dynamic_library_patch.dart:13:55)
E/flutter (30950): #1      new DynamicLibrary.open (dart:ffi-patch/ffi_dynamic_library_patch.dart:22:12)
E/flutter (30950): #2      dlopenPlatformSpecific (package:libsignal_protocol_dart/src/ecc/dylib_utils.dart:16:29)
E/flutter (30950): #3      libsignal (package:libsignal_protocol_dart/src/ecc/SignCurve25519.dart:23:5)
E/flutter (30950): #4      libsignal (package:libsignal_protocol_dart/src/ecc/SignCurve25519.dart:22:7)
E/flutter (30950): #5      sign (package:libsignal_protocol_dart/src/ecc/SignCurve25519.dart:28:24)
E/flutter (30950): #6      Curve.calculateSignature (package:libsignal_protocol_dart/src/ecc/Curve.dart:103:14)
E/flutter (30950): #7      KeyHelper.generateSignedPreKey (package:libsignal_protocol_dart/src/util/KeyHelper.dart:46:27)
E/flutter (30950): #8      install (package:example3/main.dart:18:32)
E/flutter (30950): #9      main (package:example3/main.dart:7:3)
E/flutter (30950): #10     _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:241:25)
E/flutter (30950): #11     _rootRun (dart:async/zone.dart:1184:13)
E/flutter (30950): #12     _CustomZone.run (dart:async/zone.dart:1077:19)
E/flutter (30950): #13     _runZoned (dart:async/zone.dart:1619:10)
E/flutter (30950): #14     runZonedGuarded (dart:async/zone.dart:1608:12)
E/flutter (30950): #15     _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:233:5)
E/flutter (30950): #16     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
E/flutter (30950): #17     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
flutter doctor -v
[✓] Flutter (Channel stable, v1.17.5, on Mac OS X 10.15.5 19F101, locale en-GB)
    • Flutter version 1.17.5 at /Users/rhisiart/flutter
    • Framework revision 8af6b2f038 (5 weeks ago), 2020-06-30 12:53:55 -0700
    • Engine revision ee76268252
    • Dart version 2.8.4

 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at /Users/rhisiart/Library/android/sdk
    • Platform android-29, build-tools 29.0.3
    • ANDROID_SDK_ROOT = /Users/rhisiart/Library/android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.5)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.5, Build version 11E608c
    • CocoaPods version 1.9.3

[✓] Android Studio (version 3.6)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 45.1.1
    • Dart plugin version 192.8052
    • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)

[✓] VS Code (version 1.47.3)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.13.1

[✓] Connected device (1 available)
    • Pixel • 192.168.1.65:5555 • android-arm64 • Android 10 (API 29)

• No issues found!

pubspec.yaml

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  libsignal_protocol_dart: ^0.0.2

dependency_overrides:
  collection: ^1.14.12

main.dart

void main() {
  install();
  runApp(MyApp());
}

// This is a cut'n paste from your example code
void install() {
  debugPrint('install');
  var identityKeyPair = KeyHelper.generateIdentityKeyPair();
  var registerationId = KeyHelper.generateRegistrationId(false);

  var preKeys = KeyHelper.generatePreKeys(0, 110);

  ... and so on

Instance of 'InvalidKeyException'

I am getting the following error when tryng to encrypt text:

Assertion failed: Instance of 'InvalidKeyException'
#0 SessionState.getSenderRatchetKey (package:libsignal_protocol_dart/src/state/SessionState.dart:103:7)
#1 SessionCipher.encrypt (package:libsignal_protocol_dart/src/SessionCipher.dart:59:40)
#2 SignalProtocol.sessionEncrypt (package:Okuna/services/signal_protocol.dart:166:50)
#3 OBDConversationState.sendMessage (package:Okuna/pages/home/pages/channel_chat/conversation.dart:155:26)
#4 ChatInputToolbar._sendMessage (package:dash_chat/src/chat_input_toolbar.dart:163:19)
#5 ChatInputToolbar.build. (package:dash_chat/src/chat_input_toolbar.dart:149:31)
#6 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:993:19)
#7 _InkResponseState.build. (package:flutter/src/material/ink_well.dart:1111:38)
#8 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:183:24)
#9 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:598:11)
#10 BaseTapGestureRecognizer._checkUp

This is my code - SignalProtocol class:

`class SignalProtocol{

  IdentityKeyPair identityKeyPair;
  int registrationId;

  List<PreKeyRecord> preKeys;

  SignedPreKeyRecord signedPreKey;

  int userId, deviceid;

  UserService userService;

  // No way to store
  InMemorySessionStore sessionStore;
  InMemoryPreKeyStore preKeyStore;
  InMemorySignedPreKeyStore signedPreKeyStore;
  InMemoryIdentityKeyStore identityStore;
  PreKeyBundle myBundle;

  Map<String, String> bundleMap = Map<String, String>();

  SignalProtocol({@required this.userService});

  void install(int UserId, int DeviceId) async {


    await userService.getIdentityKeyPair().then((s){
      if(s==null){
        identityKeyPair = KeyHelper.generateIdentityKeyPair();
      }else{
        Uint8List d = Uint8List.fromList(jsonDecode(s).cast<int>());
        identityKeyPair = IdentityKeyPair.fromSerialized(d);
      }
    });

    await userService.getUserId().then((s){
      if(s==null){
        userId = UserId;
      }else{
        userId = int.parse(s);
      }
    });
    await userService.getRegistrationId().then((s){
      if(s==null){
        registrationId = KeyHelper.generateRegistrationId(false);
      }else{
        registrationId = int.parse(s);
      }
    });
    await userService.getDeviceid().then((s){
      if(s==null){
        deviceid = DeviceId;
      }else{
        deviceid = int.parse(s);
      }
    });

    await userService.getSignedPreKeyRecord().then((s){
      if(s==null){
        signedPreKey = KeyHelper.generateSignedPreKey(identityKeyPair, 0);
      }else{
        Uint8List d = Uint8List.fromList(jsonDecode(s).cast<int>());
        signedPreKey = SignedPreKeyRecord.fromSerialized(d);
      }
    });
    await userService.getPreKeyRecordList().then((s){
      // print('sp prekeys - ${s} ');
      if(s==null){
        preKeys = KeyHelper.generatePreKeys(0, 110);
        print('prekeys init - ${preKeys.runtimeType}}');
      }else{
        print('jsonDecode prekeys ${(jsonDecode(s)).runtimeType}');

        var stringList = jsonDecode(s) as List<dynamic>;
        print('sp string list - ${stringList.runtimeType}');
        preKeys = stringList.map((e) {
          print('inloop ${e.runtimeType}');
          var elist = Uint8List.fromList((e as List<dynamic>).cast<int>());
          print('after cast ${elist.runtimeType}');
          return PreKeyRecord.fromBuffer(elist);
        }
             ).toList();

      }
      print('prekeys - ${preKeys.length} ');
    });

    sessionStore = InMemorySessionStore();
    preKeyStore = InMemoryPreKeyStore();
    signedPreKeyStore = InMemorySignedPreKeyStore();
    identityStore = InMemoryIdentityKeyStore(identityKeyPair, registrationId);

    print('prekeys - ${preKeys.length} ');
    for (var p in preKeys) {
      preKeyStore.storePreKey(p.id, p);
    }
    signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey);

    myBundle = PreKeyBundle(
        registrationId,
        0, // deviceId
        preKeys.first.id,
        preKeys.first.getKeyPair().publicKey,
        signedPreKey.id,
        signedPreKey.getKeyPair().publicKey,
        signedPreKey.signature,
        identityKeyPair.getPublicKey());

    bundleMap['registrationId'] = registrationId.toString();
// List<String> pks = preKeys.map((e) => e.serialize().toString()).toList();
    PreKeyRecord pks = preKeys.first;
    bundleMap['preKey'] = jsonEncode(pks.getKeyPair().publicKey.serialize());
    bundleMap['preKeyId'] = preKeys.first.id.toString();
    bundleMap['signedPreKeyId'] = signedPreKey.id.toString();
    bundleMap['signedPreKey'] = jsonEncode(signedPreKey.getKeyPair().publicKey.serialize());
    bundleMap['signature'] = signedPreKey.signature.toString();
    bundleMap['identityKeyPair'] = identityKeyPair.getPublicKey().serialize().toString();

    userService.setSignalData(identityKeyPair: identityKeyPair,
          registrationId: registrationId,
          preKeys: preKeys, signedPreKey: signedPreKey,
          userId: UserId, deviceid: DeviceId);

}

  String sessionEncrypt(PreKeyBundle recievedBundle, String username, String msg) {
    var remoteAddress = SignalProtocolAddress('remote', 1);
    var sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

// sessionBuilder.processPreKeyBundle(recievedBundle);

    var sessionCipher = SessionCipher(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);
    CiphertextMessage ciphertext = sessionCipher.encrypt(utf8.encode(msg));

    return ciphertext.serialize().toString();
  }

  String sessionDecrypt(PreKeyBundle recievedBundle, String username, String ciphertext){
    var remoteAddress = SignalProtocolAddress(username, 1);
    var sessionBuilder = SessionBuilder(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

    sessionBuilder.processPreKeyBundle(recievedBundle);

   var sessionCipher = SessionCipher(sessionStore, preKeyStore,
        signedPreKeyStore, identityStore, remoteAddress);

    PreKeySignalMessage mess = PreKeySignalMessage(Uint8List.fromList(ciphertext.codeUnits));
    String text = sessionCipher.decrypt(mess).toString();

    print('Decrypted = ${text}');

    return text;
  }

  void groupSessionEncrypt() {
    var senderKeyName = SenderKeyName("", SignalProtocolAddress("sender", 1));
    var senderKeyStore = InMemorySenderKeyStore();
    var groupSession = GroupCipher(senderKeyStore, senderKeyName);
    groupSession.encrypt(utf8.encode("Hello Mixin"));
  }

}`

I should have mostly followed the example and cant figure out why I am still getting error. The receivedBundle is being reconstructed from its components saved in the server and fetched. The code used for bundle is as follows:

`var bundle = json.decode(otherUser.publicKey);

int registrationId = int.parse(bundle['registrationId'].toString());
List<int> stringList = jsonDecode(bundle['preKey']).cast<int>();
final key = DjbECPublicKey(Uint8List.fromList(stringList));

ECPublicKey preKey = libs.Curve.decodePoint(key.serialize(), 1);
final key2 = DjbECPublicKey(Uint8List.fromList(jsonDecode(bundle['signedPreKey']).cast<int>()));
ECPublicKey signedPreKey = libs.Curve.decodePoint(key2.serialize(), 1);
Uint8List signedPreKeySignature = Uint8List.fromList(jsonDecode(bundle['signature']).cast<int>());
int preKeyId = int.parse(bundle['preKeyId']);
int signedPreKeyId = int.parse(bundle['signedPreKeyId']);
IdentityKey identityKey = IdentityKey.fromBytes(Uint8List.fromList(jsonDecode(bundle['identityKeyPair']).cast<int>()), 0) ;
setState(() {
  recieverBundle = PreKeyBundle(registrationId, 1, preKeyId, preKey, signedPreKeyId, signedPreKey, signedPreKeySignature, identityKey);
});`

Need some help in figuring out what I am doing wrong in the implementation.

not an issue but a suggestion

Can you guys make a YouTube video on how to implement this package into a real life app like very simple chatting app just having chatting and end to end encryption using signal. This will be really helpful to the community. Thank you!!

optional one-time prekey

The documentation is not clear enough. The official Signal documentation of X3DH (https://signal.org/docs/specifications/x3dh/) says that the one-time prekey are optional. However it seems that in the current implementation there must be necessarily a pre-key bundle.

I was wondering how to manage the various keys at a lower level, so that I can create a JSON that can be sent to my backend

Assertion failed: Instance of 'InvalidKeyException'

In my project i was using this version libsignal_protocol_dart: ^0.3.0-nullsafety.0.
When i updated to this version libsignal_protocol_dart: ^0.5.5 i'm getting this error Assertion failed: Instance of 'InvalidKeyException'
on encryption but other then that decryption works perfectly.

I'm getting this error on this line
CiphertextMessage encryptedMessage = await cipher.encrypt(Uint8List.fromList(utf8.encode(data)));

Benchmark encrypt and decrypt

Before we use golang library for ed25519, and dart ffi call it, but not good for multiple platform support.
Now we use pure dart encrypt and decrypt ed25519, should we compare with use rust implements the ed25519 with dart ffi?

Some implementation doubts

From the example code, I see that this function generates a random number:
var registerationId = KeyHelper.generateRegistrationId(false);
Should I save this number in the database to retrieve it later? Must it be unique?
I saw that the deviceid on PreKeyBundle is an integer, in my database I save it as uuid. can I leave "1" as the value?

Decryption works first time but throws "InvalidKeyIdException - No such prekeyrecord!" if recalled

import 'dart:typed_data';

import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';

class Session {
  final SignalProtocolStore self;
  PreKeyBundle otherKeyBundle;
  SignalProtocolAddress otherAddress;
  SessionCipher cipher;
  Session(this.self, this.otherKeyBundle, this.otherAddress);

  void build() {
    var builder = SessionBuilder(self, self, self, self, otherAddress);
    builder.processPreKeyBundle(otherKeyBundle);
    cipher = SessionCipher(self, self, self, self, otherAddress);
  }

  PreKeySignalMessage encrypt(Uint8List message) {
    var ciphertext = cipher.encrypt(message);
    var rawCiphertext = ciphertext.serialize();
    var encrypted = PreKeySignalMessage(rawCiphertext);
    return encrypted;
  }

  Uint8List decrypt(PreKeySignalMessage ciphertext) {
    var decrypted = cipher.decrypt(ciphertext);
    return decrypted;
  }
}

Encryption works every time I call encrypt() function but decrypt() function works only the first time. The second or third time I call this function it throws: "InvalidKeyIdException - No such prekeyrecord! - 2"

Error

Inconsistency with signature

I after that got InvalidKeyIdException - No such prekeyrecord! - 0 which was fixed by using the preKey index 1 (or any one that wasn't used before, since pre keys can only be used once) on the jeanSessionBuilder. For everyone interested, this is the now fixed and working code:

I ran a test on this code multiple times. Some failed on processPreKeyBundle(...)
I sometimes get InvalidKeyException - Invalid signature on device key!

I tried this example without the preKeybundle.

Click to expand
import 'dart:convert';
import 'dart:typed_data';

import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';

void main() async {
 //
 // BOB SETUP
 //
 final bobAddress = SignalProtocolAddress('bob', 1);
 final bobIdentityKeyPair = generateIdentityKeyPair();
 final bobRegistrationId = generateRegistrationId(false);

 final bobPreKeys = generatePreKeys(0, 110);
 final bobSignedPreKey = generateSignedPreKey(bobIdentityKeyPair, 0);

 final bobPreKeyStore = InMemoryPreKeyStore();
 final bobSessionStore = InMemorySessionStore();
 final bobSignedPreKeyStore = InMemorySignedPreKeyStore();
 final bobIdentityKeyStore =
     InMemoryIdentityKeyStore(bobIdentityKeyPair, bobRegistrationId);

 for (var p in bobPreKeys) {
   await bobPreKeyStore.storePreKey(p.id, p);
 }
 await bobSignedPreKeyStore.storeSignedPreKey(
   bobSignedPreKey.id,
   bobSignedPreKey,
 );

 //
 // JEAN SETUP
 //
 final jeanRegistrationId = generateRegistrationId(false);
 final jeanIdentityKeyPair = generateIdentityKeyPair();
 final jeanAddress = SignalProtocolAddress('jean', 1);

 final jeanPreKeys = generatePreKeys(0, 110);
 final jeanSignedPreKey = generateSignedPreKey(jeanIdentityKeyPair, 0);

 final jeanPreKeyStore = InMemoryPreKeyStore();
 final jeanSessionStore = InMemorySessionStore();
 final jeanSignedPreKeyStore = InMemorySignedPreKeyStore();
 final jeanIdentityKeyStore =
     InMemoryIdentityKeyStore(jeanIdentityKeyPair, jeanRegistrationId);

 final jeanSignalProtocolStore =
     InMemorySignalProtocolStore(jeanIdentityKeyPair, 1);

 for (var p in jeanPreKeys) {
   await jeanSignalProtocolStore.storePreKey(p.id, p);
 }
 await jeanSignalProtocolStore.storeSignedPreKey(
     jeanSignedPreKey.id, jeanSignedPreKey);

 //
 // ALICE SETUP
 //
 // Should get remote from the server
 final aliceRegistrationId = generateRegistrationId(false);
 final aliceIdentityKeyPair = generateIdentityKeyPair();
 final aliceAddress = SignalProtocolAddress('alice', 1);

 final alicePreKeys = generatePreKeys(0, 110);
 final aliceSignedPreKey = generateSignedPreKey(aliceIdentityKeyPair, 0);
 final aliceIdentityKeyStore =
     InMemoryIdentityKeyStore(aliceIdentityKeyPair, aliceRegistrationId);

 final aliceSignalProtocolStore =
     InMemorySignalProtocolStore(aliceIdentityKeyPair, 1);

 for (var p in alicePreKeys) {
   await aliceSignalProtocolStore.storePreKey(p.id, p);
 }
 await aliceSignalProtocolStore.storeSignedPreKey(
     aliceSignedPreKey.id, aliceSignedPreKey);

 //
 // Preparing connection
 //
 final bobSessionBuilder = SessionBuilder(
   bobSessionStore,
   bobPreKeyStore,
   bobSignedPreKeyStore,
   bobIdentityKeyStore,
   aliceAddress,
 );
 await bobSessionBuilder.processPreKeyBundle(
   PreKeyBundle(
     aliceRegistrationId,
     1,
     alicePreKeys[0].id,
     alicePreKeys[0].getKeyPair().publicKey,
     aliceSignedPreKey.id,
     aliceSignedPreKey.getKeyPair().publicKey,
     aliceSignedPreKey.signature,
     aliceIdentityKeyPair.getPublicKey(),
   ),
 );

 final jeanSessionBuilder = SessionBuilder(
   jeanSessionStore,
   jeanPreKeyStore,
   jeanSignedPreKeyStore,
   jeanIdentityKeyStore,
   aliceAddress,
 );

 await jeanSessionBuilder.processPreKeyBundle(
   PreKeyBundle(
     aliceRegistrationId,
     1,
     alicePreKeys[1].id,
     alicePreKeys[1].getKeyPair().publicKey,
     aliceSignedPreKey.id,
     aliceSignedPreKey.getKeyPair().publicKey,
     aliceSignedPreKey.signature,
     aliceIdentityKeyPair.getPublicKey(),
   ),
 );

 //
 // ENCRYPTION
 //
 final bobSessionCipher = SessionCipher(
   bobSessionStore,
   bobPreKeyStore,
   bobSignedPreKeyStore,
   bobIdentityKeyStore,
   aliceAddress,
 );
 final ciphertextFromBob = await bobSessionCipher
     .encrypt(Uint8List.fromList(utf8.encode('Hello from Bob')));

 final jeanSessionCipher = SessionCipher(
   jeanSessionStore,
   jeanPreKeyStore,
   jeanSignedPreKeyStore,
   jeanIdentityKeyStore,
   aliceAddress,
 );
 final ciphertextFromJean = await jeanSessionCipher
     .encrypt(Uint8List.fromList(utf8.encode('Hello from Jean')));

 //
 // DECRYPTION
 //
 final aliceSessionCipher1 = SessionCipher(
   aliceSignalProtocolStore.sessionStore,
   aliceSignalProtocolStore.preKeyStore,
   aliceSignalProtocolStore.signedPreKeyStore,
   aliceIdentityKeyStore,
   bobAddress,
 );
 final aliceSessionCipher2 = SessionCipher(
   aliceSignalProtocolStore.sessionStore,
   aliceSignalProtocolStore.preKeyStore,
   aliceSignalProtocolStore.signedPreKeyStore,
   aliceIdentityKeyStore,
   jeanAddress,
 );

 if (ciphertextFromBob.getType() == CiphertextMessage.prekeyType) {
   await aliceSessionCipher1.decryptWithCallback(
       ciphertextFromBob as PreKeySignalMessage, (plaintext) {
     // ignore: avoid_print
     print(utf8.decode(plaintext));
   });
 }

 if (ciphertextFromJean.getType() == CiphertextMessage.prekeyType) {
   await aliceSessionCipher2.decryptWithCallback(
       ciphertextFromJean as PreKeySignalMessage, (plaintext) {
     // ignore: avoid_print
     print(utf8.decode(plaintext));
   });
 }
}

Originally posted by @McCrafterIV in #46 (comment)

Better Docs

Firstly thank you for writing this package. I want to use this library, but it is unclear to me how to really get started. It says you have to implement certain classes but I want to see exactly how I should do so. Thanks!

How to handle emoji while encryption/decryption?

Hey, I try encryption and decryption with text, and it works perfectly. But when I add emoji in the text the emoji could not get decrypt in Unicode or in another format. So can you please guide me on how can handle emoji in the text?

Unexpected behaviour from generatePreKeys

Hey!

  static List<PreKeyRecord> generatePreKeys(int start, int count) {
    var results = <PreKeyRecord>[];
    start--;
    for (var i = 0; i < count; i++) {
      results.add(PreKeyRecord(
          ((start + i) % (Medium.MAX_VALUE - 1)) + 1, Curve.generateKeyPair()));
    }
    return results;
  }

Here, with generatePreKeys(0, 100) we would expect an output of 0...99 - according to the Java docs noting start as inclusive. However, due to the difference in modulo operators between Dart and Java (Euclidean vs Truncated), the first iteration of the function will give Medium.MAX_VALUE - 1 as the first ID.

A simple fix for this is replacing % with .remainder()

InMemorySessionStore

InMemorySessionStore 看了很多资料都是内存保存这个store, 开发中是不是要放到数据库中InDbSessionStore,网上对于signal 的资料太少了,也没有 demo看,有好的建议吗,一个开发im 老鸟

Unhandled Exception: InvalidMessageException - No valid sessions. [InvalidMessageException - Bad Mac!][0]

Getting error while decrypting the message
/flutter ( 3857): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: InvalidMessageException - No valid sessions. [InvalidMessageException - Bad Mac!][0]
E/flutter ( 3857): #0 SessionCipher._decrypt
session_cipher.dart:186
E/flutter ( 3857): #1 SessionCipher.decryptWithCallback
session_cipher.dart:109
E/flutter ( 3857):
E/flutter ( 3857): #2 ChatBloc._decryptMessage
chat_bloc.dart:97
E/flutter ( 3857):
E/flutter ( 3857): #3 Bloc.on..handleEvent
bloc.dart:229
E/flutter ( 3857):
E/flutter ( 3857):

Unit tests fail - should they?

git log --oneline
11fa11c (HEAD, origin/master, origin/HEAD, master) Update group session
dart --version
Dart VM version: 2.8.3 (stable) (Tue May 26 18:39:38 2020 +0200) on "macos_x64"
pub run test
00:06 +6 -1: test/fingerprint/numeric_fingerprint_generator_test.dart: testMismatchingIdentifiers [E]                                     
  Expected: <false>
    Actual: <true>
  
  package:test_api                                                expect
  test/fingerprint/numeric_fingerprint_generator_test.dart 514:5  main.<fn>
  
00:17 +24 -2: test/session_cipher_test.dart: testMessageKeyLimits [E]                                                                     
  InvalidProtocolBufferException: While parsing a protocol message, the input ended unexpectedly
  in the middle of a field.  This could mean either than the
  input has been truncated or that an embedded message
  misreported its own length.
  
  package:protobuf/src/protobuf/coded_buffer_reader.dart 98:7                            CodedBufferReader.readMessage
  package:protobuf/src/protobuf/coded_buffer.dart 127:15                                 _mergeFromCodedBufferReader
  package:protobuf/src/protobuf/generated_message.dart 180:5                             GeneratedMessage.mergeFromBuffer
  package:libsignal_protocol_dart/src/state/LocalStorageProtocol.pb.dart 474:133         new RecordStructure.fromBuffer
  package:libsignal_protocol_dart/src/state/SessionRecord.dart 23:34                     new SessionRecord.fromSerialized
  package:libsignal_protocol_dart/src/state/impl/InMemorySessionStore.dart 51:30         InMemorySessionStore.loadSession
  package:libsignal_protocol_dart/src/state/impl/InMemorySignalProtocolStore.dart 76:25  InMemorySignalProtocolStore.loadSession
  package:libsignal_protocol_dart/src/SessionCipher.dart 138:39                          SessionCipher.decryptFromSignalWithCallback
  package:libsignal_protocol_dart/src/SessionCipher.dart 128:12                          SessionCipher.decryptFromSignal
  test/session_cipher_test.dart 194:15                                                   main.<fn>
  
00:34 +41 -3: test/devices/device_consistency_test.dart: testDeviceConsistency [E]                                                        
  Exception: Values must not be null
  package:libsignal_protocol_dart/src/ecc/Curve.dart 128:7                          Curve.verifyVrfSignature
  package:libsignal_protocol_dart/src/protocol/DeviceConsistencyMessage.dart 24:34  new DeviceConsistencyMessage
  test/devices/device_consistency_test.dart 42:9                                    main.<fn>
  
00:34 +41 -3: Some tests failed.   

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.