├── .github └── workflows │ └── dart.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── main.dart ├── lib ├── libsignal_protocol_dart.dart └── src │ ├── cbc.dart │ ├── decryption_callback.dart │ ├── devices │ ├── device_consistency_code_generator.dart │ ├── device_consistency_commitment.dart │ └── device_consistency_signature.dart │ ├── duplicate_message_exception.dart │ ├── ecc │ ├── curve.dart │ ├── djb_ec_private_key.dart │ ├── djb_ec_public_key.dart │ ├── ec_key_pair.dart │ ├── ec_private_key.dart │ ├── ec_public_key.dart │ └── ed25519.dart │ ├── entry.dart │ ├── eq.dart │ ├── fingerprint │ ├── displayable_fingerprint.dart │ ├── fingerprint.dart │ ├── fingerprint_generator.dart │ ├── fingerprint_parsing_exception.dart │ ├── fingerprint_version_mismatch_exception.dart │ ├── numeric_fingerprint_generator.dart │ └── scannable_fingerprint.dart │ ├── groups │ ├── group_cipher.dart │ ├── group_session_builder.dart │ ├── ratchet │ │ ├── sender_chain_key.dart │ │ └── sender_message_key.dart │ ├── sender_key_name.dart │ └── state │ │ ├── in_memory_sender_key_store.dart │ │ ├── sender_key_record.dart │ │ ├── sender_key_state.dart │ │ └── sender_key_store.dart │ ├── identity_key.dart │ ├── identity_key_pair.dart │ ├── invalid_key_exception.dart │ ├── invalid_key_id_exception.dart │ ├── invalid_mac_exception.dart │ ├── invalid_message_exception.dart │ ├── kdf │ ├── derived_message_secrets.dart │ ├── derived_root_secrets.dart │ ├── hkdf.dart │ ├── hkdfv2.dart │ └── hkdfv3.dart │ ├── legacy_message_exception.dart │ ├── no_session_exception.dart │ ├── protocol │ ├── ciphertext_message.dart │ ├── device_consistency_message.dart │ ├── pre_key_signal_message.dart │ ├── sender_key_distribution_message_wrapper.dart │ ├── sender_key_message.dart │ └── signal_message.dart │ ├── provisioning_cipher.dart │ ├── ratchet │ ├── alice_signal_protocol_parameters.dart │ ├── bob_signal_protocol_parameters.dart │ ├── chain_key.dart │ ├── message_keys.dart │ ├── ratcheting_session.dart │ ├── root_key.dart │ └── symmetric_signal_protocol_parameters.dart │ ├── session_builder.dart │ ├── session_cipher.dart │ ├── signal_protocol_address.dart │ ├── state │ ├── fingerprint_protocol.pb.dart │ ├── fingerprint_protocol.pbenum.dart │ ├── fingerprint_protocol.pbjson.dart │ ├── fingerprint_protocol.pbserver.dart │ ├── identity_key_store.dart │ ├── impl │ │ ├── in_memory_identity_key_store.dart │ │ ├── in_memory_pre_key_store.dart │ │ ├── in_memory_session_store.dart │ │ ├── in_memory_signal_protocol_store.dart │ │ └── in_memory_signed_pre_key_store.dart │ ├── local_storage_protocol.pb.dart │ ├── local_storage_protocol.pbenum.dart │ ├── local_storage_protocol.pbjson.dart │ ├── local_storage_protocol.pbserver.dart │ ├── pre_key_bundle.dart │ ├── pre_key_record.dart │ ├── pre_key_store.dart │ ├── session_record.dart │ ├── session_state.dart │ ├── session_store.dart │ ├── signal_protocol_store.dart │ ├── signed_pre_key_record.dart │ ├── signed_pre_key_store.dart │ ├── whisper_text_protocol.pb.dart │ ├── whisper_text_protocol.pbenum.dart │ ├── whisper_text_protocol.pbjson.dart │ └── whisper_text_protocol.pbserver.dart │ ├── untrusted_identity_exception.dart │ └── util │ ├── byte_util.dart │ ├── identity_key_comparator.dart │ ├── key_helper.dart │ ├── log.dart │ └── medium.dart ├── protobuf ├── FingerprintProtocol.proto ├── LocalStorageProtocol.proto └── WhisperTextProtocol.proto ├── pubspec.yaml └── test ├── devices └── device_consistency_test.dart ├── ecc └── curve25519_test.dart ├── fingerprint └── numeric_fingerprint_generator_test.dart ├── groups └── group_cipher_test.dart ├── kdf └── hkdf_test.dart ├── provisioning_cipher_test.dart ├── ratchet ├── chain_key_test.dart ├── ratcheting_session_test.dart └── root_key_test.dart ├── session_builder_test.dart ├── session_cipher_test.dart ├── state └── impl │ ├── in_memory_identity_key_store_test.dart │ ├── in_memory_pre_key_store_test.dart │ ├── in_memory_session_store_test.dart │ ├── in_memory_signal_protocol_store_test.dart │ └── in_memory_signed_pre_key_store_test.dart ├── test_in_memory_identity_key_store.dart ├── test_in_memory_signal_protocol_store.dart └── util └── byte_util_test.dart /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Dart 7 | 8 | on: 9 | workflow_dispatch: 10 | 11 | push: 12 | branches: [ master ] 13 | pull_request: 14 | branches: [ master ] 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: dart-lang/setup-dart@v1.3 23 | 24 | - name: Install dependencies 25 | run: dart pub get 26 | # Uncomment this step to verify the use of 'dart format' on each commit. 27 | - name: Verify formatting 28 | run: dart format --output=none --set-exit-if-changed . 29 | 30 | # Consider passing '--fatal-infos' for slightly stricter analysis. 31 | - name: Analyze project source 32 | run: dart analyze --fatal-infos 33 | 34 | # Your project will need to have tests in test/ and a dependency on 35 | # package:test for this step to succeed. Note that Flutter projects will 36 | # want to change this to 'flutter test'. 37 | - name: Run tests 38 | run: dart test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Omit commiting pubspec.lock for library packages: 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock 7 | pubspec.lock 8 | 9 | # Conventional directory for build outputs 10 | build/ 11 | 12 | # Directory created by dartdoc 13 | doc/api/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.iml 18 | 19 | # Mac 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.0] - 2 | 3 | ## [0.1.2] - 4 | 5 | ## [0.2.0] - 6 | 7 | ## [0.2.1] - 8 | 9 | ## [0.3.0-nullsafety.0] - 10 | 11 | ## [0.4.0-nullsafety.0] - 12 | 13 | ## [0.4.1-nullsafety.0] - 14 | 15 | ## [0.5.0-nullsafety.0] - 16 | 17 | ## [0.5.1] - 18 | 19 | ## [0.5.2] - 20 | 21 | ## [0.5.3] - 22 | 23 | ## [0.5.4] - 24 | 25 | ## [0.5.5] - 26 | 27 | ## [0.5.6] - 28 | 29 | ## [0.6.0] - 30 | 31 | ## [0.6.1] - 32 | 33 | ## [0.6.2] - 34 | 35 | ## [0.6.3] - 36 | * Exported fingerprint classes 37 | 38 | ## [0.6.5] - 39 | 40 | ## [0.7.0] - 41 | 42 | ## [0.7.1] - 43 | 44 | ## [0.7.2] - 45 | 46 | ## [0.7.3] - 47 | 48 | ## [0.7.4] - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsignal_protocol_dart 2 | 3 | [![pub package](https://img.shields.io/pub/v/libsignal_protocol_dart.svg)](https://pub.dartlang.org/packages/libsignal_protocol_dart) 4 | [![Dart CI](https://github.com/MixinNetwork/libsignal_protocol_dart/workflows/Dart/badge.svg)](https://github.com/MixinNetwork/libsignal_protocol_dart/actions) 5 | 6 | libsignal_protocol_dart is a pure Dart/Flutter implementation of the Signal Protocol. 7 | 8 | ## Documentation 9 | 10 | For more information on how the Signal Protocol works: 11 | 12 | - [Double Ratchet](https://whispersystems.org/docs/specifications/doubleratchet/) 13 | - [X3DH Key Agreement](https://whispersystems.org/docs/specifications/x3dh/) 14 | - [XEdDSA Signature Schemes](https://whispersystems.org/docs/specifications/xeddsa/) 15 | - [Signal Protocol Java](https://github.com/signalapp/libsignal-protocol-java/) 16 | 17 | ## Usage 18 | 19 | ## Install time 20 | 21 | At install time, a signal client needs to generate its identity keys, registration id, and prekeys. 22 | 23 | ```dart 24 | Future install() async { 25 | final identityKeyPair = generateIdentityKeyPair(); 26 | final registrationId = generateRegistrationId(false); 27 | 28 | final preKeys = generatePreKeys(0, 110); 29 | 30 | final signedPreKey = generateSignedPreKey(identityKeyPair, 0); 31 | 32 | final sessionStore = InMemorySessionStore(); 33 | final preKeyStore = InMemoryPreKeyStore(); 34 | final signedPreKeyStore = InMemorySignedPreKeyStore(); 35 | final identityStore = 36 | InMemoryIdentityKeyStore(identityKeyPair, registrationId); 37 | 38 | for (var p in preKeys) { 39 | await preKeyStore.storePreKey(p.id, p); 40 | } 41 | await signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey); 42 | } 43 | ``` 44 | 45 | ## Building a session 46 | 47 | 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. 48 | 49 | Once those are implemented, you can build a session in this way: 50 | 51 | ```dart 52 | final remoteAddress = SignalProtocolAddress("remote", 1); 53 | final sessionBuilder = SessionBuilder(sessionStore, preKeyStore, 54 | signedPreKeyStore, identityStore, remoteAddress); 55 | 56 | sessionBuilder.processPreKeyBundle(retrievedPreKey); 57 | 58 | final sessionCipher = SessionCipher(sessionStore, preKeyStore, 59 | signedPreKeyStore, identityStore, remoteAddress); 60 | final ciphertext = sessionCipher.encrypt(utf8.encode("Hello Mixin")); 61 | 62 | deliver(ciphertext); 63 | ``` 64 | 65 | ## Building a group session 66 | 67 | If you wanna send message to a group, send a SenderKeyDistributionMessage to all members of the group. 68 | 69 | ```dart 70 | const alice = SignalProtocolAddress('+00000000001', 1); 71 | const groupSender = SenderKeyName('Private group', alice); 72 | final aliceStore = InMemorySenderKeyStore(); 73 | final bobStore = InMemorySenderKeyStore(); 74 | 75 | final aliceSessionBuilder = GroupSessionBuilder(aliceStore); 76 | final bobSessionBuilder = GroupSessionBuilder(bobStore); 77 | 78 | final aliceGroupCipher = GroupCipher(aliceStore, groupSender); 79 | final bobGroupCipher = GroupCipher(bobStore, groupSender); 80 | 81 | final sentAliceDistributionMessage = 82 | await aliceSessionBuilder.create(groupSender); 83 | final receivedAliceDistributionMessage = 84 | SenderKeyDistributionMessageWrapper.fromSerialized( 85 | sentAliceDistributionMessage.serialize()); 86 | await bobSessionBuilder.process( 87 | groupSender, receivedAliceDistributionMessage); 88 | 89 | final ciphertextFromAlice = await aliceGroupCipher 90 | .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin'))); 91 | final plaintextFromAlice = await bobGroupCipher.decrypt(ciphertextFromAlice); 92 | // ignore: avoid_print 93 | print(utf8.decode(plaintextFromAlice)); 94 | ``` 95 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | avoid_classes_with_only_static_members: ignore 6 | leading_newlines_in_multiline_strings: ignore 7 | always_use_package_imports: ignore 8 | public_member_api_docs: ignore 9 | lines_longer_than_80_chars: ignore 10 | avoid_function_literals_in_foreach_calls: ignore 11 | avoid_setters_without_getters: ignore 12 | # experimental lint 13 | use_build_context_synchronously: ignore 14 | exclude: 15 | - 'bin/cache/**' 16 | # the following two are relative to the stocks example and the flutter package respectively 17 | # see https://github.com/dart-lang/sdk/issues/28463 18 | - 'lib/i18n/messages_*.dart' 19 | - 'lib/src/http/**' 20 | # custom 21 | - 'lib/generated/**' 22 | - '**.g.dart' 23 | - 'lib/src/state/**.pb.dart' 24 | 25 | linter: 26 | rules: 27 | prefer_const_constructors_in_immutables: true 28 | prefer_relative_imports: true 29 | prefer_final_locals: true 30 | avoid_void_async: true 31 | unnecessary_await_in_return: true 32 | prefer_expression_function_bodies: true 33 | avoid_field_initializers_in_const_classes: true 34 | file_names: true 35 | unnecessary_parenthesis: true 36 | prefer_void_to_null: true 37 | avoid_bool_literals_in_conditional_expressions: true 38 | avoid_returning_null_for_void: true 39 | prefer_function_declarations_over_variables: true 40 | empty_statements: true 41 | prefer_is_not_operator: true 42 | cast_nullable_to_non_nullable: true 43 | avoid_classes_with_only_static_members: true 44 | type_annotate_public_apis: true 45 | prefer_const_literals_to_create_immutables: true 46 | use_named_constants: true 47 | use_string_buffers: true 48 | unnecessary_raw_strings: true 49 | unnecessary_null_checks: true 50 | parameter_assignments: true 51 | prefer_const_declarations: true 52 | sort_unnamed_constructors_first: true 53 | use_setters_to_change_properties: true 54 | curly_braces_in_flow_control_structures: true 55 | require_trailing_commas: false 56 | prefer_asserts_with_message: false 57 | flutter_style_todos: false 58 | document_ignores: false 59 | specify_nonobvious_property_types: false 60 | 61 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 5 | 6 | Future main() async { 7 | await install(); 8 | await groupTest(); 9 | } 10 | 11 | Future install() async { 12 | final identityKeyPair = generateIdentityKeyPair(); 13 | final registrationId = generateRegistrationId(false); 14 | 15 | final preKeys = generatePreKeys(0, 110); 16 | 17 | final signedPreKey = generateSignedPreKey(identityKeyPair, 0); 18 | 19 | final sessionStore = InMemorySessionStore(); 20 | final preKeyStore = InMemoryPreKeyStore(); 21 | final signedPreKeyStore = InMemorySignedPreKeyStore(); 22 | final identityStore = 23 | InMemoryIdentityKeyStore(identityKeyPair, registrationId); 24 | 25 | for (final p in preKeys) { 26 | await preKeyStore.storePreKey(p.id, p); 27 | } 28 | await signedPreKeyStore.storeSignedPreKey(signedPreKey.id, signedPreKey); 29 | 30 | const bobAddress = SignalProtocolAddress('bob', 1); 31 | final sessionBuilder = SessionBuilder( 32 | sessionStore, preKeyStore, signedPreKeyStore, identityStore, bobAddress); 33 | 34 | // Should get remote from the server 35 | final remoteRegId = generateRegistrationId(false); 36 | final remoteIdentityKeyPair = generateIdentityKeyPair(); 37 | final remotePreKeys = generatePreKeys(0, 110); 38 | final remoteSignedPreKey = generateSignedPreKey(remoteIdentityKeyPair, 0); 39 | 40 | final retrievedPreKey = PreKeyBundle( 41 | remoteRegId, 42 | 1, 43 | remotePreKeys[0].id, 44 | remotePreKeys[0].getKeyPair().publicKey, 45 | remoteSignedPreKey.id, 46 | remoteSignedPreKey.getKeyPair().publicKey, 47 | remoteSignedPreKey.signature, 48 | remoteIdentityKeyPair.getPublicKey()); 49 | 50 | await sessionBuilder.processPreKeyBundle(retrievedPreKey); 51 | 52 | final sessionCipher = SessionCipher( 53 | sessionStore, preKeyStore, signedPreKeyStore, identityStore, bobAddress); 54 | final ciphertext = await sessionCipher 55 | .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin🤣'))); 56 | // ignore: avoid_print 57 | print(ciphertext); 58 | // ignore: avoid_print 59 | print(ciphertext.serialize()); 60 | //deliver(ciphertext); 61 | 62 | final signalProtocolStore = 63 | InMemorySignalProtocolStore(remoteIdentityKeyPair, 1); 64 | const aliceAddress = SignalProtocolAddress('alice', 1); 65 | final remoteSessionCipher = 66 | SessionCipher.fromStore(signalProtocolStore, aliceAddress); 67 | 68 | for (final p in remotePreKeys) { 69 | await signalProtocolStore.storePreKey(p.id, p); 70 | } 71 | await signalProtocolStore.storeSignedPreKey( 72 | remoteSignedPreKey.id, remoteSignedPreKey); 73 | 74 | if (ciphertext.getType() == CiphertextMessage.prekeyType) { 75 | await remoteSessionCipher 76 | .decryptWithCallback(ciphertext as PreKeySignalMessage, (plaintext) { 77 | // ignore: avoid_print 78 | print(utf8.decode(plaintext)); 79 | }); 80 | } 81 | } 82 | 83 | Future groupTest() async { 84 | const alice = SignalProtocolAddress('+00000000001', 1); 85 | const groupSender = SenderKeyName('Private group', alice); 86 | final aliceStore = InMemorySenderKeyStore(); 87 | final bobStore = InMemorySenderKeyStore(); 88 | 89 | final aliceSessionBuilder = GroupSessionBuilder(aliceStore); 90 | final bobSessionBuilder = GroupSessionBuilder(bobStore); 91 | 92 | final aliceGroupCipher = GroupCipher(aliceStore, groupSender); 93 | final bobGroupCipher = GroupCipher(bobStore, groupSender); 94 | 95 | final sentAliceDistributionMessage = 96 | await aliceSessionBuilder.create(groupSender); 97 | final receivedAliceDistributionMessage = 98 | SenderKeyDistributionMessageWrapper.fromSerialized( 99 | sentAliceDistributionMessage.serialize()); 100 | await bobSessionBuilder.process( 101 | groupSender, receivedAliceDistributionMessage); 102 | 103 | final ciphertextFromAlice = await aliceGroupCipher 104 | .encrypt(Uint8List.fromList(utf8.encode('Hello Mixin'))); 105 | final plaintextFromAlice = await bobGroupCipher.decrypt(ciphertextFromAlice); 106 | // ignore: avoid_print 107 | print(utf8.decode(plaintextFromAlice)); 108 | } 109 | 110 | Future groupSession() async { 111 | const senderKeyName = SenderKeyName('', SignalProtocolAddress('sender', 1)); 112 | final senderKeyStore = InMemorySenderKeyStore(); 113 | final groupSession = GroupCipher(senderKeyStore, senderKeyName); 114 | await groupSession.encrypt(Uint8List.fromList(utf8.encode('Hello Mixin'))); 115 | } 116 | -------------------------------------------------------------------------------- /lib/libsignal_protocol_dart.dart: -------------------------------------------------------------------------------- 1 | export 'src/decryption_callback.dart'; 2 | export 'src/duplicate_message_exception.dart'; 3 | export 'src/ecc/curve.dart'; 4 | export 'src/ecc/djb_ec_private_key.dart'; 5 | export 'src/ecc/djb_ec_public_key.dart'; 6 | export 'src/ecc/ec_key_pair.dart'; 7 | export 'src/ecc/ec_private_key.dart'; 8 | export 'src/ecc/ec_public_key.dart'; 9 | export 'src/fingerprint/displayable_fingerprint.dart'; 10 | export 'src/fingerprint/fingerprint.dart'; 11 | export 'src/fingerprint/numeric_fingerprint_generator.dart'; 12 | export 'src/fingerprint/scannable_fingerprint.dart'; 13 | export 'src/groups/group_cipher.dart'; 14 | export 'src/groups/group_session_builder.dart'; 15 | export 'src/groups/sender_key_name.dart'; 16 | export 'src/groups/state/in_memory_sender_key_store.dart'; 17 | export 'src/groups/state/sender_key_record.dart'; 18 | export 'src/groups/state/sender_key_store.dart'; 19 | export 'src/identity_key.dart'; 20 | export 'src/identity_key_pair.dart'; 21 | export 'src/invalid_key_exception.dart'; 22 | export 'src/invalid_key_id_exception.dart'; 23 | export 'src/legacy_message_exception.dart'; 24 | export 'src/no_session_exception.dart'; 25 | export 'src/protocol/ciphertext_message.dart'; 26 | export 'src/protocol/pre_key_signal_message.dart'; 27 | export 'src/protocol/sender_key_distribution_message_wrapper.dart'; 28 | export 'src/protocol/sender_key_message.dart'; 29 | export 'src/protocol/signal_message.dart'; 30 | export 'src/provisioning_cipher.dart'; 31 | export 'src/session_builder.dart'; 32 | export 'src/session_cipher.dart'; 33 | export 'src/signal_protocol_address.dart'; 34 | export 'src/state/identity_key_store.dart'; 35 | export 'src/state/impl/in_memory_identity_key_store.dart'; 36 | export 'src/state/impl/in_memory_pre_key_store.dart'; 37 | export 'src/state/impl/in_memory_session_store.dart'; 38 | export 'src/state/impl/in_memory_signal_protocol_store.dart'; 39 | export 'src/state/impl/in_memory_signed_pre_key_store.dart'; 40 | export 'src/state/pre_key_bundle.dart'; 41 | export 'src/state/pre_key_record.dart'; 42 | export 'src/state/pre_key_store.dart'; 43 | export 'src/state/session_record.dart'; 44 | export 'src/state/session_state.dart'; 45 | export 'src/state/session_store.dart'; 46 | export 'src/state/signal_protocol_store.dart'; 47 | export 'src/state/signed_pre_key_record.dart'; 48 | export 'src/state/signed_pre_key_store.dart'; 49 | export 'src/untrusted_identity_exception.dart'; 50 | export 'src/util/byte_util.dart'; 51 | export 'src/util/key_helper.dart'; 52 | export 'src/util/medium.dart'; 53 | -------------------------------------------------------------------------------- /lib/src/cbc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:pointycastle/export.dart'; 4 | 5 | Uint8List aesCbcEncrypt(Uint8List key, Uint8List iv, Uint8List plaintext) { 6 | final paddedPlaintext = pad(plaintext, 16); 7 | final cbc = CBCBlockCipher(AESEngine()) 8 | ..init(true, ParametersWithIV(KeyParameter(key), iv)); // true=encrypt 9 | 10 | final cipherText = Uint8List(paddedPlaintext.length); // allocate space 11 | var offset = 0; 12 | while (offset < paddedPlaintext.length) { 13 | offset += cbc.processBlock(paddedPlaintext, offset, cipherText, offset); 14 | } 15 | assert(offset == paddedPlaintext.length); 16 | return cipherText; 17 | } 18 | 19 | Uint8List aesCbcDecrypt(Uint8List key, Uint8List iv, Uint8List cipherText) { 20 | final cbc = CBCBlockCipher(AESEngine()) 21 | ..init(false, ParametersWithIV(KeyParameter(key), iv)); // false=decrypt 22 | 23 | final paddedPlainText = Uint8List(cipherText.length); // allocate space 24 | var offset = 0; 25 | while (offset < cipherText.length) { 26 | offset += cbc.processBlock(cipherText, offset, paddedPlainText, offset); 27 | } 28 | assert(offset == cipherText.length); 29 | return unpad(paddedPlainText); 30 | } 31 | 32 | Uint8List pad(Uint8List bytes, int blockSize) { 33 | final padLength = blockSize - (bytes.length % blockSize); 34 | final padded = Uint8List(bytes.length + padLength)..setAll(0, bytes); 35 | PKCS7Padding().addPadding(padded, bytes.length); 36 | return padded; 37 | } 38 | 39 | Uint8List unpad(Uint8List padded) => 40 | padded.sublist(0, padded.length - PKCS7Padding().padCount(padded)); 41 | -------------------------------------------------------------------------------- /lib/src/decryption_callback.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | typedef DecryptionCallback = void Function(Uint8List plaintext); 4 | -------------------------------------------------------------------------------- /lib/src/devices/device_consistency_code_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:convert/convert.dart'; 4 | import 'package:crypto/crypto.dart'; 5 | import '../util/byte_util.dart'; 6 | 7 | import 'device_consistency_commitment.dart'; 8 | import 'device_consistency_signature.dart'; 9 | 10 | class DeviceConsistencyCodeGenerator { 11 | static const int codeVersion = 0; 12 | 13 | static String generateFor(DeviceConsistencyCommitment commitment, 14 | List signatures) { 15 | final sortedSignatures = [...signatures] 16 | ..sort(compareSignature); 17 | 18 | final output = AccumulatorSink(); 19 | final input = sha512.startChunkedConversion(output) 20 | ..add(ByteUtil.shortToByteArray(codeVersion)) 21 | ..add(commitment.serialized); 22 | 23 | for (final signature in sortedSignatures) { 24 | input.add(signature.vrfOutput); 25 | } 26 | input.close(); 27 | final hash = output.events.single.bytes; 28 | final digits = getEncodedChunk(Uint8List.fromList(hash), 0) + 29 | getEncodedChunk(Uint8List.fromList(hash), 5); 30 | return digits.substring(0, 6); 31 | } 32 | 33 | static String getEncodedChunk(Uint8List hash, int offset) { 34 | final chunk = ByteUtil.byteArray5ToLong(hash, offset).remainder(100000); 35 | return chunk.toString().padLeft(5, '0'); 36 | } 37 | } 38 | 39 | int compareSignature( 40 | DeviceConsistencySignature a, DeviceConsistencySignature b) => 41 | ByteUtil.compare(a.vrfOutput, b.vrfOutput); 42 | -------------------------------------------------------------------------------- /lib/src/devices/device_consistency_commitment.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:convert/convert.dart'; 5 | import 'package:crypto/crypto.dart'; 6 | 7 | import '../identity_key.dart'; 8 | import '../util/byte_util.dart'; 9 | 10 | class DeviceConsistencyCommitment { 11 | DeviceConsistencyCommitment(int generation, List identityKeys) { 12 | final sortedIdentityKeys = [...identityKeys]..sort((a, b) => 13 | ByteUtil.compare(a.publicKey.serialize(), b.publicKey.serialize())); 14 | 15 | final output = AccumulatorSink(); 16 | final input = sha512.startChunkedConversion(output) 17 | ..add(utf8.encode(version)) 18 | ..add(ByteUtil.intToByteArray(generation)); 19 | 20 | for (final commitment in sortedIdentityKeys) { 21 | input.add(commitment.publicKey.serialize()); 22 | } 23 | input.close(); 24 | 25 | _generation = generation; 26 | _serialized = Uint8List.fromList(output.events.single.bytes); 27 | } 28 | 29 | static const String version = 'DeviceConsistencyCommitment_V0'; 30 | 31 | late int _generation; 32 | late Uint8List _serialized; 33 | 34 | Uint8List get serialized => _serialized; 35 | 36 | int get generation => _generation; 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/devices/device_consistency_signature.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class DeviceConsistencySignature { 4 | DeviceConsistencySignature(this._signature, this._vrfOutput); 5 | 6 | final Uint8List _signature; 7 | final Uint8List _vrfOutput; 8 | 9 | Uint8List get vrfOutput => _vrfOutput; 10 | 11 | Uint8List get signature => _signature; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/duplicate_message_exception.dart: -------------------------------------------------------------------------------- 1 | class DuplicateMessageException implements Exception { 2 | DuplicateMessageException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'DuplicateMessageException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/ecc/curve.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:x25519/x25519.dart' as x25519; 4 | 5 | import '../invalid_key_exception.dart'; 6 | import '../util/key_helper.dart'; 7 | import 'djb_ec_private_key.dart'; 8 | import 'djb_ec_public_key.dart'; 9 | import 'ec_key_pair.dart'; 10 | import 'ec_private_key.dart'; 11 | import 'ec_public_key.dart'; 12 | import 'ed25519.dart'; 13 | 14 | typedef KeyPairGenerator = GeneratedKeyPair Function(); 15 | typedef AgreementCalculator = Uint8List Function(Uint8List, Uint8List); 16 | 17 | class GeneratedKeyPair { 18 | GeneratedKeyPair(this.private, this.public); 19 | 20 | final Uint8List private; 21 | final Uint8List public; 22 | } 23 | 24 | class Curve { 25 | static const int djbType = 0x05; 26 | 27 | static KeyPairGenerator? keyPairGenerator; 28 | static AgreementCalculator? agreementCalculator; 29 | 30 | static ECKeyPair generateKeyPair() { 31 | final x25519.KeyPair keyPair; 32 | final generator = keyPairGenerator; 33 | if (generator != null) { 34 | final kp = generator(); 35 | keyPair = x25519.KeyPair(privateKey: kp.private, publicKey: kp.public); 36 | } else { 37 | keyPair = x25519.generateKeyPair(); 38 | } 39 | 40 | return ECKeyPair(DjbECPublicKey(Uint8List.fromList(keyPair.publicKey)), 41 | DjbECPrivateKey(Uint8List.fromList(keyPair.privateKey))); 42 | } 43 | 44 | static ECKeyPair generateKeyPairFromPrivate(List private) { 45 | if (private.length != 32) { 46 | throw InvalidKeyException( 47 | 'Invalid private key length: ${private.length}'); 48 | } 49 | final public = List.filled(32, 0); 50 | 51 | private[0] &= 248; 52 | private[31] &= 127; 53 | private[31] |= 64; 54 | 55 | x25519.ScalarBaseMult(public, private); 56 | 57 | return ECKeyPair(DjbECPublicKey(Uint8List.fromList(public)), 58 | DjbECPrivateKey(Uint8List.fromList(private))); 59 | } 60 | 61 | static ECPublicKey decodePointList(List bytes, int offset) => 62 | decodePoint(Uint8List.fromList(bytes), offset); 63 | 64 | static ECPublicKey decodePoint(Uint8List bytes, int offset) { 65 | if (bytes.length - offset < 1) { 66 | throw InvalidKeyException('No key type identifier'); 67 | } 68 | 69 | final type = bytes[offset] & 0xFF; 70 | 71 | switch (type) { 72 | case Curve.djbType: 73 | if (bytes.length - offset < 33) { 74 | throw InvalidKeyException('Bad key length: ${bytes.length}'); 75 | } 76 | 77 | final keyBytes = Uint8List(32); 78 | arraycopy(bytes, offset + 1, keyBytes, 0, keyBytes.length); 79 | return DjbECPublicKey(keyBytes); 80 | default: 81 | throw InvalidKeyException('Bad key type: $type'); 82 | } 83 | } 84 | 85 | static void arraycopy( 86 | List src, int srcPos, List dest, int destPos, int length) { 87 | dest.setRange(destPos, length + destPos, src, srcPos); 88 | } 89 | 90 | static ECPrivateKey decodePrivatePoint(Uint8List bytes) => 91 | DjbECPrivateKey(bytes); 92 | 93 | static Uint8List calculateAgreement( 94 | ECPublicKey? publicKey, ECPrivateKey? privateKey) { 95 | if (publicKey == null) { 96 | throw Exception('publicKey value is null'); 97 | } 98 | 99 | if (privateKey == null) { 100 | throw Exception('privateKey value is null'); 101 | } 102 | if (publicKey.getType() != privateKey.getType()) { 103 | throw Exception('Public and private keys must be of the same type!'); 104 | } 105 | 106 | if (publicKey.getType() == djbType) { 107 | final calculator = agreementCalculator; 108 | if (calculator != null) { 109 | return calculator( 110 | (privateKey as DjbECPrivateKey).privateKey, 111 | (publicKey as DjbECPublicKey).publicKey, 112 | ); 113 | } 114 | 115 | final secretKey = x25519.X25519( 116 | List.from((privateKey as DjbECPrivateKey).privateKey), 117 | List.from((publicKey as DjbECPublicKey).publicKey), 118 | ); 119 | return secretKey; 120 | } else { 121 | throw Exception('Unknown type: ${publicKey.getType()}'); 122 | } 123 | } 124 | 125 | static bool verifySignature( 126 | ECPublicKey? signingKey, Uint8List? message, Uint8List? signature) { 127 | if (signingKey == null || message == null || signature == null) { 128 | throw InvalidKeyException('Values must not be null'); 129 | } 130 | 131 | if (signingKey.getType() == djbType) { 132 | if (signature.length != 64) { 133 | return false; 134 | } 135 | 136 | final publicKey = (signingKey as DjbECPublicKey).publicKey; 137 | return verifySig(publicKey, message, signature); 138 | } else { 139 | throw InvalidKeyException( 140 | 'Unknown Signing Key type${signingKey.getType()}'); 141 | } 142 | } 143 | 144 | static Uint8List calculateSignature( 145 | ECPrivateKey? signingKey, Uint8List? message) { 146 | if (signingKey == null || message == null) { 147 | throw Exception('Values must not be null'); 148 | } 149 | 150 | if (signingKey.getType() == djbType) { 151 | final privateKey = signingKey.serialize(); 152 | final random = generateRandomBytes(); 153 | 154 | return sign(privateKey, message, random); 155 | } else { 156 | throw Exception('Unknown Signing Key type${signingKey.getType()}'); 157 | } 158 | } 159 | 160 | static Uint8List calculateVrfSignature( 161 | ECPrivateKey? signingKey, Uint8List? message) { 162 | if (signingKey == null || message == null) { 163 | throw Exception('Values must not be null'); 164 | } 165 | 166 | if (signingKey.getType() == djbType) { 167 | // TODO 168 | } else { 169 | throw Exception('Unknown Signing Key type${signingKey.getType()}'); 170 | } 171 | return Uint8List(0); 172 | } 173 | 174 | static Uint8List verifyVrfSignature( 175 | ECPublicKey? signingKey, Uint8List? message, Uint8List? signature) { 176 | if (signingKey == null || message == null || signature == null) { 177 | throw Exception('Values must not be null'); 178 | } 179 | 180 | if (signingKey.getType() == djbType) { 181 | // TODO 182 | } else { 183 | throw Exception('Unknown Signing Key type${signingKey.getType()}'); 184 | } 185 | return Uint8List(0); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/src/ecc/djb_ec_private_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'curve.dart'; 4 | import 'ec_private_key.dart'; 5 | 6 | class DjbECPrivateKey extends ECPrivateKey { 7 | DjbECPrivateKey(this._privateKey); 8 | 9 | final Uint8List _privateKey; 10 | 11 | @override 12 | int getType() => Curve.djbType; 13 | 14 | @override 15 | Uint8List serialize() => privateKey; 16 | 17 | Uint8List get privateKey => _privateKey; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/ecc/djb_ec_public_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:meta/meta.dart'; 4 | 5 | import '../eq.dart'; 6 | 7 | import 'curve.dart'; 8 | import 'ec_public_key.dart'; 9 | 10 | @immutable 11 | class DjbECPublicKey extends ECPublicKey { 12 | DjbECPublicKey(this._publicKey); 13 | 14 | final Uint8List _publicKey; 15 | 16 | @override 17 | int getType() => Curve.djbType; 18 | 19 | @override 20 | Uint8List serialize() => Uint8List.fromList([Curve.djbType] + _publicKey); 21 | 22 | Uint8List get publicKey => _publicKey; 23 | 24 | @override 25 | int compareTo(ECPublicKey another) => decodeBigInt(publicKey) 26 | .compareTo(decodeBigInt((another as DjbECPublicKey).publicKey)); 27 | 28 | @override 29 | bool operator ==(Object other) { 30 | if (other is! DjbECPublicKey) return false; 31 | return eq(_publicKey, other._publicKey); 32 | } 33 | 34 | @override 35 | int get hashCode => _publicKey.hashCode; 36 | 37 | BigInt decodeBigInt(List bytes) { 38 | var result = BigInt.from(0); 39 | for (var i = 0; i < bytes.length; i++) { 40 | result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); 41 | } 42 | return result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/ecc/ec_key_pair.dart: -------------------------------------------------------------------------------- 1 | import 'ec_private_key.dart'; 2 | import 'ec_public_key.dart'; 3 | 4 | class ECKeyPair { 5 | ECKeyPair(this._publicKey, this._privateKey); 6 | 7 | final ECPublicKey _publicKey; 8 | final ECPrivateKey _privateKey; 9 | 10 | ECPublicKey get publicKey => _publicKey; 11 | 12 | ECPrivateKey get privateKey => _privateKey; 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/ecc/ec_private_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | abstract class ECPrivateKey { 4 | Uint8List serialize(); 5 | int getType(); 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/ecc/ec_public_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | abstract class ECPublicKey implements Comparable { 4 | static const int keySize = 33; 5 | 6 | Uint8List serialize(); 7 | int getType(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/ecc/ed25519.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:convert/convert.dart'; 4 | import 'package:crypto/crypto.dart' as cr; 5 | import 'package:ed25519_edwards/ed25519_edwards.dart'; 6 | // ignore: implementation_imports 7 | import 'package:ed25519_edwards/src/edwards25519.dart'; 8 | 9 | import 'curve.dart'; 10 | 11 | Uint8List sign(Uint8List privateKey, Uint8List message, Uint8List random) { 12 | final A = ExtendedGroupElement(); 13 | final publicKey = Uint8List(32); 14 | GeScalarMultBase(A, privateKey); 15 | A.ToBytes(publicKey); 16 | 17 | // Calculate r 18 | final diversifier = Uint8List.fromList([ 19 | 0xFE, 20 | 0xFF, 21 | 0xFF, 22 | 0xFF, 23 | 0xFF, 24 | 0xFF, 25 | 0xFF, 26 | 0xFF, 27 | 0xFF, 28 | 0xFF, 29 | 0xFF, 30 | 0xFF, 31 | 0xFF, 32 | 0xFF, 33 | 0xFF, 34 | 0xFF, 35 | 0xFF, 36 | 0xFF, 37 | 0xFF, 38 | 0xFF, 39 | 0xFF, 40 | 0xFF, 41 | 0xFF, 42 | 0xFF, 43 | 0xFF, 44 | 0xFF, 45 | 0xFF, 46 | 0xFF, 47 | 0xFF, 48 | 0xFF, 49 | 0xFF, 50 | 0xFF 51 | ]); 52 | 53 | var output = AccumulatorSink(); 54 | cr.sha512.startChunkedConversion(output) 55 | ..add(diversifier) 56 | ..add(privateKey) 57 | ..add(message) 58 | ..add(random) 59 | ..close(); 60 | final r = output.events.single.bytes; 61 | 62 | final rReduced = Uint8List(32); 63 | ScReduce(rReduced, Uint8List.fromList(r)); 64 | final R = ExtendedGroupElement(); 65 | GeScalarMultBase(R, rReduced); 66 | 67 | final encodedR = Uint8List(32); 68 | R.ToBytes(encodedR); 69 | 70 | output = AccumulatorSink(); 71 | cr.sha512.startChunkedConversion(output) 72 | ..add(encodedR) 73 | ..add(publicKey) 74 | ..add(message) 75 | ..close(); 76 | final hramDigest = output.events.single.bytes; 77 | 78 | final hramDigestReduced = Uint8List(32); 79 | ScReduce(hramDigestReduced, Uint8List.fromList(hramDigest)); 80 | 81 | final s = Uint8List(32); 82 | ScMulAdd(s, hramDigestReduced, privateKey, rReduced); 83 | 84 | final signature = Uint8List(64); 85 | Curve.arraycopy(encodedR, 0, signature, 0, 32); 86 | Curve.arraycopy(s, 0, signature, 32, 32); 87 | signature[63] |= publicKey[31] & 0x80; 88 | 89 | return signature; 90 | } 91 | 92 | // verify checks whether the message has a valid signature. 93 | bool verifySig(Uint8List publicKey, Uint8List message, Uint8List signature) { 94 | publicKey[31] &= 0x7F; 95 | 96 | final edY = FieldElement(); 97 | final one = FieldElement(); 98 | final montX = FieldElement(); 99 | final montXMinusOne = FieldElement(); 100 | final montXPlusOne = FieldElement(); 101 | FeFromBytes(montX, publicKey); 102 | FeOne(one); 103 | FeSub(montXMinusOne, montX, one); 104 | FeAdd(montXPlusOne, montX, one); 105 | FeInvert(montXPlusOne, montXPlusOne); 106 | FeMul(edY, montXMinusOne, montXPlusOne); 107 | 108 | // ignore: non_constant_identifier_names 109 | final A_ed = Uint8List(32); 110 | FeToBytes(A_ed, edY); 111 | 112 | A_ed[31] |= signature[63] & 0x80; 113 | signature[63] &= 0x7F; 114 | 115 | // bool verify(PublicKey publicKey, Uint8List message, Uint8List sig) { 116 | return verify(PublicKey(A_ed.toList()), message, signature); 117 | } 118 | -------------------------------------------------------------------------------- /lib/src/entry.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | base class Entry extends LinkedListEntry> { 4 | Entry(this.value); 5 | T value; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/eq.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | bool eq(List? list1, List? list2) => 4 | ListEquality().equals(list1, list2); 5 | -------------------------------------------------------------------------------- /lib/src/fingerprint/displayable_fingerprint.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/byte_util.dart'; 4 | 5 | class DisplayableFingerprint { 6 | DisplayableFingerprint( 7 | Uint8List localFingerprint, Uint8List remoteFingerprint) { 8 | localFingerprintNumbers = _getDisplayStringFor(localFingerprint); 9 | remoteFingerprintNumbers = _getDisplayStringFor(remoteFingerprint); 10 | } 11 | 12 | late String localFingerprintNumbers; 13 | late String remoteFingerprintNumbers; 14 | 15 | String getDisplayText() { 16 | if (localFingerprintNumbers.compareTo(remoteFingerprintNumbers) <= 0) { 17 | return localFingerprintNumbers + remoteFingerprintNumbers; 18 | } else { 19 | return remoteFingerprintNumbers + localFingerprintNumbers; 20 | } 21 | } 22 | 23 | String _getDisplayStringFor(Uint8List fingerprint) => 24 | _getEncodedChunk(fingerprint, 0) + 25 | _getEncodedChunk(fingerprint, 5) + 26 | _getEncodedChunk(fingerprint, 10) + 27 | _getEncodedChunk(fingerprint, 15) + 28 | _getEncodedChunk(fingerprint, 20) + 29 | _getEncodedChunk(fingerprint, 25); 30 | 31 | String _getEncodedChunk(Uint8List hash, int offset) { 32 | final chunk = ByteUtil.byteArray5ToLong(hash, offset).remainder(100000); 33 | return chunk.toString().padLeft(5, '0'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/fingerprint/fingerprint.dart: -------------------------------------------------------------------------------- 1 | import 'displayable_fingerprint.dart'; 2 | import 'scannable_fingerprint.dart'; 3 | 4 | class Fingerprint { 5 | Fingerprint(this._displayableFingerprint, this._scannableFingerprint); 6 | 7 | final DisplayableFingerprint _displayableFingerprint; 8 | final ScannableFingerprint _scannableFingerprint; 9 | 10 | DisplayableFingerprint get displayableFingerprint => _displayableFingerprint; 11 | ScannableFingerprint get scannableFingerprint => _scannableFingerprint; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/fingerprint/fingerprint_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../identity_key.dart'; 4 | import 'fingerprint.dart'; 5 | 6 | abstract class FingerprintGenerator { 7 | Fingerprint createFor( 8 | int version, 9 | Uint8List localStableIdentifier, 10 | IdentityKey localIdentityKey, 11 | Uint8List remoteStableIdentifier, 12 | IdentityKey remoteIdentityKey); 13 | 14 | Fingerprint createListFor( 15 | int version, 16 | Uint8List localStableIdentifier, 17 | List localIdentityKey, 18 | Uint8List remoteStableIdentifier, 19 | List remoteIdentityKey); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/fingerprint/fingerprint_parsing_exception.dart: -------------------------------------------------------------------------------- 1 | class FingerprintParsingException implements Exception { 2 | FingerprintParsingException(this._message); 3 | 4 | final Exception _message; 5 | 6 | @override 7 | String toString() => 'FingerprintParsingException - $_message'; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/fingerprint/fingerprint_version_mismatch_exception.dart: -------------------------------------------------------------------------------- 1 | class FingerprintVersionMismatchException implements Exception { 2 | FingerprintVersionMismatchException(this._theirVersion, this._ourVersion); 3 | 4 | // ignore: unused_field 5 | final int _theirVersion; 6 | // ignore: unused_field 7 | final int _ourVersion; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/fingerprint/numeric_fingerprint_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:convert/convert.dart'; 4 | import 'package:crypto/crypto.dart'; 5 | 6 | import '../identity_key.dart'; 7 | import '../util/byte_util.dart'; 8 | import '../util/identity_key_comparator.dart'; 9 | import 'displayable_fingerprint.dart'; 10 | import 'fingerprint.dart'; 11 | import 'fingerprint_generator.dart'; 12 | import 'scannable_fingerprint.dart'; 13 | 14 | class NumericFingerprintGenerator implements FingerprintGenerator { 15 | NumericFingerprintGenerator(this._iterations); 16 | 17 | static const int fingerprintVersion = 0; 18 | 19 | final int _iterations; 20 | 21 | @override 22 | Fingerprint createFor( 23 | int version, 24 | Uint8List localStableIdentifier, 25 | IdentityKey localIdentityKey, 26 | Uint8List remoteStableIdentifier, 27 | IdentityKey remoteIdentityKey) => 28 | createListFor(version, localStableIdentifier, [localIdentityKey], 29 | remoteStableIdentifier, [remoteIdentityKey]); 30 | 31 | @override 32 | Fingerprint createListFor( 33 | int version, 34 | Uint8List localStableIdentifier, 35 | List localIdentityKey, 36 | Uint8List remoteStableIdentifier, 37 | List remoteIdentityKey) { 38 | final localFingerprint = 39 | _getFingerprint(_iterations, localStableIdentifier, localIdentityKey); 40 | final remoteFingerprint = 41 | _getFingerprint(_iterations, remoteStableIdentifier, remoteIdentityKey); 42 | final displayableFingerprint = 43 | DisplayableFingerprint(localFingerprint, remoteFingerprint); 44 | final scannableFingerprint = 45 | ScannableFingerprint(version, localFingerprint, remoteFingerprint); 46 | return Fingerprint(displayableFingerprint, scannableFingerprint); 47 | } 48 | 49 | Uint8List _getFingerprint(int iterations, Uint8List stableIdentifier, 50 | List unsortedIdentityKeys) { 51 | final publicKey = _getLogicalKeyBytes(unsortedIdentityKeys); 52 | var hash = ByteUtil.combine([ 53 | ByteUtil.shortToByteArray(fingerprintVersion), 54 | publicKey, 55 | stableIdentifier 56 | ]); 57 | for (var i = 0; i < iterations; i++) { 58 | final output = AccumulatorSink(); 59 | sha512.startChunkedConversion(output) 60 | ..add(hash) 61 | ..add(publicKey) 62 | ..close(); 63 | hash = Uint8List.fromList(output.events.single.bytes); 64 | } 65 | 66 | return hash; 67 | } 68 | 69 | Uint8List _getLogicalKeyBytes(List identityKeys) { 70 | final sortedIdentityKeys = [...identityKeys]..sort(identityKeyComparator); 71 | 72 | final keys = []; 73 | sortedIdentityKeys.forEach((IdentityKey key) { 74 | final publicKeyBytes = key.publicKey.serialize(); 75 | keys.addAll(publicKeyBytes.toList()); 76 | }); 77 | return Uint8List.fromList(keys); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/fingerprint/scannable_fingerprint.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:crypto/crypto.dart'; 4 | import 'package:protobuf/protobuf.dart'; 5 | 6 | import '../state/fingerprint_protocol.pb.dart'; 7 | import '../util/byte_util.dart'; 8 | import 'fingerprint_parsing_exception.dart'; 9 | import 'fingerprint_version_mismatch_exception.dart'; 10 | 11 | class ScannableFingerprint { 12 | ScannableFingerprint(int version, Uint8List localFingerprintData, 13 | Uint8List remoteFingerprintData) { 14 | final localFingerprint = LogicalFingerprint.create() 15 | ..content = ByteUtil.trim(localFingerprintData, 32); 16 | 17 | final remoteFingerprint = LogicalFingerprint.create() 18 | ..content = ByteUtil.trim(remoteFingerprintData, 32); 19 | 20 | _version = version; 21 | _fingerprints = CombinedFingerprints.create() 22 | ..version = version 23 | ..localFingerprint = localFingerprint 24 | ..remoteFingerprint = remoteFingerprint; 25 | } 26 | 27 | late int _version; 28 | late CombinedFingerprints _fingerprints; 29 | 30 | bool compareTo(Uint8List scannedFingerprintData) { 31 | try { 32 | final scanned = CombinedFingerprints.fromBuffer(scannedFingerprintData); 33 | if (!scanned.hasRemoteFingerprint() || 34 | !scanned.hasLocalFingerprint() || 35 | !scanned.hasVersion() || 36 | scanned.version != _version) { 37 | throw FingerprintVersionMismatchException(scanned.version, _version); 38 | } 39 | return Digest(_fingerprints.localFingerprint.content) == 40 | Digest(scanned.remoteFingerprint.content) && 41 | Digest(_fingerprints.remoteFingerprint.content) == 42 | Digest(scanned.localFingerprint.content); 43 | } on InvalidProtocolBufferException catch (e) { 44 | throw FingerprintParsingException(e); 45 | } 46 | } 47 | 48 | Uint8List get fingerprints => _fingerprints.writeToBuffer(); 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/groups/group_cipher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../cbc.dart'; 4 | import '../decryption_callback.dart'; 5 | import '../duplicate_message_exception.dart'; 6 | import '../invalid_key_exception.dart'; 7 | import '../invalid_key_id_exception.dart'; 8 | import '../invalid_message_exception.dart'; 9 | import '../no_session_exception.dart'; 10 | import '../protocol/sender_key_message.dart'; 11 | import 'ratchet/sender_message_key.dart'; 12 | import 'sender_key_name.dart'; 13 | import 'state/sender_key_state.dart'; 14 | import 'state/sender_key_store.dart'; 15 | 16 | class GroupCipher { 17 | GroupCipher(this._senderKeyStore, this._senderKeyId); 18 | 19 | final SenderKeyStore _senderKeyStore; 20 | final SenderKeyName _senderKeyId; 21 | 22 | Future encrypt(Uint8List paddedPlaintext) async { 23 | try { 24 | final record = await _senderKeyStore.loadSenderKey(_senderKeyId); 25 | final senderKeyState = record.getSenderKeyState(); 26 | final senderKey = senderKeyState.senderChainKey.senderMessageKey; 27 | final ciphertext = 28 | aesCbcEncrypt(senderKey.cipherKey, senderKey.iv, paddedPlaintext); 29 | final senderKeyMessage = SenderKeyMessage(senderKeyState.keyId, 30 | senderKey.iteration, ciphertext, senderKeyState.signingKeyPrivate); 31 | final nextSenderChainKey = senderKeyState.senderChainKey.next; 32 | senderKeyState.senderChainKey = nextSenderChainKey; 33 | await _senderKeyStore.storeSenderKey(_senderKeyId, record); 34 | return senderKeyMessage.serialize(); 35 | } on InvalidKeyIdException catch (e) { 36 | throw NoSessionException(e.detailMessage); 37 | } 38 | } 39 | 40 | Future decrypt(Uint8List senderKeyMessageBytes) async => 41 | decryptWithCallback(senderKeyMessageBytes, () {}()); 42 | 43 | Future decryptWithCallback( 44 | Uint8List senderKeyMessageBytes, DecryptionCallback? callback) async { 45 | try { 46 | final record = await _senderKeyStore.loadSenderKey(_senderKeyId); 47 | if (record.isEmpty) { 48 | throw NoSessionException( 49 | 'No group sender key for: ${_senderKeyId.serialize()}'); 50 | } 51 | 52 | final senderKeyMessage = 53 | SenderKeyMessage.fromSerialized(senderKeyMessageBytes); 54 | final senderKeyState = 55 | record.getSenderKeyStateById(senderKeyMessage.keyId); 56 | senderKeyMessage.verifySignature(senderKeyState.signingKeyPublic); 57 | final senderKey = 58 | getSenderKey(senderKeyState, senderKeyMessage.iteration); 59 | final plaintext = aesCbcDecrypt( 60 | senderKey.cipherKey, senderKey.iv, senderKeyMessage.ciphertext); 61 | 62 | if (callback != null) { 63 | callback(plaintext); 64 | } 65 | 66 | await _senderKeyStore.storeSenderKey(_senderKeyId, record); 67 | return plaintext; 68 | } on InvalidKeyIdException catch (e) { 69 | throw InvalidMessageException(e.detailMessage); 70 | } on InvalidKeyException catch (e) { 71 | throw InvalidMessageException(e.detailMessage); 72 | } 73 | } 74 | 75 | SenderMessageKey getSenderKey(SenderKeyState senderKeyState, int iteration) { 76 | var senderChainKey = senderKeyState.senderChainKey; 77 | if (senderChainKey.iteration > iteration) { 78 | if (senderKeyState.hasSenderMessageKey(iteration)) { 79 | return senderKeyState.removeSenderMessageKey(iteration)!; 80 | } else { 81 | throw DuplicateMessageException('Received message with old counter: ' 82 | '${senderChainKey.iteration} , $iteration'); 83 | } 84 | } 85 | 86 | if (iteration - senderChainKey.iteration > 2000) { 87 | throw InvalidMessageException('Over 2000 messages into the future!'); 88 | } 89 | 90 | while (senderChainKey.iteration < iteration) { 91 | senderKeyState.addSenderMessageKey(senderChainKey.senderMessageKey); 92 | senderChainKey = senderChainKey.next; 93 | } 94 | 95 | senderKeyState.senderChainKey = senderChainKey.next; 96 | return senderChainKey.senderMessageKey; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/groups/group_session_builder.dart: -------------------------------------------------------------------------------- 1 | import '../invalid_key_exception.dart'; 2 | import '../invalid_key_id_exception.dart'; 3 | import '../protocol/sender_key_distribution_message_wrapper.dart'; 4 | import '../util/key_helper.dart'; 5 | import 'sender_key_name.dart'; 6 | import 'state/sender_key_store.dart'; 7 | 8 | class GroupSessionBuilder { 9 | GroupSessionBuilder(this._senderKeyStore); 10 | 11 | final SenderKeyStore _senderKeyStore; 12 | 13 | Future process( 14 | SenderKeyName senderKeyName, 15 | SenderKeyDistributionMessageWrapper 16 | senderKeyDistributionMessageWrapper) async { 17 | final senderKeyRecord = await _senderKeyStore.loadSenderKey(senderKeyName); 18 | senderKeyRecord.addSenderKeyState( 19 | senderKeyDistributionMessageWrapper.id, 20 | senderKeyDistributionMessageWrapper.iteration, 21 | senderKeyDistributionMessageWrapper.chainKey, 22 | senderKeyDistributionMessageWrapper.signatureKey); 23 | await _senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 24 | } 25 | 26 | Future create( 27 | SenderKeyName senderKeyName) async { 28 | try { 29 | final senderKeyRecord = 30 | await _senderKeyStore.loadSenderKey(senderKeyName); 31 | if (senderKeyRecord.isEmpty) { 32 | senderKeyRecord.setSenderKeyState(generateSenderKeyId(), 0, 33 | generateSenderKey(), generateSenderSigningKey()); 34 | await _senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); 35 | } 36 | final state = senderKeyRecord.getSenderKeyState(); 37 | return SenderKeyDistributionMessageWrapper( 38 | state.keyId, 39 | state.senderChainKey.iteration, 40 | state.senderChainKey.seed, 41 | state.signingKeyPublic); 42 | } on InvalidKeyIdException catch (e) { 43 | throw AssertionError(e); 44 | } on InvalidKeyException catch (e) { 45 | throw AssertionError(e); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/groups/ratchet/sender_chain_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:crypto/crypto.dart'; 4 | 5 | import 'sender_message_key.dart'; 6 | 7 | class SenderChainKey { 8 | SenderChainKey(this._iteration, this._chainKey); 9 | 10 | static final Uint8List _messageKeySeed = Uint8List.fromList([0x01]); 11 | static final Uint8List _chainKeySeed = Uint8List.fromList([0x02]); 12 | 13 | final int _iteration; 14 | final Uint8List _chainKey; 15 | 16 | int get iteration => _iteration; 17 | 18 | Uint8List get seed => _chainKey; 19 | 20 | SenderMessageKey get senderMessageKey => 21 | SenderMessageKey(_iteration, getDerivative(_messageKeySeed, _chainKey)); 22 | 23 | SenderChainKey get next => 24 | SenderChainKey(_iteration + 1, getDerivative(_chainKeySeed, _chainKey)); 25 | 26 | Uint8List getDerivative(Uint8List seed, Uint8List key) { 27 | final hmacSha256 = Hmac(sha256, key); 28 | final digest = hmacSha256.convert(seed); 29 | return Uint8List.fromList(digest.bytes); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/groups/ratchet/sender_message_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../../kdf/hkdfv3.dart'; 4 | import '../../util/byte_util.dart'; 5 | 6 | class SenderMessageKey { 7 | SenderMessageKey(this._iteration, this._seed) { 8 | final derivative = HKDFv3() 9 | .deriveSecrets(seed, Uint8List.fromList('WhisperGroup'.codeUnits), 48); 10 | final parts = ByteUtil.splitTwo(derivative, 16, 32); 11 | _iv = parts[0]; 12 | _cipherKey = parts[1]; 13 | } 14 | 15 | final int _iteration; 16 | final Uint8List _seed; 17 | late Uint8List _iv; 18 | late Uint8List _cipherKey; 19 | 20 | int get iteration => _iteration; 21 | Uint8List get iv => _iv; 22 | Uint8List get cipherKey => _cipherKey; 23 | Uint8List get seed => _seed; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/groups/sender_key_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | import '../signal_protocol_address.dart'; 4 | 5 | @immutable 6 | class SenderKeyName { 7 | const SenderKeyName(this._groupId, this._sender); 8 | 9 | final String _groupId; 10 | final SignalProtocolAddress _sender; 11 | 12 | String get groupId => _groupId; 13 | 14 | SignalProtocolAddress get sender => _sender; 15 | 16 | String serialize() => 17 | '$_groupId::${_sender.getName()}::${_sender.getDeviceId()}'; 18 | 19 | @override 20 | bool operator ==(Object other) { 21 | if (other is! SenderKeyName) return false; 22 | 23 | return _groupId == other.groupId && _sender == other.sender; 24 | } 25 | 26 | @override 27 | int get hashCode => _groupId.hashCode ^ _sender.hashCode; 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/groups/state/in_memory_sender_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import '../sender_key_name.dart'; 4 | import 'sender_key_record.dart'; 5 | import 'sender_key_store.dart'; 6 | 7 | class InMemorySenderKeyStore extends SenderKeyStore { 8 | final _store = HashMap(); 9 | 10 | @override 11 | Future loadSenderKey(SenderKeyName senderKeyName) async { 12 | try { 13 | final record = _store[senderKeyName]; 14 | if (record == null) { 15 | return SenderKeyRecord(); 16 | } else { 17 | return SenderKeyRecord.fromSerialized(record.serialize()); 18 | } 19 | } on Exception catch (e) { 20 | throw AssertionError(e); 21 | } 22 | } 23 | 24 | @override 25 | Future storeSenderKey( 26 | SenderKeyName senderKeyName, SenderKeyRecord record) async { 27 | _store[senderKeyName] = record; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/groups/state/sender_key_record.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../../ecc/ec_key_pair.dart'; 5 | import '../../ecc/ec_public_key.dart'; 6 | import '../../entry.dart'; 7 | import '../../invalid_key_id_exception.dart'; 8 | import '../../state/local_storage_protocol.pb.dart'; 9 | import 'sender_key_state.dart'; 10 | 11 | class SenderKeyRecord { 12 | SenderKeyRecord(); 13 | 14 | SenderKeyRecord.fromSerialized(Uint8List serialized) { 15 | final senderKeyRecordStructure = 16 | SenderKeyRecordStructure.fromBuffer(serialized); 17 | for (final structure in senderKeyRecordStructure.senderKeyStates) { 18 | _senderKeyStates 19 | .add(Entry(SenderKeyState.fromSenderKeyStateStructure(structure))); 20 | } 21 | } 22 | 23 | static const int _maxStates = 5; 24 | 25 | final LinkedList> _senderKeyStates = 26 | LinkedList>(); 27 | 28 | bool get isEmpty => _senderKeyStates.isEmpty; 29 | 30 | SenderKeyState getSenderKeyState() { 31 | if (_senderKeyStates.isNotEmpty) { 32 | return _senderKeyStates.first.value; 33 | } else { 34 | throw InvalidKeyIdException('No key state in record!'); 35 | } 36 | } 37 | 38 | SenderKeyState getSenderKeyStateById(int keyId) { 39 | for (final state in _senderKeyStates) { 40 | if (state.value.keyId == keyId) { 41 | return state.value; 42 | } 43 | } 44 | throw InvalidKeyIdException('No key for: $keyId'); 45 | } 46 | 47 | void addSenderKeyState( 48 | int id, int iteration, Uint8List chainKey, ECPublicKey signatureKey) { 49 | _senderKeyStates.addFirst(Entry( 50 | SenderKeyState.fromPublicKey(id, iteration, chainKey, signatureKey))); 51 | if (_senderKeyStates.length > _maxStates) { 52 | _senderKeyStates.remove(_senderKeyStates.last); 53 | } 54 | } 55 | 56 | void setSenderKeyState( 57 | int id, int iteration, Uint8List chainKey, ECKeyPair signatureKey) { 58 | _senderKeyStates 59 | ..clear() 60 | ..add(Entry( 61 | SenderKeyState.fromKeyPair(id, iteration, chainKey, signatureKey))); 62 | } 63 | 64 | Uint8List serialize() { 65 | final recordStructure = SenderKeyRecordStructure.create(); 66 | _senderKeyStates.forEach((entry) { 67 | recordStructure.senderKeyStates.add(entry.value.structure); 68 | }); 69 | return recordStructure.writeToBuffer(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/groups/state/sender_key_state.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:optional/optional.dart'; 4 | 5 | import '../../ecc/curve.dart'; 6 | import '../../ecc/ec_key_pair.dart'; 7 | import '../../ecc/ec_private_key.dart'; 8 | import '../../ecc/ec_public_key.dart'; 9 | import '../../state/local_storage_protocol.pb.dart'; 10 | import '../ratchet/sender_chain_key.dart'; 11 | import '../ratchet/sender_message_key.dart'; 12 | 13 | class SenderKeyState { 14 | SenderKeyState.fromPublicKey(int id, int iteration, Uint8List chainKey, 15 | ECPublicKey signatureKeyPublic) { 16 | init(id, iteration, chainKey, signatureKeyPublic, const Optional.empty()); 17 | } 18 | 19 | SenderKeyState.fromKeyPair( 20 | int id, int iteration, Uint8List chainKey, ECKeyPair signatureKey) { 21 | final signatureKeyPublic = signatureKey.publicKey; 22 | final signatureKeyPrivate = Optional.of(signatureKey.privateKey); 23 | init(id, iteration, chainKey, signatureKeyPublic, signatureKeyPrivate); 24 | } 25 | 26 | SenderKeyState.fromSenderKeyStateStructure( 27 | SenderKeyStateStructure senderKeyStateStructure) { 28 | _senderKeyStateStructure = senderKeyStateStructure; 29 | } 30 | 31 | static const int _maxMessageKeys = 2000; 32 | 33 | late SenderKeyStateStructure _senderKeyStateStructure; 34 | 35 | void init( 36 | int id, int iteration, Uint8List chainKey, ECPublicKey signatureKeyPublic, 37 | [Optional? signatureKeyPrivate]) { 38 | final seed = Uint8List.fromList(chainKey); 39 | final senderChainKeyStructure = 40 | SenderKeyStateStructureSenderChainKey.create() 41 | ..iteration = iteration 42 | ..seed = seed; 43 | final signingKeyStructure = SenderKeyStateStructureSenderSigningKey.create() 44 | ..public = signatureKeyPublic.serialize(); 45 | if (signatureKeyPrivate!.isPresent) { 46 | signingKeyStructure.private = signatureKeyPrivate.value.serialize(); 47 | } 48 | _senderKeyStateStructure = SenderKeyStateStructure.create() 49 | ..senderKeyId = id 50 | ..senderChainKey = senderChainKeyStructure 51 | ..senderSigningKey = signingKeyStructure; 52 | } 53 | 54 | int get keyId => _senderKeyStateStructure.senderKeyId; 55 | 56 | SenderChainKey get senderChainKey => SenderChainKey( 57 | _senderKeyStateStructure.senderChainKey.iteration, 58 | Uint8List.fromList(_senderKeyStateStructure.senderChainKey.seed)); 59 | 60 | set senderChainKey(SenderChainKey senderChainKey) => { 61 | _senderKeyStateStructure.senderChainKey = 62 | SenderKeyStateStructureSenderChainKey.create() 63 | ..iteration = senderChainKey.iteration 64 | ..seed = List.from(senderChainKey.seed) 65 | }; 66 | 67 | ECPublicKey get signingKeyPublic => Curve.decodePointList( 68 | _senderKeyStateStructure.senderSigningKey.public, 0); 69 | 70 | ECPrivateKey get signingKeyPrivate => Curve.decodePrivatePoint( 71 | Uint8List.fromList(_senderKeyStateStructure.senderSigningKey.private)); 72 | 73 | bool hasSenderMessageKey(int iteration) { 74 | for (final senderMessageKey in _senderKeyStateStructure.senderMessageKeys) { 75 | if (senderMessageKey.iteration == iteration) { 76 | return true; 77 | } 78 | } 79 | return false; 80 | } 81 | 82 | void addSenderMessageKey(SenderMessageKey senderMessageKey) { 83 | final senderMessageKeyStructure = 84 | SenderKeyStateStructureSenderMessageKey.create() 85 | ..iteration = senderMessageKey.iteration 86 | ..seed = senderMessageKey.seed; 87 | _senderKeyStateStructure.senderMessageKeys.add(senderMessageKeyStructure); 88 | if (_senderKeyStateStructure.senderMessageKeys.length > _maxMessageKeys) { 89 | _senderKeyStateStructure.senderMessageKeys.removeAt(0); 90 | } 91 | } 92 | 93 | SenderMessageKey? removeSenderMessageKey(int iteration) { 94 | _senderKeyStateStructure.senderMessageKeys 95 | .toList() 96 | .addAll(_senderKeyStateStructure.senderMessageKeys); 97 | final index = _senderKeyStateStructure.senderMessageKeys 98 | .indexWhere((item) => item.iteration == iteration); 99 | if (index == -1) return null; 100 | final senderMessageKey = 101 | _senderKeyStateStructure.senderMessageKeys.removeAt(index); 102 | return SenderMessageKey( 103 | senderMessageKey.iteration, Uint8List.fromList(senderMessageKey.seed)); 104 | } 105 | 106 | SenderKeyStateStructure get structure => _senderKeyStateStructure; 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/groups/state/sender_key_store.dart: -------------------------------------------------------------------------------- 1 | import '../sender_key_name.dart'; 2 | import 'sender_key_record.dart'; 3 | 4 | abstract class SenderKeyStore { 5 | Future storeSenderKey( 6 | SenderKeyName senderKeyName, SenderKeyRecord record); 7 | 8 | Future loadSenderKey(SenderKeyName senderKeyName); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/identity_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:convert/convert.dart'; 4 | import 'package:meta/meta.dart'; 5 | 6 | import 'ecc/curve.dart'; 7 | import 'ecc/ec_public_key.dart'; 8 | 9 | @immutable 10 | class IdentityKey { 11 | const IdentityKey(this._publicKey); 12 | 13 | factory IdentityKey.fromBytes(Uint8List bytes, int offset) => 14 | IdentityKey(Curve.decodePoint(bytes, offset)); 15 | 16 | final ECPublicKey _publicKey; 17 | 18 | ECPublicKey get publicKey => _publicKey; 19 | 20 | Uint8List serialize() => _publicKey.serialize(); 21 | 22 | String getFingerprint() => hex.encode(_publicKey.serialize()); 23 | 24 | @override 25 | bool operator ==(Object other) { 26 | if (other is! IdentityKey) return false; 27 | 28 | return _publicKey == other._publicKey; 29 | } 30 | 31 | @override 32 | int get hashCode => _publicKey.hashCode; 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/identity_key_pair.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'ecc/curve.dart'; 4 | import 'ecc/ec_private_key.dart'; 5 | import 'identity_key.dart'; 6 | import 'state/local_storage_protocol.pb.dart'; 7 | 8 | class IdentityKeyPair { 9 | IdentityKeyPair(this._publicKey, this._privateKey); 10 | 11 | IdentityKeyPair.fromSerialized(Uint8List serialized) { 12 | final structure = IdentityKeyPairStructure.fromBuffer(serialized); 13 | _publicKey = 14 | IdentityKey.fromBytes(Uint8List.fromList(structure.publicKey), 0); 15 | _privateKey = 16 | Curve.decodePrivatePoint(Uint8List.fromList(structure.privateKey)); 17 | } 18 | 19 | late IdentityKey _publicKey; 20 | late ECPrivateKey _privateKey; 21 | 22 | IdentityKey getPublicKey() => _publicKey; 23 | 24 | ECPrivateKey getPrivateKey() => _privateKey; 25 | 26 | Uint8List serialize() { 27 | final i = IdentityKeyPairStructure.create() 28 | ..publicKey = List.from(_publicKey.serialize()) 29 | ..privateKey = List.from(_privateKey.serialize()); 30 | return i.toBuilder().writeToBuffer(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/invalid_key_exception.dart: -------------------------------------------------------------------------------- 1 | class InvalidKeyException implements Exception { 2 | InvalidKeyException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'InvalidKeyException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/invalid_key_id_exception.dart: -------------------------------------------------------------------------------- 1 | class InvalidKeyIdException implements Exception { 2 | InvalidKeyIdException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'InvalidKeyIdException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/invalid_mac_exception.dart: -------------------------------------------------------------------------------- 1 | class InvalidMacException implements Exception { 2 | InvalidMacException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'InvalidMacException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/invalid_message_exception.dart: -------------------------------------------------------------------------------- 1 | class InvalidMessageException implements Exception { 2 | InvalidMessageException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'InvalidMessageException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/kdf/derived_message_secrets.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/byte_util.dart'; 4 | 5 | class DerivedMessageSecrets { 6 | DerivedMessageSecrets(Uint8List okm) { 7 | final keys = 8 | ByteUtil.split(okm, _cipherKeyLength, _macKeyLength, _ivLength); 9 | 10 | _cipherKey = keys[0]; 11 | _macKey = keys[1]; 12 | _iv = keys[2]; 13 | } 14 | 15 | static const int size = 80; 16 | static const int _cipherKeyLength = 32; 17 | static const int _macKeyLength = 32; 18 | static const int _ivLength = 16; 19 | 20 | late Uint8List _cipherKey; 21 | late Uint8List _macKey; 22 | late Uint8List _iv; 23 | 24 | Uint8List getCipherKey() => _cipherKey; 25 | 26 | Uint8List getMacKey() => _macKey; 27 | 28 | Uint8List getIv() => _iv; 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/kdf/derived_root_secrets.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../util/byte_util.dart'; 4 | 5 | class DerivedRootSecrets { 6 | DerivedRootSecrets(Uint8List okm) { 7 | final keys = ByteUtil.splitTwo(okm, 32, 32); 8 | _rootKey = keys[0]; 9 | _chainKey = keys[1]; 10 | } 11 | 12 | static const int size = 64; 13 | 14 | late Uint8List _rootKey; 15 | late Uint8List _chainKey; 16 | 17 | Uint8List getRootKey() => _rootKey; 18 | 19 | Uint8List getChainKey() => _chainKey; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/kdf/hkdf.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:convert/convert.dart'; 5 | import 'package:crypto/crypto.dart'; 6 | 7 | import '../invalid_key_exception.dart'; 8 | import 'hkdfv2.dart'; 9 | import 'hkdfv3.dart'; 10 | 11 | abstract class HKDF { 12 | static const int hashOutputSize = 32; 13 | 14 | static HKDF createFor(int messageVersion) { 15 | switch (messageVersion) { 16 | case 2: 17 | return HKDFv2(); 18 | case 3: 19 | return HKDFv3(); 20 | default: 21 | throw AssertionError('Unknown version: $messageVersion'); 22 | } 23 | } 24 | 25 | Uint8List deriveSecrets( 26 | Uint8List inputKeyMaterial, Uint8List info, int outputLength) { 27 | final salt = Uint8List(hashOutputSize); 28 | return deriveSecrets4(inputKeyMaterial, salt, info, outputLength); 29 | } 30 | 31 | Uint8List deriveSecrets4(Uint8List inputKeyMaterial, Uint8List salt, 32 | Uint8List info, int outputLength) { 33 | final prk = extract(salt, inputKeyMaterial); 34 | return expand(prk, info, outputLength); 35 | } 36 | 37 | Uint8List extract(Uint8List salt, Uint8List inputKeyMaterial) { 38 | final hmacSha256 = Hmac(sha256, salt); 39 | final digest = hmacSha256.convert(inputKeyMaterial); 40 | return Uint8List.fromList(digest.bytes); 41 | } 42 | 43 | Uint8List expand(Uint8List prk, Uint8List? info, int outputSize) { 44 | try { 45 | final iterations = 46 | (outputSize.toDouble() / hashOutputSize.toDouble()).ceil(); 47 | var mix = Uint8List(0); 48 | final results = Uint8List(outputSize); 49 | var remainingBytes = outputSize; 50 | 51 | for (var i = getIterationStartOffset(); 52 | i < iterations + getIterationStartOffset(); 53 | i++) { 54 | final mac = Hmac(sha256, prk); 55 | final output = AccumulatorSink(); 56 | final input = mac.startChunkedConversion(output)..add(mix); 57 | if (info != null) { 58 | input.add(info); 59 | } 60 | input 61 | ..add([i]) 62 | ..close(); 63 | final stepResult = Uint8List.fromList(output.events.single.bytes); 64 | final stepSize = min(remainingBytes, stepResult.length); 65 | 66 | for (var j = 0; j < stepSize; j++) { 67 | final offset = (i - getIterationStartOffset()) * hashOutputSize + j; 68 | results[offset] = stepResult[j]; 69 | } 70 | 71 | mix = stepResult; 72 | remainingBytes -= stepSize; 73 | } 74 | return results.buffer.asUint8List(); 75 | } on InvalidKeyException catch (e) { 76 | throw AssertionError(e); 77 | } 78 | } 79 | 80 | int getIterationStartOffset(); 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/kdf/hkdfv2.dart: -------------------------------------------------------------------------------- 1 | import 'hkdf.dart'; 2 | 3 | class HKDFv2 extends HKDF { 4 | @override 5 | int getIterationStartOffset() => 0; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/kdf/hkdfv3.dart: -------------------------------------------------------------------------------- 1 | import 'hkdf.dart'; 2 | 3 | class HKDFv3 extends HKDF { 4 | @override 5 | int getIterationStartOffset() => 1; 6 | } 7 | -------------------------------------------------------------------------------- /lib/src/legacy_message_exception.dart: -------------------------------------------------------------------------------- 1 | class LegacyMessageException implements Exception { 2 | LegacyMessageException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'LegacyMessageException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/no_session_exception.dart: -------------------------------------------------------------------------------- 1 | class NoSessionException implements Exception { 2 | NoSessionException(this.detailMessage); 3 | final String detailMessage; 4 | 5 | @override 6 | String toString() => 'NoSessionException - $detailMessage'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/protocol/ciphertext_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | abstract class CiphertextMessage { 4 | static const int currentVersion = 3; 5 | 6 | static const int whisperType = 2; 7 | static const int prekeyType = 3; 8 | static const int senderKeyType = 4; 9 | static const int senderKeyDistributionType = 5; 10 | 11 | // This should be the worst case (worse than V2). So not always accurate, but good enough for padding. 12 | static const int encryptedMessageOverhead = 53; 13 | 14 | Uint8List serialize(); 15 | int getType(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/protocol/device_consistency_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:protobuf/protobuf.dart'; 4 | 5 | import '../devices/device_consistency_commitment.dart'; 6 | import '../devices/device_consistency_signature.dart'; 7 | import '../ecc/curve.dart'; 8 | import '../identity_key.dart'; 9 | import '../identity_key_pair.dart'; 10 | import '../invalid_key_exception.dart'; 11 | import '../invalid_message_exception.dart'; 12 | import '../state/whisper_text_protocol.pb.dart'; 13 | 14 | class DeviceConsistencyMessage { 15 | DeviceConsistencyMessage( 16 | DeviceConsistencyCommitment commitment, IdentityKeyPair identityKeyPair) { 17 | try { 18 | final signatureBytes = Curve.calculateVrfSignature( 19 | identityKeyPair.getPrivateKey(), commitment.serialized); 20 | final vrfOutputBytes = Curve.verifyVrfSignature( 21 | identityKeyPair.getPublicKey().publicKey, 22 | commitment.serialized, 23 | signatureBytes); 24 | 25 | _generation = commitment.generation; 26 | _signature = DeviceConsistencySignature(signatureBytes, vrfOutputBytes); 27 | final d = DeviceConsistencyCodeMessage.create() 28 | ..generation = commitment.generation 29 | ..signature = _signature.signature.toList(); 30 | _serialized = d.writeToBuffer(); 31 | } on InvalidKeyException catch (e) { 32 | throw AssertionError(e); 33 | } 34 | // } on VrfSignatureVerificationFailedException catch (e) { 35 | // throw AssertionError(e); 36 | // } 37 | } 38 | 39 | DeviceConsistencyMessage.fromSerialized( 40 | DeviceConsistencyCommitment commitment, 41 | Uint8List serialized, 42 | IdentityKey identityKey) { 43 | try { 44 | final message = DeviceConsistencyCodeMessage.fromBuffer(serialized); 45 | final vrfOutputBytes = Curve.verifyVrfSignature(identityKey.publicKey, 46 | commitment.serialized, Uint8List.fromList(message.signature)); 47 | 48 | _generation = message.generation; 49 | _signature = DeviceConsistencySignature( 50 | Uint8List.fromList(message.signature), vrfOutputBytes); 51 | _serialized = serialized; 52 | } on InvalidProtocolBufferException catch (e) { 53 | throw InvalidMessageException(e.message); 54 | } on InvalidKeyException catch (e) { 55 | throw InvalidMessageException(e.detailMessage); 56 | } 57 | // } on VrfSignatureVerificationFailedException catch (e) { 58 | // throw AssertionError(e); 59 | // } 60 | } 61 | 62 | late DeviceConsistencySignature _signature; 63 | late int _generation; 64 | late Uint8List _serialized; 65 | 66 | Uint8List get serialized => _serialized; 67 | 68 | DeviceConsistencySignature get signature => _signature; 69 | 70 | int get generation => _generation; 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/protocol/pre_key_signal_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:optional/optional.dart'; 4 | 5 | import '../ecc/curve.dart'; 6 | import '../ecc/ec_public_key.dart'; 7 | import '../identity_key.dart'; 8 | import '../invalid_key_exception.dart'; 9 | import '../invalid_message_exception.dart'; 10 | import '../legacy_message_exception.dart'; 11 | import '../protocol/ciphertext_message.dart'; 12 | import '../protocol/signal_message.dart'; 13 | import '../state/whisper_text_protocol.pb.dart' as signal_protos; 14 | import '../util/byte_util.dart'; 15 | 16 | class PreKeySignalMessage extends CiphertextMessage { 17 | PreKeySignalMessage(Uint8List serialized) { 18 | try { 19 | _version = ByteUtil.highBitsToInt(serialized[0]); 20 | 21 | final preKeyWhisperMessage = 22 | signal_protos.PreKeySignalMessage.fromBuffer(serialized.sublist(1)); 23 | 24 | if (!preKeyWhisperMessage.hasSignedPreKeyId() || 25 | !preKeyWhisperMessage.hasBaseKey() || 26 | !preKeyWhisperMessage.hasIdentityKey() || 27 | !preKeyWhisperMessage.hasMessage()) { 28 | throw InvalidMessageException('Incomplete message.'); 29 | } 30 | 31 | this.serialized = serialized; 32 | registrationId = preKeyWhisperMessage.registrationId; 33 | preKeyId = preKeyWhisperMessage.hasPreKeyId() 34 | ? Optional.of(preKeyWhisperMessage.preKeyId) 35 | : const Optional.empty(); 36 | signedPreKeyId = preKeyWhisperMessage.hasSignedPreKeyId() 37 | ? preKeyWhisperMessage.signedPreKeyId 38 | : -1; 39 | baseKey = Curve.decodePoint( 40 | Uint8List.fromList(preKeyWhisperMessage.baseKey), 0); 41 | identityKey = IdentityKey(Curve.decodePoint( 42 | Uint8List.fromList(preKeyWhisperMessage.identityKey), 0)); 43 | message = SignalMessage.fromSerialized( 44 | Uint8List.fromList(preKeyWhisperMessage.message)); 45 | } on InvalidKeyException catch (e) { 46 | throw InvalidMessageException(e.detailMessage); 47 | } on LegacyMessageException catch (e) { 48 | throw InvalidMessageException(e.detailMessage); 49 | } 50 | } 51 | 52 | PreKeySignalMessage.from(this._version, this.registrationId, this.preKeyId, 53 | this.signedPreKeyId, this.baseKey, this.identityKey, this.message) { 54 | final builder = signal_protos.PreKeySignalMessage.create() 55 | ..signedPreKeyId = signedPreKeyId 56 | ..baseKey = baseKey.serialize() 57 | ..identityKey = identityKey.serialize() 58 | ..message = message.serialize() 59 | ..registrationId = registrationId; 60 | 61 | if (preKeyId.isPresent) { 62 | builder.preKeyId = preKeyId.value; 63 | } 64 | 65 | final versionBytes = [ 66 | ByteUtil.intsToByteHighAndLow(_version, CiphertextMessage.currentVersion) 67 | ]; 68 | 69 | final messageBytes = builder.toBuilder().writeToBuffer(); 70 | serialized = Uint8List.fromList(versionBytes + messageBytes); 71 | } 72 | 73 | late int _version; 74 | late int registrationId; 75 | late Optional preKeyId; 76 | late int signedPreKeyId; 77 | late ECPublicKey baseKey; 78 | late IdentityKey identityKey; 79 | late SignalMessage message; 80 | late Uint8List serialized; 81 | 82 | int getMessageVersion() => _version; 83 | 84 | IdentityKey getIdentityKey() => identityKey; 85 | 86 | int getRegistrationId() => registrationId; 87 | 88 | Optional getPreKeyId() => preKeyId; 89 | 90 | int getSignedPreKeyId() => signedPreKeyId; 91 | 92 | ECPublicKey getBaseKey() => baseKey; 93 | 94 | SignalMessage getWhisperMessage() => message; 95 | 96 | @override 97 | int getType() => CiphertextMessage.prekeyType; 98 | 99 | @override 100 | Uint8List serialize() => serialized; 101 | } 102 | -------------------------------------------------------------------------------- /lib/src/protocol/sender_key_distribution_message_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:protobuf/protobuf.dart'; 4 | 5 | import '../ecc/curve.dart'; 6 | import '../ecc/ec_public_key.dart'; 7 | import '../invalid_key_exception.dart'; 8 | import '../invalid_message_exception.dart'; 9 | import '../legacy_message_exception.dart'; 10 | import '../state/whisper_text_protocol.pb.dart'; 11 | import '../util/byte_util.dart'; 12 | import 'ciphertext_message.dart'; 13 | 14 | class SenderKeyDistributionMessageWrapper extends CiphertextMessage { 15 | SenderKeyDistributionMessageWrapper( 16 | int id, int iteration, Uint8List chainKey, ECPublicKey signatureKey) { 17 | final version = Uint8List.fromList([ 18 | ByteUtil.intsToByteHighAndLow( 19 | CiphertextMessage.currentVersion, CiphertextMessage.currentVersion) 20 | ]); 21 | final protobuf = SenderKeyDistributionMessage.create() 22 | ..id = id 23 | ..iteration = iteration 24 | ..chainKey = List.from(chainKey) 25 | ..signingKey = List.from(signatureKey.serialize()); 26 | _id = id; 27 | _iteration = iteration; 28 | _chainKey = chainKey; 29 | _signatureKey = signatureKey; 30 | _serialized = ByteUtil.combine([version, protobuf.writeToBuffer()]); 31 | } 32 | 33 | SenderKeyDistributionMessageWrapper.fromSerialized(Uint8List serialized) { 34 | try { 35 | final messageParts = 36 | ByteUtil.splitTwo(serialized, 1, serialized.length - 1); 37 | final version = messageParts[0][0]; 38 | final message = messageParts[1]; 39 | 40 | if (ByteUtil.highBitsToInt(version) < CiphertextMessage.currentVersion) { 41 | throw LegacyMessageException( 42 | 'Legacy message: ${ByteUtil.highBitsToInt(version)}'); 43 | } 44 | if (ByteUtil.highBitsToInt(version) > CiphertextMessage.currentVersion) { 45 | throw InvalidMessageException( 46 | 'Unknown version: ${ByteUtil.highBitsToInt(version)}'); 47 | } 48 | 49 | final distributionMessages = 50 | SenderKeyDistributionMessage.fromBuffer(message); 51 | if (!distributionMessages.hasId() || 52 | !distributionMessages.hasIteration() || 53 | !distributionMessages.hasChainKey() || 54 | !distributionMessages.hasSigningKey()) { 55 | throw InvalidMessageException('Incomplete message.'); 56 | } 57 | 58 | _serialized = serialized; 59 | _id = distributionMessages.id; 60 | _iteration = distributionMessages.iteration; 61 | _chainKey = Uint8List.fromList(distributionMessages.chainKey); 62 | _signatureKey = Curve.decodePoint( 63 | Uint8List.fromList(distributionMessages.signingKey), 0); 64 | } on InvalidProtocolBufferException catch (e) { 65 | throw InvalidMessageException(e.message); 66 | } on InvalidKeyException catch (e) { 67 | throw InvalidMessageException(e.detailMessage); 68 | } 69 | } 70 | 71 | late int _id; 72 | late int _iteration; 73 | late Uint8List _chainKey; 74 | late ECPublicKey _signatureKey; 75 | late Uint8List _serialized; 76 | 77 | @override 78 | int getType() => CiphertextMessage.senderKeyDistributionType; 79 | 80 | @override 81 | Uint8List serialize() => _serialized; 82 | 83 | int get iteration => _iteration; 84 | Uint8List get chainKey => _chainKey; 85 | ECPublicKey get signatureKey => _signatureKey; 86 | int get id => _id; 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/protocol/sender_key_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../ecc/curve.dart'; 4 | import '../ecc/ec_private_key.dart'; 5 | import '../ecc/ec_public_key.dart'; 6 | import '../invalid_key_exception.dart'; 7 | import '../invalid_key_id_exception.dart'; 8 | import '../invalid_message_exception.dart'; 9 | import '../legacy_message_exception.dart'; 10 | import '../state/whisper_text_protocol.pb.dart' as protocol; 11 | import '../util/byte_util.dart'; 12 | import 'ciphertext_message.dart'; 13 | 14 | class SenderKeyMessage extends CiphertextMessage { 15 | SenderKeyMessage(int keyId, int iteration, Uint8List ciphertext, 16 | ECPrivateKey signatureKey) { 17 | final version = Uint8List.fromList([ 18 | ByteUtil.intsToByteHighAndLow( 19 | CiphertextMessage.currentVersion, CiphertextMessage.currentVersion) 20 | ]); 21 | final message = protocol.SenderKeyMessage.create() 22 | ..id = keyId 23 | ..iteration = iteration 24 | ..ciphertext = ciphertext; 25 | final messageList = message.writeToBuffer(); 26 | final signature = 27 | _getSignature(signatureKey, ByteUtil.combine([version, messageList])); 28 | _serialized = ByteUtil.combine([version, messageList, signature]); 29 | _messageVersion = CiphertextMessage.currentVersion; 30 | _keyId = keyId; 31 | _iteration = iteration; 32 | _ciphertext = ciphertext; 33 | } 34 | 35 | SenderKeyMessage.fromSerialized(Uint8List serialized) { 36 | final messageParts = ByteUtil.split(serialized, 1, 37 | serialized.length - 1 - signatureLength, signatureLength); 38 | final version = messageParts[0][0]; 39 | final message = messageParts[1]; 40 | // ignore: unused_local_variable 41 | final signature = messageParts[2]; 42 | 43 | if (ByteUtil.highBitsToInt(version) < 3) { 44 | throw LegacyMessageException( 45 | 'Legacy message: ${ByteUtil.highBitsToInt(version)}'); 46 | } 47 | 48 | if (ByteUtil.highBitsToInt(version) > CiphertextMessage.currentVersion) { 49 | throw InvalidMessageException( 50 | 'Unknown version: ${ByteUtil.highBitsToInt(version)}'); 51 | } 52 | 53 | final senderKeyMessage = protocol.SenderKeyMessage.fromBuffer(message); 54 | 55 | if (!senderKeyMessage.hasId() || 56 | !senderKeyMessage.hasIteration() || 57 | !senderKeyMessage.hasCiphertext()) { 58 | throw InvalidMessageException('Incomplete message.'); 59 | } 60 | 61 | _serialized = serialized; 62 | _messageVersion = ByteUtil.highBitsToInt(version); 63 | _keyId = senderKeyMessage.id; 64 | _iteration = senderKeyMessage.iteration; 65 | _ciphertext = Uint8List.fromList(senderKeyMessage.ciphertext); 66 | } 67 | 68 | static const int signatureLength = 64; 69 | 70 | // ignore: unused_field 71 | late int _messageVersion; 72 | late int _keyId; 73 | late int _iteration; 74 | late Uint8List _ciphertext; 75 | late Uint8List _serialized; 76 | 77 | Uint8List _getSignature(ECPrivateKey signatureKey, Uint8List serialized) { 78 | try { 79 | return Curve.calculateSignature(signatureKey, serialized); 80 | } on InvalidKeyIdException catch (e) { 81 | throw AssertionError(e); 82 | } 83 | } 84 | 85 | int get keyId => _keyId; 86 | 87 | int get iteration => _iteration; 88 | 89 | Uint8List get ciphertext => _ciphertext; 90 | 91 | void verifySignature(ECPublicKey signatureKey) { 92 | try { 93 | final parts = ByteUtil.splitTwo( 94 | _serialized, _serialized.length - signatureLength, signatureLength); 95 | if (!Curve.verifySignature(signatureKey, parts[0], parts[1])) { 96 | throw InvalidMessageException('Invalid signature!'); 97 | } 98 | } on InvalidKeyException catch (e) { 99 | throw InvalidMessageException(e.detailMessage); 100 | } 101 | } 102 | 103 | @override 104 | int getType() => CiphertextMessage.senderKeyType; 105 | 106 | @override 107 | Uint8List serialize() => _serialized; 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/protocol/signal_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:convert/convert.dart'; 4 | import 'package:crypto/crypto.dart'; 5 | import 'package:protobuf/protobuf.dart'; 6 | 7 | import '../ecc/curve.dart'; 8 | import '../ecc/ec_public_key.dart'; 9 | import '../identity_key.dart'; 10 | import '../invalid_key_exception.dart'; 11 | import '../invalid_message_exception.dart'; 12 | import '../legacy_message_exception.dart'; 13 | import '../protocol/ciphertext_message.dart'; 14 | import '../state/whisper_text_protocol.pb.dart' as signal_protos; 15 | import '../util/byte_util.dart'; 16 | 17 | class SignalMessage extends CiphertextMessage { 18 | SignalMessage( 19 | int messageVersion, 20 | Uint8List macKey, 21 | ECPublicKey senderRatchetKey, 22 | int counter, 23 | int previousCounter, 24 | Uint8List ciphertext, 25 | IdentityKey senderIdentityKey, 26 | IdentityKey? receiverIdentityKey) { 27 | final version = Uint8List.fromList([ 28 | ByteUtil.intsToByteHighAndLow( 29 | messageVersion, CiphertextMessage.currentVersion) 30 | ]); 31 | 32 | final m = signal_protos.SignalMessage.create() 33 | ..ratchetKey = senderRatchetKey.serialize() 34 | ..counter = counter 35 | ..previousCounter = previousCounter 36 | ..ciphertext = ciphertext; 37 | final message = m.writeToBuffer(); 38 | 39 | final mac = _getMac(senderIdentityKey, receiverIdentityKey!, macKey, 40 | ByteUtil.combine([version, message])); 41 | 42 | _serialized = ByteUtil.combine([version, message, mac]); 43 | _senderRatchetKey = senderRatchetKey; 44 | _counter = counter; 45 | _previousCounter = previousCounter; 46 | _ciphertext = ciphertext; 47 | _messageVersion = messageVersion; 48 | } 49 | 50 | SignalMessage.fromSerialized(Uint8List serialized) { 51 | try { 52 | final messageParts = ByteUtil.split( 53 | serialized, 1, serialized.length - 1 - macLength, macLength); 54 | final version = messageParts[0].first; 55 | final message = messageParts[1]; 56 | // ignore: unused_local_variable 57 | final mac = messageParts[2]; 58 | 59 | if (ByteUtil.highBitsToInt(version) < CiphertextMessage.currentVersion) { 60 | throw LegacyMessageException( 61 | 'Legacy message: $ByteUtil.highBitsToInt(version)'); 62 | } 63 | 64 | if (ByteUtil.highBitsToInt(version) > CiphertextMessage.currentVersion) { 65 | throw InvalidMessageException( 66 | 'Unknown version: $ByteUtil.highBitsToInt(version)'); 67 | } 68 | 69 | final whisperMessage = signal_protos.SignalMessage.fromBuffer(message); 70 | 71 | if (!whisperMessage.hasCiphertext() || 72 | !whisperMessage.hasCounter() || 73 | !whisperMessage.hasRatchetKey()) { 74 | throw InvalidMessageException('Incomplete message.'); 75 | } 76 | 77 | _serialized = serialized; 78 | _senderRatchetKey = 79 | Curve.decodePoint(Uint8List.fromList(whisperMessage.ratchetKey), 0); 80 | _messageVersion = ByteUtil.highBitsToInt(version); 81 | _counter = whisperMessage.counter; 82 | _previousCounter = whisperMessage.previousCounter; 83 | _ciphertext = Uint8List.fromList(whisperMessage.ciphertext); 84 | } on InvalidProtocolBufferException catch (e) { 85 | throw InvalidMessageException(e.toString()); 86 | } on InvalidKeyException catch (e) { 87 | throw InvalidMessageException(e.detailMessage); 88 | } 89 | } 90 | 91 | static const int macLength = 8; 92 | 93 | late int _messageVersion; 94 | late ECPublicKey _senderRatchetKey; 95 | late int _counter; 96 | // ignore: unused_field 97 | late int _previousCounter; 98 | late Uint8List _ciphertext; 99 | late Uint8List _serialized; 100 | 101 | ECPublicKey getSenderRatchetKey() => _senderRatchetKey; 102 | 103 | int getMessageVersion() => _messageVersion; 104 | 105 | int getCounter() => _counter; 106 | 107 | Uint8List getBody() => _ciphertext; 108 | 109 | void verifyMac(IdentityKey senderIdentityKey, IdentityKey receiverIdentityKey, 110 | Uint8List macKey) { 111 | final parts = ByteUtil.splitTwo( 112 | _serialized, _serialized.length - macLength, macLength); 113 | final ourMac = 114 | _getMac(senderIdentityKey, receiverIdentityKey, macKey, parts[0]); 115 | final theirMac = parts[1]; 116 | 117 | if (Digest(ourMac) != Digest(theirMac)) { 118 | throw InvalidMessageException('Bad Mac!'); 119 | } 120 | } 121 | 122 | Uint8List _getMac(IdentityKey senderIdentityKey, 123 | IdentityKey receiverIdentityKey, Uint8List macKey, Uint8List serialized) { 124 | final mac = Hmac(sha256, macKey); // HMAC-SHA256 125 | 126 | final output = AccumulatorSink(); 127 | mac.startChunkedConversion(output) 128 | ..add(senderIdentityKey.publicKey.serialize()) 129 | ..add(receiverIdentityKey.publicKey.serialize()) 130 | ..add(serialized) 131 | ..close(); 132 | final fullMac = Uint8List.fromList(output.events.single.bytes); 133 | return ByteUtil.trim(fullMac, macLength); 134 | } 135 | 136 | @override 137 | int getType() => CiphertextMessage.whisperType; 138 | 139 | @override 140 | Uint8List serialize() => _serialized; 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/provisioning_cipher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:crypto/crypto.dart'; 5 | 6 | import 'cbc.dart'; 7 | import 'ecc/curve.dart'; 8 | import 'ecc/ec_public_key.dart'; 9 | import 'eq.dart'; 10 | import 'invalid_mac_exception.dart'; 11 | import 'kdf/derived_root_secrets.dart'; 12 | import 'kdf/hkdfv3.dart'; 13 | import 'legacy_message_exception.dart'; 14 | import 'util/byte_util.dart'; 15 | 16 | const String provision = 'Mixin Provisioning Message'; 17 | 18 | class ProvisionEnvelope { 19 | ProvisionEnvelope(this.publicKey, this.body); 20 | 21 | ProvisionEnvelope.fromJson(Map json) 22 | : publicKey = base64Decode(json['public_key'] as String), 23 | body = base64Decode(json['body'] as String); 24 | 25 | final Uint8List publicKey; 26 | final Uint8List body; 27 | 28 | Map toJson() => { 29 | 'public_key': base64Encode(publicKey), 30 | 'body': base64Encode(body), 31 | }; 32 | } 33 | 34 | Uint8List decrypt(String privateKey, String content) { 35 | final ourPrivateKey = base64Decode(privateKey); 36 | final envelopeDecode = base64Decode(content); 37 | 38 | final map = jsonDecode(String.fromCharCodes(envelopeDecode)); 39 | final provisionEnvelope = 40 | ProvisionEnvelope.fromJson(map as Map); 41 | final publicKeyable = Curve.decodePoint(provisionEnvelope.publicKey, 0); 42 | final message = provisionEnvelope.body; 43 | if (message[0] != 1) { 44 | throw LegacyMessageException('Invalid version'); 45 | } 46 | final iv = Uint8List.fromList(message.getRange(1, 16 + 1).toList()); 47 | final mac = message.getRange(message.length - 32, message.length).toList(); 48 | final ivAndCiphertext = 49 | Uint8List.fromList(message.getRange(0, message.length - 32).toList()); 50 | final cipherText = Uint8List.fromList( 51 | message.getRange(16 + 1, message.length - 32).toList()); 52 | final sharedSecret = Curve.calculateAgreement( 53 | publicKeyable, Curve.decodePrivatePoint(ourPrivateKey)); 54 | 55 | final derivedSecretBytes = HKDFv3().deriveSecrets(sharedSecret, 56 | Uint8List.fromList(utf8.encode(provision)), DerivedRootSecrets.size); 57 | 58 | final aesKey = 59 | Uint8List.fromList(derivedSecretBytes.getRange(0, 32).toList()); 60 | final macKey = Uint8List.fromList( 61 | derivedSecretBytes.getRange(32, derivedSecretBytes.length).toList()); 62 | 63 | if (!verifyMAC(macKey, ivAndCiphertext, mac)) { 64 | throw InvalidMacException("MAC doesn't match!"); 65 | } 66 | final plaintext = aesCbcDecrypt(aesKey, iv, cipherText); 67 | return plaintext; 68 | } 69 | 70 | bool verifyMAC(Uint8List key, Uint8List input, List mac) { 71 | final hmacSha256 = Hmac(sha256, key); 72 | final digest = hmacSha256.convert(input); 73 | return eq(digest.bytes, mac); 74 | } 75 | 76 | class ProvisioningCipher { 77 | ProvisioningCipher(this._theirPublicKey); 78 | 79 | final ECPublicKey _theirPublicKey; 80 | 81 | Uint8List encrypt(Uint8List message) { 82 | final ourKeyPair = Curve.generateKeyPair(); 83 | final sharedSecret = 84 | Curve.calculateAgreement(_theirPublicKey, ourKeyPair.privateKey); 85 | final derivedSecret = HKDFv3().deriveSecrets( 86 | sharedSecret, Uint8List.fromList(utf8.encode(provision)), 64); 87 | final parts = ByteUtil.splitTwo(derivedSecret, 32, 32); 88 | 89 | final version = Uint8List.fromList([1]); 90 | final ciphertext = getCiphertext(parts[0], message); 91 | final mac = _getMac(parts[1], ByteUtil.combine([version, ciphertext])); 92 | final body = ByteUtil.combine([version, ciphertext, mac]); 93 | final envelope = ProvisionEnvelope(ourKeyPair.publicKey.serialize(), body); 94 | final result = jsonEncode(envelope); 95 | return Uint8List.fromList(utf8.encode(result)); 96 | } 97 | 98 | Uint8List getCiphertext(Uint8List key, Uint8List message) { 99 | final iv = Uint8List(16); 100 | final m = aesCbcEncrypt(key, iv, message); 101 | return Uint8List.fromList(iv + m); 102 | } 103 | 104 | Uint8List _getMac(Uint8List key, Uint8List message) { 105 | final hmacSha256 = Hmac(sha256, key); 106 | final digest = hmacSha256.convert(message); 107 | return Uint8List.fromList(digest.bytes); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/src/ratchet/alice_signal_protocol_parameters.dart: -------------------------------------------------------------------------------- 1 | import 'package:optional/optional.dart'; 2 | 3 | import '../ecc/ec_key_pair.dart'; 4 | import '../ecc/ec_public_key.dart'; 5 | import '../identity_key.dart'; 6 | import '../identity_key_pair.dart'; 7 | 8 | class AliceSignalProtocolParameters { 9 | AliceSignalProtocolParameters({ 10 | required this.ourIdentityKey, 11 | required this.ourBaseKey, 12 | required this.theirIdentityKey, 13 | required this.theirSignedPreKey, 14 | required this.theirRatchetKey, 15 | required this.theirOneTimePreKey, 16 | }); 17 | 18 | final IdentityKeyPair ourIdentityKey; 19 | final ECKeyPair ourBaseKey; 20 | 21 | final IdentityKey theirIdentityKey; 22 | final ECPublicKey theirSignedPreKey; 23 | final Optional theirOneTimePreKey; 24 | final ECPublicKey theirRatchetKey; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/ratchet/bob_signal_protocol_parameters.dart: -------------------------------------------------------------------------------- 1 | import 'package:optional/optional.dart'; 2 | 3 | import '../ecc/ec_key_pair.dart'; 4 | import '../ecc/ec_public_key.dart'; 5 | import '../identity_key.dart'; 6 | import '../identity_key_pair.dart'; 7 | 8 | class BobSignalProtocolParameters { 9 | BobSignalProtocolParameters({ 10 | required this.ourIdentityKey, 11 | required this.ourSignedPreKey, 12 | required this.ourRatchetKey, 13 | required this.ourOneTimePreKey, 14 | required this.theirIdentityKey, 15 | required this.theirBaseKey, 16 | }); 17 | 18 | final IdentityKeyPair ourIdentityKey; 19 | final ECKeyPair ourSignedPreKey; 20 | final Optional ourOneTimePreKey; 21 | final ECKeyPair ourRatchetKey; 22 | 23 | final IdentityKey theirIdentityKey; 24 | final ECPublicKey theirBaseKey; 25 | 26 | IdentityKeyPair getOurIdentityKey() => ourIdentityKey; 27 | 28 | ECKeyPair getOurSignedPreKey() => ourSignedPreKey; 29 | 30 | Optional getOurOneTimePreKey() => ourOneTimePreKey; 31 | 32 | IdentityKey getTheirIdentityKey() => theirIdentityKey; 33 | 34 | ECPublicKey getTheirBaseKey() => theirBaseKey; 35 | 36 | ECKeyPair getOurRatchetKey() => ourRatchetKey; 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/ratchet/chain_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:core'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:crypto/crypto.dart'; 6 | import '../kdf/derived_message_secrets.dart'; 7 | import '../kdf/hkdf.dart'; 8 | import '../ratchet/message_keys.dart'; 9 | 10 | class ChainKey { 11 | ChainKey(this._kdf, this._key, this._index); 12 | 13 | static final Uint8List messageKeySeed = Uint8List.fromList([0x01]); 14 | static final Uint8List chainKeySeed = Uint8List.fromList([0x02]); 15 | 16 | final HKDF _kdf; 17 | final Uint8List _key; 18 | final int _index; 19 | 20 | Uint8List get key => _key; 21 | 22 | int get index => _index; 23 | 24 | ChainKey getNextChainKey() { 25 | final nextKey = _getBaseMaterial(chainKeySeed); 26 | return ChainKey(_kdf, nextKey, _index + 1); 27 | } 28 | 29 | MessageKeys getMessageKeys() { 30 | final bytes = Uint8List.fromList(utf8.encode('WhisperMessageKeys')); 31 | 32 | final inputKeyMaterial = _getBaseMaterial(messageKeySeed); 33 | final keyMaterialBytes = 34 | _kdf.deriveSecrets(inputKeyMaterial, bytes, DerivedMessageSecrets.size); 35 | final keyMaterial = DerivedMessageSecrets(keyMaterialBytes); 36 | 37 | return MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), 38 | keyMaterial.getIv(), _index); 39 | } 40 | 41 | Uint8List _getBaseMaterial(Uint8List seed) { 42 | final hmacSha256 = Hmac(sha256, _key); 43 | final digest = hmacSha256.convert(seed); 44 | return Uint8List.fromList(digest.bytes); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/ratchet/message_keys.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | class MessageKeys { 4 | MessageKeys(this.cipherKey, this.macKey, this.iv, this.counter); 5 | 6 | final Uint8List cipherKey; 7 | final Uint8List macKey; 8 | final Uint8List iv; 9 | final int counter; 10 | 11 | Uint8List getCipherKey() => cipherKey; 12 | 13 | Uint8List getMacKey() => macKey; 14 | 15 | Uint8List getIv() => iv; 16 | 17 | int getCounter() => counter; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/ratchet/ratcheting_session.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:optional/optional.dart'; 5 | 6 | import '../ecc/curve.dart'; 7 | import '../ecc/ec_key_pair.dart'; 8 | import '../ecc/ec_public_key.dart'; 9 | import '../kdf/hkdf.dart'; 10 | import '../kdf/hkdfv3.dart'; 11 | import '../protocol/ciphertext_message.dart'; 12 | import '../ratchet/alice_signal_protocol_parameters.dart'; 13 | import '../ratchet/bob_signal_protocol_parameters.dart'; 14 | import '../ratchet/chain_key.dart'; 15 | import '../ratchet/root_key.dart'; 16 | import '../ratchet/symmetric_signal_protocol_parameters.dart'; 17 | import '../state/session_state.dart'; 18 | import '../util/byte_util.dart'; 19 | 20 | class RatchetingSession { 21 | static void initializeSession( 22 | SessionState sessionState, SymmetricSignalProtocolParameters parameters) { 23 | if (isAlice(parameters.ourBaseKey.publicKey, parameters.theirBaseKey)) { 24 | final aliceParameters = AliceSignalProtocolParameters( 25 | ourBaseKey: parameters.ourBaseKey, 26 | ourIdentityKey: parameters.ourIdentityKey, 27 | theirRatchetKey: parameters.theirRatchetKey, 28 | theirIdentityKey: parameters.theirIdentityKey, 29 | theirSignedPreKey: parameters.theirBaseKey, 30 | theirOneTimePreKey: const Optional.empty(), 31 | ); 32 | RatchetingSession.initializeSessionAlice(sessionState, aliceParameters); 33 | } else { 34 | final bobParameters = BobSignalProtocolParameters( 35 | ourIdentityKey: parameters.ourIdentityKey, 36 | ourRatchetKey: parameters.ourRatchetKey, 37 | ourSignedPreKey: parameters.ourBaseKey, 38 | ourOneTimePreKey: const Optional.empty(), 39 | theirBaseKey: parameters.theirBaseKey, 40 | theirIdentityKey: parameters.theirIdentityKey, 41 | ); 42 | 43 | RatchetingSession.initializeSessionBob(sessionState, bobParameters); 44 | } 45 | } 46 | 47 | static void initializeSessionAlice( 48 | SessionState sessionState, AliceSignalProtocolParameters parameters) { 49 | try { 50 | sessionState 51 | ..sessionVersion = CiphertextMessage.currentVersion 52 | ..remoteIdentityKey = parameters.theirIdentityKey 53 | ..localIdentityKey = parameters.ourIdentityKey.getPublicKey(); 54 | 55 | final sendingRatchetKey = Curve.generateKeyPair(); 56 | final secrets = [ 57 | ...getDiscontinuityBytes(), 58 | ...Curve.calculateAgreement(parameters.theirSignedPreKey, 59 | parameters.ourIdentityKey.getPrivateKey()), 60 | ...Curve.calculateAgreement(parameters.theirIdentityKey.publicKey, 61 | parameters.ourBaseKey.privateKey), 62 | ...Curve.calculateAgreement( 63 | parameters.theirSignedPreKey, parameters.ourBaseKey.privateKey) 64 | ]; 65 | 66 | if (parameters.theirOneTimePreKey.isPresent) { 67 | secrets.addAll(Curve.calculateAgreement( 68 | parameters.theirOneTimePreKey.value, 69 | parameters.ourBaseKey.privateKey)); 70 | } 71 | 72 | final derivedKeys = calculateDerivedKeys(Uint8List.fromList(secrets)); 73 | final sendingChain = derivedKeys 74 | .getRootKey() 75 | .createChain(parameters.theirRatchetKey, sendingRatchetKey); 76 | 77 | sessionState 78 | ..addReceiverChain( 79 | parameters.theirRatchetKey, derivedKeys.getChainKey()) 80 | ..setSenderChain(sendingRatchetKey, sendingChain.$2) 81 | ..rootKey = sendingChain.$1; 82 | } on Exception catch (e) { 83 | throw AssertionError(e); 84 | } 85 | } 86 | 87 | static void initializeSessionBob( 88 | SessionState sessionState, BobSignalProtocolParameters parameters) { 89 | try { 90 | sessionState 91 | ..sessionVersion = CiphertextMessage.currentVersion 92 | ..remoteIdentityKey = parameters.theirIdentityKey 93 | ..localIdentityKey = parameters.ourIdentityKey.getPublicKey(); 94 | 95 | final secrets = [ 96 | ...getDiscontinuityBytes(), 97 | ...Curve.calculateAgreement(parameters.theirIdentityKey.publicKey, 98 | parameters.ourSignedPreKey.privateKey), 99 | ...Curve.calculateAgreement( 100 | parameters.theirBaseKey, parameters.ourIdentityKey.getPrivateKey()), 101 | ...Curve.calculateAgreement( 102 | parameters.theirBaseKey, parameters.ourSignedPreKey.privateKey) 103 | ]; 104 | if (parameters.ourOneTimePreKey.isPresent) { 105 | secrets.addAll(Curve.calculateAgreement(parameters.theirBaseKey, 106 | parameters.ourOneTimePreKey.value.privateKey)); 107 | } 108 | 109 | final derivedKeys = calculateDerivedKeys(Uint8List.fromList(secrets)); 110 | 111 | sessionState 112 | ..setSenderChain(parameters.ourRatchetKey, derivedKeys.getChainKey()) 113 | ..rootKey = derivedKeys.getRootKey(); 114 | } on Exception catch (e) { 115 | throw AssertionError(e); 116 | } 117 | } 118 | 119 | static Uint8List getDiscontinuityBytes() { 120 | final discontinuity = Uint8List(32); 121 | final len = discontinuity.length; 122 | for (var i = 0; i < len; i++) { 123 | discontinuity[i] = 0xFF; 124 | } 125 | return discontinuity; 126 | } 127 | 128 | static DerivedKeys calculateDerivedKeys(Uint8List masterSecret) { 129 | final HKDF kdf = HKDFv3(); 130 | final bytes = Uint8List.fromList(utf8.encode('WhisperText')); 131 | final derivedSecretBytes = kdf.deriveSecrets(masterSecret, bytes, 64); 132 | final derivedSecrets = ByteUtil.splitTwo(derivedSecretBytes, 32, 32); 133 | 134 | return DerivedKeys( 135 | RootKey(kdf, derivedSecrets[0]), ChainKey(kdf, derivedSecrets[1], 0)); 136 | } 137 | 138 | static bool isAlice(ECPublicKey ourKey, ECPublicKey theirKey) => 139 | ourKey.compareTo(theirKey) < 0; 140 | } 141 | 142 | class DerivedKeys { 143 | DerivedKeys(this._rootKey, this._chainKey); 144 | 145 | final RootKey _rootKey; 146 | final ChainKey _chainKey; 147 | 148 | RootKey getRootKey() => _rootKey; 149 | 150 | ChainKey getChainKey() => _chainKey; 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/ratchet/root_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import '../ecc/curve.dart'; 5 | import '../ecc/ec_key_pair.dart'; 6 | import '../ecc/ec_public_key.dart'; 7 | import '../kdf/derived_root_secrets.dart'; 8 | import '../kdf/hkdf.dart'; 9 | import '../ratchet/chain_key.dart'; 10 | 11 | class RootKey { 12 | RootKey(this._kdf, this._key); 13 | 14 | final HKDF _kdf; 15 | final Uint8List _key; 16 | 17 | Uint8List getKeyBytes() => _key; 18 | 19 | (RootKey, ChainKey) createChain( 20 | ECPublicKey theirRatchetKey, ECKeyPair ourRatchetKey) { 21 | final sharedSecret = 22 | Curve.calculateAgreement(theirRatchetKey, ourRatchetKey.privateKey); 23 | final bytes = Uint8List.fromList(utf8.encode('WhisperRatchet')); 24 | final derivedSecretBytes = 25 | _kdf.deriveSecrets4(sharedSecret, _key, bytes, DerivedRootSecrets.size); 26 | final derivedSecrets = DerivedRootSecrets(derivedSecretBytes); 27 | 28 | final newRootKey = RootKey(_kdf, derivedSecrets.getRootKey()); 29 | final newChainKey = ChainKey(_kdf, derivedSecrets.getChainKey(), 0); 30 | 31 | return (newRootKey, newChainKey); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/ratchet/symmetric_signal_protocol_parameters.dart: -------------------------------------------------------------------------------- 1 | import '../ecc/ec_key_pair.dart'; 2 | import '../ecc/ec_public_key.dart'; 3 | import '../identity_key.dart'; 4 | import '../identity_key_pair.dart'; 5 | 6 | class SymmetricSignalProtocolParameters { 7 | SymmetricSignalProtocolParameters({ 8 | required this.ourBaseKey, 9 | required this.ourRatchetKey, 10 | required this.ourIdentityKey, 11 | required this.theirBaseKey, 12 | required this.theirRatchetKey, 13 | required this.theirIdentityKey, 14 | }); 15 | 16 | final ECKeyPair ourBaseKey; 17 | final ECKeyPair ourRatchetKey; 18 | final IdentityKeyPair ourIdentityKey; 19 | 20 | final ECPublicKey theirBaseKey; 21 | final ECPublicKey theirRatchetKey; 22 | final IdentityKey theirIdentityKey; 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/session_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:optional/optional.dart'; 2 | 3 | import 'ecc/curve.dart'; 4 | import 'ecc/ec_key_pair.dart'; 5 | import 'invalid_key_exception.dart'; 6 | import 'protocol/pre_key_signal_message.dart'; 7 | import 'ratchet/alice_signal_protocol_parameters.dart'; 8 | import 'ratchet/bob_signal_protocol_parameters.dart'; 9 | import 'ratchet/ratcheting_session.dart'; 10 | import 'signal_protocol_address.dart'; 11 | import 'state/identity_key_store.dart'; 12 | import 'state/pre_key_bundle.dart'; 13 | import 'state/pre_key_store.dart'; 14 | import 'state/session_record.dart'; 15 | import 'state/session_store.dart'; 16 | import 'state/signal_protocol_store.dart'; 17 | import 'state/signed_pre_key_store.dart'; 18 | import 'untrusted_identity_exception.dart'; 19 | import 'util/log.dart' as $log; 20 | 21 | class SessionBuilder { 22 | SessionBuilder(this._sessionStore, this._preKeyStore, this._signedPreKeyStore, 23 | this._identityKeyStore, this._remoteAddress); 24 | 25 | SessionBuilder.fromSignalStore( 26 | SignalProtocolStore store, SignalProtocolAddress remoteAddress) 27 | : this(store, store, store, store, remoteAddress); 28 | 29 | static const String tag = 'SessionBuilder'; 30 | 31 | SessionStore _sessionStore; 32 | PreKeyStore _preKeyStore; 33 | SignedPreKeyStore _signedPreKeyStore; 34 | IdentityKeyStore _identityKeyStore; 35 | SignalProtocolAddress _remoteAddress; 36 | 37 | Future> process( 38 | SessionRecord sessionRecord, PreKeySignalMessage message) async { 39 | final theirIdentityKey = message.getIdentityKey(); 40 | 41 | if (!await _identityKeyStore.isTrustedIdentity( 42 | _remoteAddress, theirIdentityKey, Direction.receiving)) { 43 | throw UntrustedIdentityException( 44 | _remoteAddress.getName(), theirIdentityKey); 45 | } 46 | 47 | final unsignedPreKeyId = processV3(sessionRecord, message); 48 | 49 | await _identityKeyStore.saveIdentity(_remoteAddress, theirIdentityKey); 50 | 51 | return unsignedPreKeyId; 52 | } 53 | 54 | Future> processV3( 55 | SessionRecord sessionRecord, PreKeySignalMessage message) async { 56 | if (sessionRecord.hasSessionState( 57 | message.getMessageVersion(), message.getBaseKey().serialize())) { 58 | $log.log( 59 | "We've already setup a session for this V3 message, letting bundled message fall through..."); 60 | return const Optional.empty(); 61 | } 62 | 63 | final ourSignedPreKey = _signedPreKeyStore 64 | .loadSignedPreKey(message.getSignedPreKeyId()) 65 | .then((value) => value.getKeyPair()); 66 | 67 | late final Optional ourOneTimePreKey; 68 | if (message.getPreKeyId().isPresent) { 69 | ourOneTimePreKey = Optional.of(await _preKeyStore 70 | .loadPreKey(message.getPreKeyId().value) 71 | .then((value) => value.getKeyPair())); 72 | } else { 73 | ourOneTimePreKey = const Optional.empty(); 74 | } 75 | 76 | if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); 77 | 78 | final parameters = BobSignalProtocolParameters( 79 | theirBaseKey: message.getBaseKey(), 80 | theirIdentityKey: message.getIdentityKey(), 81 | ourIdentityKey: await _identityKeyStore.getIdentityKeyPair(), 82 | ourSignedPreKey: await ourSignedPreKey, 83 | ourRatchetKey: await ourSignedPreKey, 84 | ourOneTimePreKey: ourOneTimePreKey, 85 | ); 86 | 87 | RatchetingSession.initializeSessionBob( 88 | sessionRecord.sessionState, parameters); 89 | 90 | sessionRecord.sessionState.localRegistrationId = 91 | await _identityKeyStore.getLocalRegistrationId(); 92 | sessionRecord.sessionState.remoteRegistrationId = 93 | message.getRegistrationId(); 94 | sessionRecord.sessionState.aliceBaseKey = message.getBaseKey().serialize(); 95 | 96 | if (message.getPreKeyId().isPresent) { 97 | return message.getPreKeyId(); 98 | } else { 99 | return const Optional.empty(); 100 | } 101 | } 102 | 103 | Future processPreKeyBundle(PreKeyBundle preKey) async { 104 | if (!await _identityKeyStore.isTrustedIdentity( 105 | _remoteAddress, preKey.getIdentityKey(), Direction.sending)) { 106 | throw UntrustedIdentityException( 107 | _remoteAddress.getName(), preKey.getIdentityKey()); 108 | } 109 | 110 | if (preKey.getSignedPreKey() != null && 111 | !Curve.verifySignature( 112 | preKey.getIdentityKey().publicKey, 113 | preKey.getSignedPreKey()!.serialize(), 114 | preKey.getSignedPreKeySignature())) { 115 | throw InvalidKeyException('Invalid signature on device key!'); 116 | } 117 | 118 | if (preKey.getSignedPreKey() == null) { 119 | throw InvalidKeyException('No signed prekey!'); 120 | } 121 | 122 | final sessionRecord = await _sessionStore.loadSession(_remoteAddress); 123 | final ourBaseKey = Curve.generateKeyPair(); 124 | final theirSignedPreKey = preKey.getSignedPreKey(); 125 | final theirOneTimePreKey = Optional.ofNullable(preKey.getPreKey()); 126 | final theirOneTimePreKeyId = theirOneTimePreKey.isPresent 127 | ? Optional.ofNullable(preKey.getPreKeyId()) 128 | : const Optional.empty(); 129 | 130 | final parameters = AliceSignalProtocolParameters( 131 | ourBaseKey: ourBaseKey, 132 | ourIdentityKey: await _identityKeyStore.getIdentityKeyPair(), 133 | theirIdentityKey: preKey.getIdentityKey(), 134 | theirSignedPreKey: theirSignedPreKey!, 135 | theirRatchetKey: theirSignedPreKey, 136 | theirOneTimePreKey: theirOneTimePreKey, 137 | ); 138 | 139 | if (!sessionRecord.isFresh()) sessionRecord.archiveCurrentState(); 140 | 141 | RatchetingSession.initializeSessionAlice( 142 | sessionRecord.sessionState, parameters); 143 | 144 | sessionRecord.sessionState.setUnacknowledgedPreKeyMessage( 145 | theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.publicKey); 146 | sessionRecord.sessionState.localRegistrationId = 147 | await _identityKeyStore.getLocalRegistrationId(); 148 | sessionRecord.sessionState.remoteRegistrationId = 149 | preKey.getRegistrationId(); 150 | sessionRecord.sessionState.aliceBaseKey = ourBaseKey.publicKey.serialize(); 151 | 152 | await _identityKeyStore.saveIdentity( 153 | _remoteAddress, preKey.getIdentityKey()); 154 | await _sessionStore.storeSession(_remoteAddress, sessionRecord); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/signal_protocol_address.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | @immutable 4 | class SignalProtocolAddress { 5 | const SignalProtocolAddress(this._name, this._deviceId); 6 | 7 | final String _name; 8 | final int _deviceId; 9 | 10 | String getName() => _name; 11 | 12 | int getDeviceId() => _deviceId; 13 | 14 | @override 15 | String toString() => '$_name:$_deviceId'; 16 | 17 | @override 18 | bool operator ==(Object other) { 19 | if (other is! SignalProtocolAddress) return false; 20 | 21 | return _name == other._name && _deviceId == other._deviceId; 22 | } 23 | 24 | @override 25 | int get hashCode => _name.hashCode ^ _deviceId; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/state/fingerprint_protocol.pb.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core' as $core; 2 | import 'dart:core'; 3 | 4 | import 'package:protobuf/protobuf.dart' as $pb; 5 | 6 | class LogicalFingerprint extends $pb.GeneratedMessage { 7 | factory LogicalFingerprint({ 8 | $core.List<$core.int>? content, 9 | }) { 10 | final _result = create(); 11 | if (content != null) { 12 | _result.content = content; 13 | } 14 | return _result; 15 | } 16 | 17 | LogicalFingerprint._() : super(); 18 | 19 | factory LogicalFingerprint.fromBuffer($core.List<$core.int> i, 20 | [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => 21 | create()..mergeFromBuffer(i, r); 22 | 23 | factory LogicalFingerprint.fromJson($core.String i, 24 | [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => 25 | create()..mergeFromJson(i, r); 26 | static final $pb.BuilderInfo _i = $pb.BuilderInfo( 27 | const $core.bool.fromEnvironment('protobuf.omit_message_names') 28 | ? '' 29 | : 'LogicalFingerprint', 30 | package: const $pb.PackageName( 31 | $core.bool.fromEnvironment('protobuf.omit_message_names') 32 | ? '' 33 | : 'textsecure'), 34 | createEmptyInstance: create) 35 | ..a<$core.List<$core.int>>( 36 | 1, 37 | const $core.bool.fromEnvironment('protobuf.omit_field_names') 38 | ? '' 39 | : 'content', 40 | $pb.PbFieldType.OY) 41 | ..hasRequiredFields = false; 42 | 43 | @$core.Deprecated('Using this can add significant overhead to your binary. ' 44 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 45 | 'Will be removed in next major version') 46 | @override 47 | LogicalFingerprint clone() => LogicalFingerprint()..mergeFromMessage(this); 48 | 49 | @$core.Deprecated('Using this can add significant overhead to your binary. ' 50 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 51 | 'Will be removed in next major version') 52 | @override 53 | LogicalFingerprint copyWith(void Function(LogicalFingerprint) updates) => 54 | super.copyWith((message) => updates(message as LogicalFingerprint)) 55 | as LogicalFingerprint; // ignore: deprecated_member_use 56 | @override 57 | $pb.BuilderInfo get info_ => _i; 58 | 59 | @$core.pragma('dart2js:noInline') 60 | static LogicalFingerprint create() => LogicalFingerprint._(); 61 | 62 | @override 63 | LogicalFingerprint createEmptyInstance() => create(); 64 | 65 | static $pb.PbList createRepeated() => 66 | $pb.PbList(); 67 | 68 | @$core.pragma('dart2js:noInline') 69 | static LogicalFingerprint getDefault() => _defaultInstance ??= 70 | $pb.GeneratedMessage.$_defaultFor(create); 71 | static LogicalFingerprint? _defaultInstance; 72 | 73 | @$pb.TagNumber(1) 74 | $core.List<$core.int> get content => $_getN(0); 75 | 76 | @$pb.TagNumber(1) 77 | set content($core.List<$core.int> v) { 78 | $_setBytes(0, v); 79 | } 80 | 81 | @$pb.TagNumber(1) 82 | $core.bool hasContent() => $_has(0); 83 | 84 | @$pb.TagNumber(1) 85 | void clearContent() => clearField(1); 86 | } 87 | 88 | class CombinedFingerprints extends $pb.GeneratedMessage { 89 | factory CombinedFingerprints({ 90 | $core.int? version, 91 | LogicalFingerprint? localFingerprint, 92 | LogicalFingerprint? remoteFingerprint, 93 | }) { 94 | final _result = create(); 95 | if (version != null) { 96 | _result.version = version; 97 | } 98 | if (localFingerprint != null) { 99 | _result.localFingerprint = localFingerprint; 100 | } 101 | if (remoteFingerprint != null) { 102 | _result.remoteFingerprint = remoteFingerprint; 103 | } 104 | return _result; 105 | } 106 | 107 | CombinedFingerprints._() : super(); 108 | 109 | factory CombinedFingerprints.fromBuffer($core.List<$core.int> i, 110 | [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => 111 | create()..mergeFromBuffer(i, r); 112 | 113 | factory CombinedFingerprints.fromJson($core.String i, 114 | [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => 115 | create()..mergeFromJson(i, r); 116 | static final $pb.BuilderInfo _i = $pb.BuilderInfo( 117 | const $core.bool.fromEnvironment('protobuf.omit_message_names') 118 | ? '' 119 | : 'CombinedFingerprints', 120 | package: const $pb.PackageName( 121 | $core.bool.fromEnvironment('protobuf.omit_message_names') 122 | ? '' 123 | : 'textsecure'), 124 | createEmptyInstance: create) 125 | ..a<$core.int>( 126 | 1, 127 | const $core.bool.fromEnvironment('protobuf.omit_field_names') 128 | ? '' 129 | : 'version', 130 | $pb.PbFieldType.OU3) 131 | ..aOM( 132 | 2, 133 | const $core.bool.fromEnvironment('protobuf.omit_field_names') 134 | ? '' 135 | : 'localFingerprint', 136 | protoName: 'localFingerprint', 137 | subBuilder: LogicalFingerprint.create) 138 | ..aOM( 139 | 3, 140 | const $core.bool.fromEnvironment('protobuf.omit_field_names') 141 | ? '' 142 | : 'remoteFingerprint', 143 | protoName: 'remoteFingerprint', 144 | subBuilder: LogicalFingerprint.create) 145 | ..hasRequiredFields = false; 146 | 147 | @$core.Deprecated('Using this can add significant overhead to your binary. ' 148 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 149 | 'Will be removed in next major version') 150 | @override 151 | CombinedFingerprints clone() => 152 | CombinedFingerprints()..mergeFromMessage(this); 153 | 154 | @$core.Deprecated('Using this can add significant overhead to your binary. ' 155 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 156 | 'Will be removed in next major version') 157 | @override 158 | CombinedFingerprints copyWith(void Function(CombinedFingerprints) updates) => 159 | super.copyWith((message) => updates(message as CombinedFingerprints)) 160 | as CombinedFingerprints; // ignore: deprecated_member_use 161 | @override 162 | $pb.BuilderInfo get info_ => _i; 163 | 164 | @$core.pragma('dart2js:noInline') 165 | static CombinedFingerprints create() => CombinedFingerprints._(); 166 | 167 | @override 168 | CombinedFingerprints createEmptyInstance() => create(); 169 | 170 | static $pb.PbList createRepeated() => 171 | $pb.PbList(); 172 | 173 | @$core.pragma('dart2js:noInline') 174 | static CombinedFingerprints getDefault() => _defaultInstance ??= 175 | $pb.GeneratedMessage.$_defaultFor(create); 176 | static CombinedFingerprints? _defaultInstance; 177 | 178 | @$pb.TagNumber(1) 179 | $core.int get version => $_getIZ(0); 180 | 181 | @$pb.TagNumber(1) 182 | set version($core.int v) { 183 | $_setUnsignedInt32(0, v); 184 | } 185 | 186 | @$pb.TagNumber(1) 187 | $core.bool hasVersion() => $_has(0); 188 | 189 | @$pb.TagNumber(1) 190 | void clearVersion() => clearField(1); 191 | 192 | @$pb.TagNumber(2) 193 | LogicalFingerprint get localFingerprint => $_getN(1); 194 | 195 | @$pb.TagNumber(2) 196 | set localFingerprint(LogicalFingerprint v) { 197 | setField(2, v); 198 | } 199 | 200 | @$pb.TagNumber(2) 201 | $core.bool hasLocalFingerprint() => $_has(1); 202 | 203 | @$pb.TagNumber(2) 204 | void clearLocalFingerprint() => clearField(2); 205 | 206 | @$pb.TagNumber(2) 207 | LogicalFingerprint ensureLocalFingerprint() => $_ensure(1); 208 | 209 | @$pb.TagNumber(3) 210 | LogicalFingerprint get remoteFingerprint => $_getN(2); 211 | 212 | @$pb.TagNumber(3) 213 | set remoteFingerprint(LogicalFingerprint v) { 214 | setField(3, v); 215 | } 216 | 217 | @$pb.TagNumber(3) 218 | $core.bool hasRemoteFingerprint() => $_has(2); 219 | 220 | @$pb.TagNumber(3) 221 | void clearRemoteFingerprint() => clearField(3); 222 | 223 | @$pb.TagNumber(3) 224 | LogicalFingerprint ensureRemoteFingerprint() => $_ensure(2); 225 | } 226 | -------------------------------------------------------------------------------- /lib/src/state/fingerprint_protocol.pbenum.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: FingerprintProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 6 | -------------------------------------------------------------------------------- /lib/src/state/fingerprint_protocol.pbjson.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as $convert; 2 | import 'dart:core' as $core; 3 | import 'dart:typed_data' as $typed_data; 4 | 5 | @$core.Deprecated('Use logicalFingerprintDescriptor instead') 6 | const logicalFingerprint$json = { 7 | '1': 'LogicalFingerprint', 8 | '2': [ 9 | {'1': 'content', '3': 1, '4': 1, '5': 12, '10': 'content'}, 10 | ], 11 | }; 12 | 13 | /// Descriptor for `LogicalFingerprint`. Decode as a `google.protobuf.DescriptorProto`. 14 | final $typed_data.Uint8List logicalFingerprintDescriptor = 15 | $convert.base64Decode( 16 | 'ChJMb2dpY2FsRmluZ2VycHJpbnQSGAoHY29udGVudBgBIAEoDFIHY29udGVudA=='); 17 | @$core.Deprecated('Use combinedFingerprintsDescriptor instead') 18 | const sombinedFingerprints$json = { 19 | '1': 'CombinedFingerprints', 20 | '2': [ 21 | {'1': 'version', '3': 1, '4': 1, '5': 13, '10': 'version'}, 22 | { 23 | '1': 'localFingerprint', 24 | '3': 2, 25 | '4': 1, 26 | '5': 11, 27 | '6': '.textsecure.LogicalFingerprint', 28 | '10': 'localFingerprint' 29 | }, 30 | { 31 | '1': 'remoteFingerprint', 32 | '3': 3, 33 | '4': 1, 34 | '5': 11, 35 | '6': '.textsecure.LogicalFingerprint', 36 | '10': 'remoteFingerprint' 37 | }, 38 | ], 39 | }; 40 | 41 | /// Descriptor for `CombinedFingerprints`. Decode as a `google.protobuf.DescriptorProto`. 42 | final $typed_data.Uint8List combinedFingerprintsDescriptor = $convert.base64Decode( 43 | 'ChRDb21iaW5lZEZpbmdlcnByaW50cxIYCgd2ZXJzaW9uGAEgASgNUgd2ZXJzaW9uEkoKEGxvY2FsRmluZ2VycHJpbnQYAiABKAsyHi50ZXh0c2VjdXJlLkxvZ2ljYWxGaW5nZXJwcmludFIQbG9jYWxGaW5nZXJwcmludBJMChFyZW1vdGVGaW5nZXJwcmludBgDIAEoCzIeLnRleHRzZWN1cmUuTG9naWNhbEZpbmdlcnByaW50UhFyZW1vdGVGaW5nZXJwcmludA=='); 44 | -------------------------------------------------------------------------------- /lib/src/state/fingerprint_protocol.pbserver.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: FingerprintProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 6 | 7 | export 'fingerprint_protocol.pb.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/state/identity_key_store.dart: -------------------------------------------------------------------------------- 1 | import '../identity_key.dart'; 2 | import '../identity_key_pair.dart'; 3 | import '../signal_protocol_address.dart'; 4 | 5 | enum Direction { sending, receiving } 6 | 7 | abstract class IdentityKeyStore { 8 | Future getIdentityKeyPair(); 9 | Future getLocalRegistrationId(); 10 | Future saveIdentity( 11 | SignalProtocolAddress address, 12 | IdentityKey? identityKey, 13 | ); 14 | Future isTrustedIdentity( 15 | SignalProtocolAddress address, 16 | IdentityKey? identityKey, 17 | Direction direction, 18 | ); 19 | Future getIdentity(SignalProtocolAddress address); 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/state/impl/in_memory_identity_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import '../../eq.dart'; 4 | import '../../identity_key.dart'; 5 | import '../../identity_key_pair.dart'; 6 | import '../../signal_protocol_address.dart'; 7 | import '../identity_key_store.dart'; 8 | 9 | class InMemoryIdentityKeyStore extends IdentityKeyStore { 10 | InMemoryIdentityKeyStore(this.identityKeyPair, this.localRegistrationId); 11 | 12 | final trustedKeys = HashMap(); 13 | 14 | final IdentityKeyPair identityKeyPair; 15 | final int localRegistrationId; 16 | 17 | @override 18 | Future getIdentity(SignalProtocolAddress address) async => 19 | trustedKeys[address]!; 20 | 21 | @override 22 | Future getIdentityKeyPair() async => identityKeyPair; 23 | 24 | @override 25 | Future getLocalRegistrationId() async => localRegistrationId; 26 | 27 | @override 28 | Future isTrustedIdentity(SignalProtocolAddress address, 29 | IdentityKey? identityKey, Direction? direction) async { 30 | final trusted = trustedKeys[address]; 31 | if (identityKey == null) { 32 | return false; 33 | } 34 | return trusted == null || eq(trusted.serialize(), identityKey.serialize()); 35 | } 36 | 37 | @override 38 | Future saveIdentity( 39 | SignalProtocolAddress address, IdentityKey? identityKey) async { 40 | final existing = trustedKeys[address]; 41 | if (identityKey == null) { 42 | return false; 43 | } 44 | if (identityKey != existing) { 45 | trustedKeys[address] = identityKey; 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/state/impl/in_memory_pre_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../../invalid_key_id_exception.dart'; 5 | import '../pre_key_record.dart'; 6 | import '../pre_key_store.dart'; 7 | 8 | class InMemoryPreKeyStore extends PreKeyStore { 9 | final store = HashMap(); 10 | 11 | @override 12 | Future containsPreKey(int preKeyId) async => 13 | store.containsKey(preKeyId); 14 | 15 | @override 16 | Future loadPreKey(int preKeyId) async { 17 | if (!store.containsKey(preKeyId)) { 18 | throw InvalidKeyIdException('No such prekeyrecord! - $preKeyId'); 19 | } 20 | return PreKeyRecord.fromBuffer(store[preKeyId]!); 21 | } 22 | 23 | @override 24 | Future removePreKey(int preKeyId) async { 25 | store.remove(preKeyId); 26 | } 27 | 28 | @override 29 | Future storePreKey(int preKeyId, PreKeyRecord record) async { 30 | store[preKeyId] = record.serialize(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/state/impl/in_memory_session_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../../signal_protocol_address.dart'; 5 | import '../session_record.dart'; 6 | import '../session_store.dart'; 7 | 8 | class InMemorySessionStore extends SessionStore { 9 | InMemorySessionStore(); 10 | 11 | HashMap sessions = 12 | HashMap(); 13 | 14 | @override 15 | Future containsSession(SignalProtocolAddress address) async => 16 | sessions.containsKey(address); 17 | 18 | @override 19 | Future deleteAllSessions(String name) async { 20 | for (final k in sessions.keys.toList()) { 21 | if (k.getName() == name) { 22 | sessions.remove(k); 23 | } 24 | } 25 | } 26 | 27 | @override 28 | Future deleteSession(SignalProtocolAddress address) async { 29 | sessions.remove(address); 30 | } 31 | 32 | @override 33 | Future> getSubDeviceSessions(String name) async { 34 | final deviceIds = []; 35 | 36 | for (final key in sessions.keys) { 37 | if (key.getName() == name && key.getDeviceId() != 1) { 38 | deviceIds.add(key.getDeviceId()); 39 | } 40 | } 41 | 42 | return deviceIds; 43 | } 44 | 45 | @override 46 | Future loadSession(SignalProtocolAddress address) async { 47 | try { 48 | if (await containsSession(address)) { 49 | return SessionRecord.fromSerialized(sessions[address]!); 50 | } else { 51 | return SessionRecord(); 52 | } 53 | } on Exception catch (e) { 54 | throw AssertionError(e); 55 | } 56 | } 57 | 58 | @override 59 | Future storeSession( 60 | SignalProtocolAddress address, SessionRecord record) async { 61 | sessions[address] = record.serialize(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/state/impl/in_memory_signal_protocol_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | import '../../identity_key.dart'; 4 | import '../../identity_key_pair.dart'; 5 | import '../../signal_protocol_address.dart'; 6 | import '../identity_key_store.dart'; 7 | import '../pre_key_record.dart'; 8 | import '../session_record.dart'; 9 | import '../signal_protocol_store.dart'; 10 | import '../signed_pre_key_record.dart'; 11 | import 'in_memory_identity_key_store.dart'; 12 | import 'in_memory_pre_key_store.dart'; 13 | import 'in_memory_session_store.dart'; 14 | import 'in_memory_signed_pre_key_store.dart'; 15 | 16 | class InMemorySignalProtocolStore implements SignalProtocolStore { 17 | InMemorySignalProtocolStore( 18 | IdentityKeyPair identityKeyPair, int registrationId) { 19 | _identityKeyStore = 20 | InMemoryIdentityKeyStore(identityKeyPair, registrationId); 21 | } 22 | 23 | final preKeyStore = InMemoryPreKeyStore(); 24 | final sessionStore = InMemorySessionStore(); 25 | final signedPreKeyStore = InMemorySignedPreKeyStore(); 26 | 27 | late IdentityKeyStore _identityKeyStore; 28 | 29 | @override 30 | Future getIdentityKeyPair() async => 31 | _identityKeyStore.getIdentityKeyPair(); 32 | 33 | @override 34 | Future getLocalRegistrationId() async => 35 | _identityKeyStore.getLocalRegistrationId(); 36 | 37 | @override 38 | Future saveIdentity( 39 | SignalProtocolAddress address, IdentityKey? identityKey) async => 40 | _identityKeyStore.saveIdentity(address, identityKey); 41 | 42 | @override 43 | Future isTrustedIdentity(SignalProtocolAddress address, 44 | IdentityKey? identityKey, Direction direction) async => 45 | _identityKeyStore.isTrustedIdentity(address, identityKey, direction); 46 | 47 | @override 48 | Future getIdentity(SignalProtocolAddress address) async => 49 | _identityKeyStore.getIdentity(address); 50 | 51 | @override 52 | Future loadPreKey(int preKeyId) async => 53 | preKeyStore.loadPreKey(preKeyId); 54 | 55 | @override 56 | Future storePreKey(int preKeyId, PreKeyRecord record) async { 57 | await preKeyStore.storePreKey(preKeyId, record); 58 | } 59 | 60 | @override 61 | Future containsPreKey(int preKeyId) async => 62 | preKeyStore.containsPreKey(preKeyId); 63 | 64 | @override 65 | Future removePreKey(int preKeyId) async { 66 | await preKeyStore.removePreKey(preKeyId); 67 | } 68 | 69 | @override 70 | Future loadSession(SignalProtocolAddress address) async => 71 | sessionStore.loadSession(address); 72 | 73 | @override 74 | Future> getSubDeviceSessions(String name) async => 75 | sessionStore.getSubDeviceSessions(name); 76 | 77 | @override 78 | Future storeSession( 79 | SignalProtocolAddress address, SessionRecord record) async { 80 | await sessionStore.storeSession(address, record); 81 | } 82 | 83 | @override 84 | Future containsSession(SignalProtocolAddress address) async => 85 | sessionStore.containsSession(address); 86 | 87 | @override 88 | Future deleteSession(SignalProtocolAddress address) async { 89 | await sessionStore.deleteSession(address); 90 | } 91 | 92 | @override 93 | Future deleteAllSessions(String name) async { 94 | await sessionStore.deleteAllSessions(name); 95 | } 96 | 97 | @override 98 | Future loadSignedPreKey(int signedPreKeyId) async => 99 | signedPreKeyStore.loadSignedPreKey(signedPreKeyId); 100 | 101 | @override 102 | Future> loadSignedPreKeys() async => 103 | signedPreKeyStore.loadSignedPreKeys(); 104 | 105 | @override 106 | Future storeSignedPreKey( 107 | int signedPreKeyId, SignedPreKeyRecord record) async { 108 | await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); 109 | } 110 | 111 | @override 112 | Future containsSignedPreKey(int signedPreKeyId) async => 113 | signedPreKeyStore.containsSignedPreKey(signedPreKeyId); 114 | 115 | @override 116 | Future removeSignedPreKey(int signedPreKeyId) async { 117 | await signedPreKeyStore.removeSignedPreKey(signedPreKeyId); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/state/impl/in_memory_signed_pre_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../../invalid_key_id_exception.dart'; 5 | import '../signed_pre_key_record.dart'; 6 | import '../signed_pre_key_store.dart'; 7 | 8 | class InMemorySignedPreKeyStore extends SignedPreKeyStore { 9 | final store = HashMap(); 10 | 11 | @override 12 | Future loadSignedPreKey(int signedPreKeyId) async { 13 | if (!store.containsKey(signedPreKeyId)) { 14 | throw InvalidKeyIdException( 15 | 'No such signedprekeyrecord! $signedPreKeyId'); 16 | } 17 | return SignedPreKeyRecord.fromSerialized(store[signedPreKeyId]!); 18 | } 19 | 20 | @override 21 | Future> loadSignedPreKeys() async { 22 | final results = []; 23 | for (final serialized in store.values) { 24 | results.add(SignedPreKeyRecord.fromSerialized(serialized)); 25 | } 26 | return results; 27 | } 28 | 29 | @override 30 | Future storeSignedPreKey( 31 | int signedPreKeyId, SignedPreKeyRecord record) async { 32 | store[signedPreKeyId] = record.serialize(); 33 | } 34 | 35 | @override 36 | Future containsSignedPreKey(int signedPreKeyId) async => 37 | store.containsKey(signedPreKeyId); 38 | 39 | @override 40 | Future removeSignedPreKey(int signedPreKeyId) async { 41 | store.remove(signedPreKeyId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/state/local_storage_protocol.pbenum.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: LocalStorageProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 6 | -------------------------------------------------------------------------------- /lib/src/state/local_storage_protocol.pbserver.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: LocalStorageProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 6 | 7 | export 'local_storage_protocol.pb.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/state/pre_key_bundle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '../ecc/ec_public_key.dart'; 4 | import '../identity_key.dart'; 5 | 6 | class PreKeyBundle { 7 | PreKeyBundle( 8 | this._registrationId, 9 | this._deviceId, 10 | this._preKeyId, 11 | this._preKeyPublic, 12 | this._signedPreKeyId, 13 | this._signedPreKeyPublic, 14 | this._signedPreKeySignature, 15 | this._identityKey); 16 | 17 | final int _registrationId; 18 | 19 | final int _deviceId; 20 | 21 | final int? _preKeyId; 22 | final ECPublicKey? _preKeyPublic; 23 | 24 | final int _signedPreKeyId; 25 | final ECPublicKey? _signedPreKeyPublic; 26 | final Uint8List? _signedPreKeySignature; 27 | 28 | final IdentityKey _identityKey; 29 | 30 | int getDeviceId() => _deviceId; 31 | 32 | int? getPreKeyId() => _preKeyId; 33 | 34 | ECPublicKey? getPreKey() => _preKeyPublic; 35 | 36 | int getSignedPreKeyId() => _signedPreKeyId; 37 | 38 | ECPublicKey? getSignedPreKey() => _signedPreKeyPublic; 39 | 40 | Uint8List? getSignedPreKeySignature() => _signedPreKeySignature; 41 | 42 | IdentityKey getIdentityKey() => _identityKey; 43 | 44 | int getRegistrationId() => _registrationId; 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/state/pre_key_record.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import '../ecc/curve.dart'; 3 | import '../ecc/ec_key_pair.dart'; 4 | import '../invalid_key_exception.dart'; 5 | import 'local_storage_protocol.pb.dart'; 6 | 7 | class PreKeyRecord { 8 | PreKeyRecord(int id, ECKeyPair keyPair) { 9 | _structure = PreKeyRecordStructure.create() 10 | ..id = id 11 | ..publicKey = keyPair.publicKey.serialize() 12 | ..privateKey = keyPair.privateKey.serialize() 13 | ..toBuilder(); 14 | } 15 | 16 | PreKeyRecord.fromBuffer(Uint8List serialized) { 17 | _structure = PreKeyRecordStructure.fromBuffer(serialized); 18 | } 19 | 20 | late PreKeyRecordStructure _structure; 21 | 22 | int get id => _structure.id; 23 | 24 | ECKeyPair getKeyPair() { 25 | try { 26 | final publicKey = 27 | Curve.decodePoint(Uint8List.fromList(_structure.publicKey), 0); 28 | final privateKey = 29 | Curve.decodePrivatePoint(Uint8List.fromList(_structure.privateKey)); 30 | return ECKeyPair(publicKey, privateKey); 31 | } on InvalidKeyException catch (e) { 32 | throw AssertionError(e); 33 | } 34 | } 35 | 36 | Uint8List serialize() => _structure.writeToBuffer(); 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/state/pre_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'pre_key_record.dart'; 2 | 3 | abstract mixin class PreKeyStore { 4 | Future loadPreKey( 5 | int preKeyId); // throws InvalidKeyIdException; 6 | 7 | Future storePreKey(int preKeyId, PreKeyRecord record); 8 | 9 | Future containsPreKey(int preKeyId); 10 | 11 | Future removePreKey(int preKeyId); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/state/session_record.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../eq.dart'; 5 | import 'local_storage_protocol.pb.dart'; 6 | import 'session_state.dart'; 7 | 8 | class SessionRecord { 9 | SessionRecord() { 10 | _fresh = true; 11 | } 12 | 13 | SessionRecord.fromSessionState(SessionState sessionState) { 14 | _sessionState = sessionState; 15 | _fresh = false; 16 | } 17 | 18 | SessionRecord.fromSerialized(Uint8List serialized) { 19 | final record = RecordStructure.fromBuffer(serialized); 20 | _sessionState = SessionState.fromStructure(record.currentSession); 21 | _fresh = false; 22 | 23 | for (final previousStructure in record.previousSessions) { 24 | _previousStates.add(SessionState.fromStructure(previousStructure)); 25 | } 26 | } 27 | 28 | static const int archivedStatesMaxLength = 40; 29 | var _sessionState = SessionState(); 30 | final _previousStates = LinkedList(); 31 | bool _fresh = false; 32 | 33 | bool hasSessionState(int version, Uint8List aliceBaseKey) { 34 | if (_sessionState.getSessionVersion() == version && 35 | eq(aliceBaseKey, _sessionState.aliceBaseKey)) { 36 | return true; 37 | } 38 | 39 | for (final state in _previousStates) { 40 | if (state.getSessionVersion() == version && 41 | eq(aliceBaseKey, _sessionState.aliceBaseKey)) { 42 | return true; 43 | } 44 | } 45 | return false; 46 | } 47 | 48 | SessionState get sessionState => _sessionState; 49 | 50 | LinkedList get previousSessionStates => _previousStates; 51 | 52 | void removePreviousSessionStates() { 53 | _previousStates.clear(); 54 | } 55 | 56 | bool isFresh() => _fresh; 57 | 58 | void archiveCurrentState() { 59 | promoteState(SessionState()); 60 | } 61 | 62 | void promoteState(SessionState promotedState) { 63 | _previousStates.addFirst(_sessionState); 64 | _sessionState = promotedState; 65 | 66 | if (_previousStates.length > archivedStatesMaxLength) { 67 | _previousStates.remove(_previousStates.last); 68 | } 69 | } 70 | 71 | set state(SessionState sessionState) { 72 | _sessionState = sessionState; 73 | } 74 | 75 | Uint8List serialize() { 76 | final previousStructures = []; 77 | for (final previousState in _previousStates) { 78 | previousStructures.add(previousState.structure); 79 | } 80 | final record = RecordStructure.create() 81 | ..currentSession = _sessionState.structure 82 | ..previousSessions.addAll(previousStructures); 83 | 84 | return record.toBuilder().writeToBuffer(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/state/session_store.dart: -------------------------------------------------------------------------------- 1 | import '../signal_protocol_address.dart'; 2 | 3 | import 'session_record.dart'; 4 | 5 | abstract mixin class SessionStore { 6 | Future loadSession(SignalProtocolAddress address); 7 | 8 | Future> getSubDeviceSessions(String name); 9 | 10 | Future storeSession( 11 | SignalProtocolAddress address, SessionRecord record); 12 | 13 | Future containsSession(SignalProtocolAddress address); 14 | 15 | Future deleteSession(SignalProtocolAddress address); 16 | 17 | Future deleteAllSessions(String name); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/state/signal_protocol_store.dart: -------------------------------------------------------------------------------- 1 | import 'identity_key_store.dart'; 2 | import 'pre_key_store.dart'; 3 | import 'session_store.dart'; 4 | import 'signed_pre_key_store.dart'; 5 | 6 | abstract class SignalProtocolStore extends IdentityKeyStore 7 | with PreKeyStore, SessionStore, SignedPreKeyStore {} 8 | -------------------------------------------------------------------------------- /lib/src/state/signed_pre_key_record.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:fixnum/fixnum.dart'; 4 | import '../ecc/curve.dart'; 5 | import '../ecc/ec_key_pair.dart'; 6 | import '../invalid_key_exception.dart'; 7 | import 'local_storage_protocol.pb.dart'; 8 | 9 | class SignedPreKeyRecord { 10 | SignedPreKeyRecord( 11 | int id, Int64 timestamp, ECKeyPair keyPair, Uint8List signature) { 12 | _structure = SignedPreKeyRecordStructure.create() 13 | ..id = id 14 | ..timestamp = timestamp 15 | ..publicKey = keyPair.publicKey.serialize() 16 | ..privateKey = keyPair.privateKey.serialize() 17 | ..signature = signature; 18 | } 19 | 20 | SignedPreKeyRecord.fromSerialized(Uint8List serialized) { 21 | _structure = SignedPreKeyRecordStructure.fromBuffer(serialized); 22 | } 23 | 24 | late SignedPreKeyRecordStructure _structure; 25 | 26 | int get id => _structure.id; 27 | 28 | Int64 get timestamp => _structure.timestamp; 29 | 30 | ECKeyPair getKeyPair() { 31 | try { 32 | final publicKey = Curve.decodePointList(_structure.publicKey, 0); 33 | final privateKey = 34 | Curve.decodePrivatePoint(Uint8List.fromList(_structure.privateKey)); 35 | 36 | return ECKeyPair(publicKey, privateKey); 37 | } on InvalidKeyException catch (e) { 38 | throw AssertionError(e); 39 | } 40 | } 41 | 42 | Uint8List get signature => Uint8List.fromList(_structure.signature); 43 | 44 | Uint8List serialize() => _structure.writeToBuffer(); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/state/signed_pre_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'signed_pre_key_record.dart'; 2 | 3 | abstract mixin class SignedPreKeyStore { 4 | Future loadSignedPreKey( 5 | int signedPreKeyId, 6 | ); //throws InvalidKeyIdException; 7 | 8 | Future> loadSignedPreKeys(); 9 | 10 | Future storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record); 11 | 12 | Future containsSignedPreKey(int signedPreKeyId); 13 | 14 | Future removeSignedPreKey(int signedPreKeyId); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/state/whisper_text_protocol.pbenum.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: WhisperTextProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 6 | -------------------------------------------------------------------------------- /lib/src/state/whisper_text_protocol.pbjson.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as $convert; 2 | import 'dart:core' as $core; 3 | import 'dart:typed_data' as $typed_data; 4 | 5 | @$core.Deprecated('Use signalMessageDescriptor instead') 6 | const signalMessage$json = { 7 | '1': 'SignalMessage', 8 | '2': [ 9 | {'1': 'ratchetKey', '3': 1, '4': 1, '5': 12, '10': 'ratchetKey'}, 10 | {'1': 'counter', '3': 2, '4': 1, '5': 13, '10': 'counter'}, 11 | {'1': 'previousCounter', '3': 3, '4': 1, '5': 13, '10': 'previousCounter'}, 12 | {'1': 'ciphertext', '3': 4, '4': 1, '5': 12, '10': 'ciphertext'}, 13 | ], 14 | }; 15 | 16 | /// Descriptor for `SignalMessage`. Decode as a `google.protobuf.DescriptorProto`. 17 | final $typed_data.Uint8List signalMessageDescriptor = $convert.base64Decode( 18 | 'Cg1TaWduYWxNZXNzYWdlEh4KCnJhdGNoZXRLZXkYASABKAxSCnJhdGNoZXRLZXkSGAoHY291bnRlchgCIAEoDVIHY291bnRlchIoCg9wcmV2aW91c0NvdW50ZXIYAyABKA1SD3ByZXZpb3VzQ291bnRlchIeCgpjaXBoZXJ0ZXh0GAQgASgMUgpjaXBoZXJ0ZXh0'); 19 | @$core.Deprecated('Use preKeySignalMessageDescriptor instead') 20 | const preKeySignalMessage$json = { 21 | '1': 'PreKeySignalMessage', 22 | '2': [ 23 | {'1': 'registrationId', '3': 5, '4': 1, '5': 13, '10': 'registrationId'}, 24 | {'1': 'preKeyId', '3': 1, '4': 1, '5': 13, '10': 'preKeyId'}, 25 | {'1': 'signedPreKeyId', '3': 6, '4': 1, '5': 13, '10': 'signedPreKeyId'}, 26 | {'1': 'baseKey', '3': 2, '4': 1, '5': 12, '10': 'baseKey'}, 27 | {'1': 'identityKey', '3': 3, '4': 1, '5': 12, '10': 'identityKey'}, 28 | {'1': 'message', '3': 4, '4': 1, '5': 12, '10': 'message'}, 29 | ], 30 | }; 31 | 32 | /// Descriptor for `PreKeySignalMessage`. Decode as a `google.protobuf.DescriptorProto`. 33 | final $typed_data.Uint8List preKeySignalMessageDescriptor = $convert.base64Decode( 34 | 'ChNQcmVLZXlTaWduYWxNZXNzYWdlEiYKDnJlZ2lzdHJhdGlvbklkGAUgASgNUg5yZWdpc3RyYXRpb25JZBIaCghwcmVLZXlJZBgBIAEoDVIIcHJlS2V5SWQSJgoOc2lnbmVkUHJlS2V5SWQYBiABKA1SDnNpZ25lZFByZUtleUlkEhgKB2Jhc2VLZXkYAiABKAxSB2Jhc2VLZXkSIAoLaWRlbnRpdHlLZXkYAyABKAxSC2lkZW50aXR5S2V5EhgKB21lc3NhZ2UYBCABKAxSB21lc3NhZ2U='); 35 | @$core.Deprecated('Use keyExchangeMessageDescriptor instead') 36 | const keyExchangeMessage$json = { 37 | '1': 'KeyExchangeMessage', 38 | '2': [ 39 | {'1': 'id', '3': 1, '4': 1, '5': 13, '10': 'id'}, 40 | {'1': 'baseKey', '3': 2, '4': 1, '5': 12, '10': 'baseKey'}, 41 | {'1': 'ratchetKey', '3': 3, '4': 1, '5': 12, '10': 'ratchetKey'}, 42 | {'1': 'identityKey', '3': 4, '4': 1, '5': 12, '10': 'identityKey'}, 43 | { 44 | '1': 'baseKeySignature', 45 | '3': 5, 46 | '4': 1, 47 | '5': 12, 48 | '10': 'baseKeySignature' 49 | }, 50 | ], 51 | }; 52 | 53 | /// Descriptor for `KeyExchangeMessage`. Decode as a `google.protobuf.DescriptorProto`. 54 | final $typed_data.Uint8List keyExchangeMessageDescriptor = $convert.base64Decode( 55 | 'ChJLZXlFeGNoYW5nZU1lc3NhZ2USDgoCaWQYASABKA1SAmlkEhgKB2Jhc2VLZXkYAiABKAxSB2Jhc2VLZXkSHgoKcmF0Y2hldEtleRgDIAEoDFIKcmF0Y2hldEtleRIgCgtpZGVudGl0eUtleRgEIAEoDFILaWRlbnRpdHlLZXkSKgoQYmFzZUtleVNpZ25hdHVyZRgFIAEoDFIQYmFzZUtleVNpZ25hdHVyZQ=='); 56 | @$core.Deprecated('Use senderKeyMessageDescriptor instead') 57 | const senderKeyMessage$json = { 58 | '1': 'SenderKeyMessage', 59 | '2': [ 60 | {'1': 'id', '3': 1, '4': 1, '5': 13, '10': 'id'}, 61 | {'1': 'iteration', '3': 2, '4': 1, '5': 13, '10': 'iteration'}, 62 | {'1': 'ciphertext', '3': 3, '4': 1, '5': 12, '10': 'ciphertext'}, 63 | ], 64 | }; 65 | 66 | /// Descriptor for `SenderKeyMessage`. Decode as a `google.protobuf.DescriptorProto`. 67 | final $typed_data.Uint8List senderKeyMessageDescriptor = $convert.base64Decode( 68 | 'ChBTZW5kZXJLZXlNZXNzYWdlEg4KAmlkGAEgASgNUgJpZBIcCglpdGVyYXRpb24YAiABKA1SCWl0ZXJhdGlvbhIeCgpjaXBoZXJ0ZXh0GAMgASgMUgpjaXBoZXJ0ZXh0'); 69 | @$core.Deprecated('Use senderKeyDistributionMessageDescriptor instead') 70 | const senderKeyDistributionMessage$json = { 71 | '1': 'SenderKeyDistributionMessage', 72 | '2': [ 73 | {'1': 'id', '3': 1, '4': 1, '5': 13, '10': 'id'}, 74 | {'1': 'iteration', '3': 2, '4': 1, '5': 13, '10': 'iteration'}, 75 | {'1': 'chainKey', '3': 3, '4': 1, '5': 12, '10': 'chainKey'}, 76 | {'1': 'signingKey', '3': 4, '4': 1, '5': 12, '10': 'signingKey'}, 77 | ], 78 | }; 79 | 80 | /// Descriptor for `SenderKeyDistributionMessage`. Decode as a `google.protobuf.DescriptorProto`. 81 | final $typed_data.Uint8List senderKeyDistributionMessageDescriptor = 82 | $convert.base64Decode( 83 | 'ChxTZW5kZXJLZXlEaXN0cmlidXRpb25NZXNzYWdlEg4KAmlkGAEgASgNUgJpZBIcCglpdGVyYXRpb24YAiABKA1SCWl0ZXJhdGlvbhIaCghjaGFpbktleRgDIAEoDFIIY2hhaW5LZXkSHgoKc2lnbmluZ0tleRgEIAEoDFIKc2lnbmluZ0tleQ=='); 84 | @$core.Deprecated('Use deviceConsistencyCodeMessageDescriptor instead') 85 | const deviceConsistencyCodeMessage$json = { 86 | '1': 'DeviceConsistencyCodeMessage', 87 | '2': [ 88 | {'1': 'generation', '3': 1, '4': 1, '5': 13, '10': 'generation'}, 89 | {'1': 'signature', '3': 2, '4': 1, '5': 12, '10': 'signature'}, 90 | ], 91 | }; 92 | 93 | /// Descriptor for `DeviceConsistencyCodeMessage`. Decode as a `google.protobuf.DescriptorProto`. 94 | final $typed_data.Uint8List deviceConsistencyCodeMessageDescriptor = 95 | $convert.base64Decode( 96 | 'ChxEZXZpY2VDb25zaXN0ZW5jeUNvZGVNZXNzYWdlEh4KCmdlbmVyYXRpb24YASABKA1SCmdlbmVyYXRpb24SHAoJc2lnbmF0dXJlGAIgASgMUglzaWduYXR1cmU='); 97 | -------------------------------------------------------------------------------- /lib/src/state/whisper_text_protocol.pbserver.dart: -------------------------------------------------------------------------------- 1 | // Generated code. Do not modify. 2 | // source: WhisperTextProtocol.proto 3 | // 4 | // @dart = 2.12 5 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 6 | 7 | export 'whisper_text_protocol.pb.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/untrusted_identity_exception.dart: -------------------------------------------------------------------------------- 1 | import 'identity_key.dart'; 2 | 3 | class UntrustedIdentityException implements Exception { 4 | UntrustedIdentityException(this.name, this.key); 5 | 6 | final String name; 7 | final IdentityKey? key; 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/util/byte_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:adaptive_number/adaptive_number.dart'; 4 | import 'package:convert/convert.dart'; 5 | 6 | class ByteUtil { 7 | static Uint8List combine(List elements) { 8 | final results = []; 9 | elements.forEach(results.addAll); 10 | return Uint8List.fromList(results); 11 | } 12 | 13 | static Uint8List shortToByteArray(int value) { 14 | final bytes = Uint8List(2); 15 | bytes[0] = value >> 8; 16 | bytes[1] = value; 17 | return bytes; 18 | } 19 | 20 | static Uint8List intToByteArray(int value) { 21 | final bytes = Uint8List(4); 22 | bytes[0] = value >> 24; 23 | bytes[1] = value >> 16; 24 | bytes[2] = value >> 8; 25 | bytes[3] = value; 26 | return bytes; 27 | } 28 | 29 | static Uint8List trim(Uint8List input, int length) => 30 | input.sublist(0, length); 31 | 32 | static List splitTwo( 33 | Uint8List input, int firstLength, int secondLength) { 34 | final first = input.sublist(0, firstLength); 35 | final second = input.sublist(firstLength, firstLength + secondLength); 36 | return [first, second]; 37 | } 38 | 39 | static List split( 40 | Uint8List input, int firstLength, int secondLength, int thirdLength) { 41 | if (firstLength < 0 || 42 | secondLength < 0 || 43 | thirdLength < 0 || 44 | input.length < firstLength + secondLength + thirdLength) { 45 | throw Exception('Input too small: ${hex.encode(input)}'); 46 | } 47 | final first = input.sublist(0, firstLength); 48 | final second = input.sublist(firstLength, firstLength + secondLength); 49 | final third = input.sublist( 50 | firstLength + secondLength, firstLength + secondLength + thirdLength); 51 | return [first, second, third]; 52 | } 53 | 54 | static int intsToByteHighAndLow(int highValue, int lowValue) => 55 | ((highValue << 4) | lowValue) & 0xFF; 56 | 57 | static int highBitsToInt(int value) => (value & 0xFF) >> 4; 58 | 59 | static int lowBitsToInt(int value) => value & 0xF; 60 | 61 | static int highBitsToMedium(int value) => value >> 12; 62 | 63 | static int lowBitsToMedium(int value) => value & 0xFFF; 64 | 65 | static int byteArray5ToLong(Uint8List bytes, int offset) { 66 | final v1 = (Number(bytes[offset]) & Number(0xff)) << 32; 67 | final v2 = (Number(bytes[offset + 1]) & Number(0xff)) << 24; 68 | final v3 = (Number(bytes[offset + 2]) & Number(0xff)) << 16; 69 | final v4 = (Number(bytes[offset + 3]) & Number(0xff)) << 8; 70 | final v5 = Number(bytes[offset + 4]) & Number(0xff); 71 | return (v1 | v2 | v3 | v4 | v5).intValue; 72 | } 73 | 74 | static int compare(Uint8List left, Uint8List right) { 75 | // ignore: parameter_assignments, avoid_multiple_declarations_per_line 76 | for (var i = 0, j = 0; i < left.length && j < right.length; i++, j++) { 77 | final a = left[i] & 0xff; 78 | final b = right[j] & 0xff; 79 | 80 | if (a != b) { 81 | return a - b; 82 | } 83 | } 84 | 85 | return left.length - right.length; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/util/identity_key_comparator.dart: -------------------------------------------------------------------------------- 1 | import '../identity_key.dart'; 2 | import 'byte_util.dart'; 3 | 4 | int identityKeyComparator(IdentityKey a, IdentityKey b) => 5 | ByteUtil.compare(a.publicKey.serialize(), b.publicKey.serialize()); 6 | -------------------------------------------------------------------------------- /lib/src/util/key_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:fixnum/fixnum.dart'; 5 | 6 | import '../ecc/curve.dart'; 7 | import '../ecc/ec_key_pair.dart'; 8 | import '../identity_key.dart'; 9 | import '../identity_key_pair.dart'; 10 | import '../state/pre_key_record.dart'; 11 | import '../state/signed_pre_key_record.dart'; 12 | import 'medium.dart'; 13 | 14 | IdentityKeyPair generateIdentityKeyPair() { 15 | final keyPair = Curve.generateKeyPair(); 16 | final publicKey = IdentityKey(keyPair.publicKey); 17 | return IdentityKeyPair(publicKey, keyPair.privateKey); 18 | } 19 | 20 | IdentityKeyPair generateIdentityKeyPairFromPrivate(List private) { 21 | final keyPair = Curve.generateKeyPairFromPrivate(private); 22 | final publicKey = IdentityKey(keyPair.publicKey); 23 | return IdentityKeyPair(publicKey, keyPair.privateKey); 24 | } 25 | 26 | int integerMax = 0x7fffffff; 27 | 28 | // ignore: avoid_positional_boolean_parameters 29 | int generateRegistrationId(bool extendedRange) { 30 | if (extendedRange) { 31 | return _random.nextInt(integerMax - 1) + 1; 32 | } else { 33 | return _random.nextInt(16380) + 1; 34 | } 35 | } 36 | 37 | List generatePreKeys(int start, int count) { 38 | final results = []; 39 | // ignore: parameter_assignments 40 | start--; 41 | for (var i = 0; i < count; i++) { 42 | results.add(PreKeyRecord( 43 | ((start + i).remainder(maxValue - 1)) + 1, Curve.generateKeyPair())); 44 | } 45 | return results; 46 | } 47 | 48 | SignedPreKeyRecord generateSignedPreKey( 49 | IdentityKeyPair identityKeyPair, int signedPreKeyId) { 50 | final keyPair = Curve.generateKeyPair(); 51 | final signature = Curve.calculateSignature( 52 | identityKeyPair.getPrivateKey(), keyPair.publicKey.serialize()); 53 | 54 | return SignedPreKeyRecord(signedPreKeyId, 55 | Int64(DateTime.now().millisecondsSinceEpoch), keyPair, signature); 56 | } 57 | 58 | ECKeyPair generateSenderSigningKey() => Curve.generateKeyPair(); 59 | 60 | Uint8List generateSenderKey() => generateRandomBytes(); 61 | 62 | int generateSenderKeyId() => _random.nextInt(integerMax); 63 | 64 | final Random _random = Random.secure(); 65 | 66 | Uint8List generateRandomBytes([int length = 32]) { 67 | final values = List.generate(length, (i) => _random.nextInt(256)); 68 | return Uint8List.fromList(values); 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/util/log.dart: -------------------------------------------------------------------------------- 1 | bool enabled = false; 2 | 3 | void log(Object? object) { 4 | if (enabled) { 5 | // ignore: avoid_print 6 | print(object); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/util/medium.dart: -------------------------------------------------------------------------------- 1 | const int maxValue = 0xFFFFFF; 2 | -------------------------------------------------------------------------------- /protobuf/FingerprintProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.fingerprint"; 6 | option java_outer_classname = "FingerprintProtos"; 7 | 8 | message LogicalFingerprint { 9 | optional bytes content = 1; 10 | // optional bytes identifier = 2; 11 | } 12 | 13 | message CombinedFingerprints { 14 | optional uint32 version = 1; 15 | optional LogicalFingerprint localFingerprint = 2; 16 | optional LogicalFingerprint remoteFingerprint = 3; 17 | } -------------------------------------------------------------------------------- /protobuf/LocalStorageProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.state"; 6 | option java_outer_classname = "StorageProtos"; 7 | 8 | message SessionStructure { 9 | message Chain { 10 | optional bytes senderRatchetKey = 1; 11 | optional bytes senderRatchetKeyPrivate = 2; 12 | 13 | message ChainKey { 14 | optional uint32 index = 1; 15 | optional bytes key = 2; 16 | } 17 | 18 | optional ChainKey chainKey = 3; 19 | 20 | message MessageKey { 21 | optional uint32 index = 1; 22 | optional bytes cipherKey = 2; 23 | optional bytes macKey = 3; 24 | optional bytes iv = 4; 25 | } 26 | 27 | repeated MessageKey messageKeys = 4; 28 | } 29 | 30 | message PendingKeyExchange { 31 | optional uint32 sequence = 1; 32 | optional bytes localBaseKey = 2; 33 | optional bytes localBaseKeyPrivate = 3; 34 | optional bytes localRatchetKey = 4; 35 | optional bytes localRatchetKeyPrivate = 5; 36 | optional bytes localIdentityKey = 7; 37 | optional bytes localIdentityKeyPrivate = 8; 38 | } 39 | 40 | message PendingPreKey { 41 | optional uint32 preKeyId = 1; 42 | optional int32 signedPreKeyId = 3; 43 | optional bytes baseKey = 2; 44 | } 45 | 46 | optional uint32 sessionVersion = 1; 47 | optional bytes localIdentityPublic = 2; 48 | optional bytes remoteIdentityPublic = 3; 49 | 50 | optional bytes rootKey = 4; 51 | optional uint32 previousCounter = 5; 52 | 53 | optional Chain senderChain = 6; 54 | repeated Chain receiverChains = 7; 55 | 56 | optional PendingKeyExchange pendingKeyExchange = 8; 57 | optional PendingPreKey pendingPreKey = 9; 58 | 59 | optional uint32 remoteRegistrationId = 10; 60 | optional uint32 localRegistrationId = 11; 61 | 62 | optional bool needsRefresh = 12; 63 | optional bytes aliceBaseKey = 13; 64 | } 65 | 66 | message RecordStructure { 67 | optional SessionStructure currentSession = 1; 68 | repeated SessionStructure previousSessions = 2; 69 | } 70 | 71 | message PreKeyRecordStructure { 72 | optional uint32 id = 1; 73 | optional bytes publicKey = 2; 74 | optional bytes privateKey = 3; 75 | } 76 | 77 | message SignedPreKeyRecordStructure { 78 | optional uint32 id = 1; 79 | optional bytes publicKey = 2; 80 | optional bytes privateKey = 3; 81 | optional bytes signature = 4; 82 | optional fixed64 timestamp = 5; 83 | } 84 | 85 | message IdentityKeyPairStructure { 86 | optional bytes publicKey = 1; 87 | optional bytes privateKey = 2; 88 | } 89 | 90 | message SenderKeyStateStructure { 91 | message SenderChainKey { 92 | optional uint32 iteration = 1; 93 | optional bytes seed = 2; 94 | } 95 | 96 | message SenderMessageKey { 97 | optional uint32 iteration = 1; 98 | optional bytes seed = 2; 99 | } 100 | 101 | message SenderSigningKey { 102 | optional bytes public = 1; 103 | optional bytes private = 2; 104 | } 105 | 106 | optional uint32 senderKeyId = 1; 107 | optional SenderChainKey senderChainKey = 2; 108 | optional SenderSigningKey senderSigningKey = 3; 109 | repeated SenderMessageKey senderMessageKeys = 4; 110 | } 111 | 112 | message SenderKeyRecordStructure { 113 | repeated SenderKeyStateStructure senderKeyStates = 1; 114 | } -------------------------------------------------------------------------------- /protobuf/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package textsecure; 4 | 5 | option java_package = "org.whispersystems.libsignal.protocol"; 6 | option java_outer_classname = "SignalProtos"; 7 | 8 | message SignalMessage { 9 | optional bytes ratchetKey = 1; 10 | optional uint32 counter = 2; 11 | optional uint32 previousCounter = 3; 12 | optional bytes ciphertext = 4; 13 | } 14 | 15 | message PreKeySignalMessage { 16 | optional uint32 registrationId = 5; 17 | optional uint32 preKeyId = 1; 18 | optional uint32 signedPreKeyId = 6; 19 | optional bytes baseKey = 2; 20 | optional bytes identityKey = 3; 21 | optional bytes message = 4; // SignalMessage 22 | } 23 | 24 | message KeyExchangeMessage { 25 | optional uint32 id = 1; 26 | optional bytes baseKey = 2; 27 | optional bytes ratchetKey = 3; 28 | optional bytes identityKey = 4; 29 | optional bytes baseKeySignature = 5; 30 | } 31 | 32 | message SenderKeyMessage { 33 | optional uint32 id = 1; 34 | optional uint32 iteration = 2; 35 | optional bytes ciphertext = 3; 36 | } 37 | 38 | message SenderKeyDistributionMessage { 39 | optional uint32 id = 1; 40 | optional uint32 iteration = 2; 41 | optional bytes chainKey = 3; 42 | optional bytes signingKey = 4; 43 | } 44 | 45 | message DeviceConsistencyCodeMessage { 46 | optional uint32 generation = 1; 47 | optional bytes signature = 2; 48 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: libsignal_protocol_dart 2 | description: Signal Protocol libray for Dart native and Flutter, pure Dart implementation of the the Signal Protocol 3 | version: 0.7.4 4 | repository: https://github.com/MixinNetwork/libsignal_protocol_dart 5 | topics: 6 | - crypto 7 | 8 | environment: 9 | sdk: '>=3.4.0 <4.0.0' 10 | 11 | 12 | dependencies: 13 | adaptive_number: ^1.0.0 14 | collection: ^1.19.0 15 | convert: ^3.1.2 16 | crypto: ^3.0.6 17 | ed25519_edwards: ^0.3.1 18 | fixnum: ^1.1.1 19 | meta: ^1.16.0 20 | optional: ^6.1.0+1 21 | pointycastle: ^4.0.0 22 | protobuf: ^4.0.0 23 | x25519: ^0.1.1 24 | 25 | 26 | dev_dependencies: 27 | test: ^1.25.8 28 | very_good_analysis: ^8.0.0 29 | -------------------------------------------------------------------------------- /test/devices/device_consistency_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/src/devices/device_consistency_code_generator.dart'; 2 | import 'package:libsignal_protocol_dart/src/devices/device_consistency_commitment.dart'; 3 | import 'package:libsignal_protocol_dart/src/devices/device_consistency_signature.dart'; 4 | import 'package:libsignal_protocol_dart/src/identity_key.dart'; 5 | import 'package:libsignal_protocol_dart/src/protocol/device_consistency_message.dart'; 6 | import 'package:libsignal_protocol_dart/src/util/key_helper.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | String generateCode(DeviceConsistencyCommitment commitment, 11 | List messages) { 12 | final signatures = []; 13 | for (final message in messages) { 14 | signatures.add(message.signature); 15 | } 16 | return DeviceConsistencyCodeGenerator.generateFor(commitment, signatures); 17 | } 18 | 19 | test('testDeviceConsistency', () { 20 | final deviceOne = generateIdentityKeyPair(); 21 | final deviceTwo = generateIdentityKeyPair(); 22 | final deviceThree = generateIdentityKeyPair(); 23 | 24 | final keyList = [ 25 | deviceOne.getPublicKey(), 26 | deviceTwo.getPublicKey(), 27 | deviceThree.getPublicKey() 28 | ]..shuffle(); 29 | final deviceOneCommitment = DeviceConsistencyCommitment(1, keyList); 30 | 31 | keyList.shuffle(); 32 | final deviceTwoCommitment = DeviceConsistencyCommitment(1, keyList); 33 | 34 | keyList.shuffle(); 35 | final deviceThreeCommitment = DeviceConsistencyCommitment(1, keyList); 36 | 37 | expect(deviceOneCommitment.serialized, deviceTwoCommitment.serialized); 38 | expect(deviceTwoCommitment.serialized, deviceThreeCommitment.serialized); 39 | 40 | final deviceOneMessage = 41 | DeviceConsistencyMessage(deviceOneCommitment, deviceOne); 42 | final deviceTwoMessage = 43 | DeviceConsistencyMessage(deviceOneCommitment, deviceTwo); 44 | final deviceThreeMessage = 45 | DeviceConsistencyMessage(deviceOneCommitment, deviceThree); 46 | 47 | final receivedDeviceOneMessage = DeviceConsistencyMessage.fromSerialized( 48 | deviceOneCommitment, 49 | deviceOneMessage.serialized, 50 | deviceOne.getPublicKey()); 51 | final receivedDeviceTwoMessage = DeviceConsistencyMessage.fromSerialized( 52 | deviceOneCommitment, 53 | deviceTwoMessage.serialized, 54 | deviceTwo.getPublicKey()); 55 | final receivedDeviceThreeMessage = DeviceConsistencyMessage.fromSerialized( 56 | deviceOneCommitment, 57 | deviceThreeMessage.serialized, 58 | deviceThree.getPublicKey()); 59 | 60 | expect(deviceOneMessage.signature.vrfOutput, 61 | receivedDeviceOneMessage.signature.vrfOutput); 62 | expect(deviceTwoMessage.signature.vrfOutput, 63 | receivedDeviceTwoMessage.signature.vrfOutput); 64 | expect(deviceThreeMessage.signature.vrfOutput, 65 | receivedDeviceThreeMessage.signature.vrfOutput); 66 | 67 | final codeOne = generateCode(deviceOneCommitment, [ 68 | deviceOneMessage, 69 | receivedDeviceTwoMessage, 70 | receivedDeviceThreeMessage 71 | ]); 72 | final codeTwo = generateCode(deviceTwoCommitment, [ 73 | deviceTwoMessage, 74 | receivedDeviceThreeMessage, 75 | receivedDeviceOneMessage 76 | ]); 77 | final codeThree = generateCode(deviceThreeCommitment, [ 78 | deviceThreeMessage, 79 | receivedDeviceTwoMessage, 80 | receivedDeviceOneMessage 81 | ]); 82 | 83 | expect(codeOne, codeTwo); 84 | expect(codeTwo, codeThree); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /test/kdf/hkdf_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:libsignal_protocol_dart/src/kdf/hkdf.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | test('testVectorV3', () { 8 | final ikm = Uint8List.fromList([ 9 | 0x0b, 10 | 0x0b, 11 | 0x0b, 12 | 0x0b, 13 | 0x0b, 14 | 0x0b, 15 | 0x0b, 16 | 0x0b, 17 | 0x0b, 18 | 0x0b, 19 | 0x0b, 20 | 0x0b, 21 | 0x0b, 22 | 0x0b, 23 | 0x0b, 24 | 0x0b, 25 | 0x0b, 26 | 0x0b, 27 | 0x0b, 28 | 0x0b, 29 | 0x0b, 30 | 0x0b 31 | ]); 32 | 33 | final salt = Uint8List.fromList([ 34 | 0x00, 35 | 0x01, 36 | 0x02, 37 | 0x03, 38 | 0x04, 39 | 0x05, 40 | 0x06, 41 | 0x07, 42 | 0x08, 43 | 0x09, 44 | 0x0a, 45 | 0x0b, 46 | 0x0c 47 | ]); 48 | 49 | final info = Uint8List.fromList( 50 | [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]); 51 | 52 | final okm = Uint8List.fromList([ 53 | 0x3c, 54 | 0xb2, 55 | 0x5f, 56 | 0x25, 57 | 0xfa, 58 | 0xac, 59 | 0xd5, 60 | 0x7a, 61 | 0x90, 62 | 0x43, 63 | 0x4f, 64 | 0x64, 65 | 0xd0, 66 | 0x36, 67 | 0x2f, 68 | 0x2a, 69 | 0x2d, 70 | 0x2d, 71 | 0x0a, 72 | 0x90, 73 | 0xcf, 74 | 0x1a, 75 | 0x5a, 76 | 0x4c, 77 | 0x5d, 78 | 0xb0, 79 | 0x2d, 80 | 0x56, 81 | 0xec, 82 | 0xc4, 83 | 0xc5, 84 | 0xbf, 85 | 0x34, 86 | 0x00, 87 | 0x72, 88 | 0x08, 89 | 0xd5, 90 | 0xb8, 91 | 0x87, 92 | 0x18, 93 | 0x58, 94 | 0x65 95 | ]); 96 | 97 | final actualOutput = HKDF.createFor(3).deriveSecrets4(ikm, salt, info, 42); 98 | expect(okm, actualOutput); 99 | }); 100 | 101 | test('testVectorLong3', () { 102 | final ikm = Uint8List.fromList([ 103 | 0x00, 104 | 0x01, 105 | 0x02, 106 | 0x03, 107 | 0x04, 108 | 0x05, 109 | 0x06, 110 | 0x07, 111 | 0x08, 112 | 0x09, 113 | 0x0a, 114 | 0x0b, 115 | 0x0c, 116 | 0x0d, 117 | 0x0e, 118 | 0x0f, 119 | 0x10, 120 | 0x11, 121 | 0x12, 122 | 0x13, 123 | 0x14, 124 | 0x15, 125 | 0x16, 126 | 0x17, 127 | 0x18, 128 | 0x19, 129 | 0x1a, 130 | 0x1b, 131 | 0x1c, 132 | 0x1d, 133 | 0x1e, 134 | 0x1f, 135 | 0x20, 136 | 0x21, 137 | 0x22, 138 | 0x23, 139 | 0x24, 140 | 0x25, 141 | 0x26, 142 | 0x27, 143 | 0x28, 144 | 0x29, 145 | 0x2a, 146 | 0x2b, 147 | 0x2c, 148 | 0x2d, 149 | 0x2e, 150 | 0x2f, 151 | 0x30, 152 | 0x31, 153 | 0x32, 154 | 0x33, 155 | 0x34, 156 | 0x35, 157 | 0x36, 158 | 0x37, 159 | 0x38, 160 | 0x39, 161 | 0x3a, 162 | 0x3b, 163 | 0x3c, 164 | 0x3d, 165 | 0x3e, 166 | 0x3f, 167 | 0x40, 168 | 0x41, 169 | 0x42, 170 | 0x43, 171 | 0x44, 172 | 0x45, 173 | 0x46, 174 | 0x47, 175 | 0x48, 176 | 0x49, 177 | 0x4a, 178 | 0x4b, 179 | 0x4c, 180 | 0x4d, 181 | 0x4e, 182 | 0x4f 183 | ]); 184 | 185 | final salt = Uint8List.fromList([ 186 | 0x60, 187 | 0x61, 188 | 0x62, 189 | 0x63, 190 | 0x64, 191 | 0x65, 192 | 0x66, 193 | 0x67, 194 | 0x68, 195 | 0x69, 196 | 0x6a, 197 | 0x6b, 198 | 0x6c, 199 | 0x6d, 200 | 0x6e, 201 | 0x6f, 202 | 0x70, 203 | 0x71, 204 | 0x72, 205 | 0x73, 206 | 0x74, 207 | 0x75, 208 | 0x76, 209 | 0x77, 210 | 0x78, 211 | 0x79, 212 | 0x7a, 213 | 0x7b, 214 | 0x7c, 215 | 0x7d, 216 | 0x7e, 217 | 0x7f, 218 | 0x80, 219 | 0x81, 220 | 0x82, 221 | 0x83, 222 | 0x84, 223 | 0x85, 224 | 0x86, 225 | 0x87, 226 | 0x88, 227 | 0x89, 228 | 0x8a, 229 | 0x8b, 230 | 0x8c, 231 | 0x8d, 232 | 0x8e, 233 | 0x8f, 234 | 0x90, 235 | 0x91, 236 | 0x92, 237 | 0x93, 238 | 0x94, 239 | 0x95, 240 | 0x96, 241 | 0x97, 242 | 0x98, 243 | 0x99, 244 | 0x9a, 245 | 0x9b, 246 | 0x9c, 247 | 0x9d, 248 | 0x9e, 249 | 0x9f, 250 | 0xa0, 251 | 0xa1, 252 | 0xa2, 253 | 0xa3, 254 | 0xa4, 255 | 0xa5, 256 | 0xa6, 257 | 0xa7, 258 | 0xa8, 259 | 0xa9, 260 | 0xaa, 261 | 0xab, 262 | 0xac, 263 | 0xad, 264 | 0xae, 265 | 0xaf 266 | ]); 267 | 268 | final info = Uint8List.fromList([ 269 | 0xb0, 270 | 0xb1, 271 | 0xb2, 272 | 0xb3, 273 | 0xb4, 274 | 0xb5, 275 | 0xb6, 276 | 0xb7, 277 | 0xb8, 278 | 0xb9, 279 | 0xba, 280 | 0xbb, 281 | 0xbc, 282 | 0xbd, 283 | 0xbe, 284 | 0xbf, 285 | 0xc0, 286 | 0xc1, 287 | 0xc2, 288 | 0xc3, 289 | 0xc4, 290 | 0xc5, 291 | 0xc6, 292 | 0xc7, 293 | 0xc8, 294 | 0xc9, 295 | 0xca, 296 | 0xcb, 297 | 0xcc, 298 | 0xcd, 299 | 0xce, 300 | 0xcf, 301 | 0xd0, 302 | 0xd1, 303 | 0xd2, 304 | 0xd3, 305 | 0xd4, 306 | 0xd5, 307 | 0xd6, 308 | 0xd7, 309 | 0xd8, 310 | 0xd9, 311 | 0xda, 312 | 0xdb, 313 | 0xdc, 314 | 0xdd, 315 | 0xde, 316 | 0xdf, 317 | 0xe0, 318 | 0xe1, 319 | 0xe2, 320 | 0xe3, 321 | 0xe4, 322 | 0xe5, 323 | 0xe6, 324 | 0xe7, 325 | 0xe8, 326 | 0xe9, 327 | 0xea, 328 | 0xeb, 329 | 0xec, 330 | 0xed, 331 | 0xee, 332 | 0xef, 333 | 0xf0, 334 | 0xf1, 335 | 0xf2, 336 | 0xf3, 337 | 0xf4, 338 | 0xf5, 339 | 0xf6, 340 | 0xf7, 341 | 0xf8, 342 | 0xf9, 343 | 0xfa, 344 | 0xfb, 345 | 0xfc, 346 | 0xfd, 347 | 0xfe, 348 | 0xff 349 | ]); 350 | 351 | final okm = Uint8List.fromList([ 352 | 0xb1, 353 | 0x1e, 354 | 0x39, 355 | 0x8d, 356 | 0xc8, 357 | 0x03, 358 | 0x27, 359 | 0xa1, 360 | 0xc8, 361 | 0xe7, 362 | 0xf7, 363 | 0x8c, 364 | 0x59, 365 | 0x6a, 366 | 0x49, 367 | 0x34, 368 | 0x4f, 369 | 0x01, 370 | 0x2e, 371 | 0xda, 372 | 0x2d, 373 | 0x4e, 374 | 0xfa, 375 | 0xd8, 376 | 0xa0, 377 | 0x50, 378 | 0xcc, 379 | 0x4c, 380 | 0x19, 381 | 0xaf, 382 | 0xa9, 383 | 0x7c, 384 | 0x59, 385 | 0x04, 386 | 0x5a, 387 | 0x99, 388 | 0xca, 389 | 0xc7, 390 | 0x82, 391 | 0x72, 392 | 0x71, 393 | 0xcb, 394 | 0x41, 395 | 0xc6, 396 | 0x5e, 397 | 0x59, 398 | 0x0e, 399 | 0x09, 400 | 0xda, 401 | 0x32, 402 | 0x75, 403 | 0x60, 404 | 0x0c, 405 | 0x2f, 406 | 0x09, 407 | 0xb8, 408 | 0x36, 409 | 0x77, 410 | 0x93, 411 | 0xa9, 412 | 0xac, 413 | 0xa3, 414 | 0xdb, 415 | 0x71, 416 | 0xcc, 417 | 0x30, 418 | 0xc5, 419 | 0x81, 420 | 0x79, 421 | 0xec, 422 | 0x3e, 423 | 0x87, 424 | 0xc1, 425 | 0x4c, 426 | 0x01, 427 | 0xd5, 428 | 0xc1, 429 | 0xf3, 430 | 0x43, 431 | 0x4f, 432 | 0x1d, 433 | 0x87 434 | ]); 435 | 436 | final actualOutput = HKDF.createFor(3).deriveSecrets4(ikm, salt, info, 82); 437 | expect(okm, actualOutput); 438 | }); 439 | 440 | test('testVectorV2', () { 441 | final ikm = Uint8List.fromList([ 442 | 0x0b, 443 | 0x0b, 444 | 0x0b, 445 | 0x0b, 446 | 0x0b, 447 | 0x0b, 448 | 0x0b, 449 | 0x0b, 450 | 0x0b, 451 | 0x0b, 452 | 0x0b, 453 | 0x0b, 454 | 0x0b, 455 | 0x0b, 456 | 0x0b, 457 | 0x0b, 458 | 0x0b, 459 | 0x0b, 460 | 0x0b, 461 | 0x0b, 462 | 0x0b, 463 | 0x0b 464 | ]); 465 | 466 | final salt = Uint8List.fromList([ 467 | 0x00, 468 | 0x01, 469 | 0x02, 470 | 0x03, 471 | 0x04, 472 | 0x05, 473 | 0x06, 474 | 0x07, 475 | 0x08, 476 | 0x09, 477 | 0x0a, 478 | 0x0b, 479 | 0x0c 480 | ]); 481 | 482 | final info = Uint8List.fromList( 483 | [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9]); 484 | 485 | final okm = Uint8List.fromList([ 486 | 0x6e, 487 | 0xc2, 488 | 0x55, 489 | 0x6d, 490 | 0x5d, 491 | 0x7b, 492 | 0x1d, 493 | 0x81, 494 | 0xde, 495 | 0xe4, 496 | 0x22, 497 | 0x2a, 498 | 0xd7, 499 | 0x48, 500 | 0x36, 501 | 0x95, 502 | 0xdd, 503 | 0xc9, 504 | 0x8f, 505 | 0x4f, 506 | 0x5f, 507 | 0xab, 508 | 0xc0, 509 | 0xe0, 510 | 0x20, 511 | 0x5d, 512 | 0xc2, 513 | 0xef, 514 | 0x87, 515 | 0x52, 516 | 0xd4, 517 | 0x1e, 518 | 0x04, 519 | 0xe2, 520 | 0xe2, 521 | 0x11, 522 | 0x01, 523 | 0xc6, 524 | 0x8f, 525 | 0xf0, 526 | 0x93, 527 | 0x94, 528 | 0xb8, 529 | 0xad, 530 | 0x0b, 531 | 0xdc, 532 | 0xb9, 533 | 0x60, 534 | 0x9c, 535 | 0xd4, 536 | 0xee, 537 | 0x82, 538 | 0xac, 539 | 0x13, 540 | 0x19, 541 | 0x9b, 542 | 0x4a, 543 | 0xa9, 544 | 0xfd, 545 | 0xa8, 546 | 0x99, 547 | 0xda, 548 | 0xeb, 549 | 0xec 550 | ]); 551 | 552 | final actualOutput = HKDF.createFor(2).deriveSecrets4(ikm, salt, info, 64); 553 | expect(okm, actualOutput); 554 | }); 555 | } 556 | -------------------------------------------------------------------------------- /test/provisioning_cipher_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 5 | import 'package:libsignal_protocol_dart/src/cbc.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | test('testDecrypt', () { 10 | const privateKey = 'GHtZBYTNqbCofFo0keD3jTGoHF6bUAeiW9iV5ad/HHA='; 11 | const content = 12 | 'eyJwdWJsaWNfa2V5IjoiQmZFOWJFa3EzZ2FsUTFHTnVEMWlJaHBrSkE0RHRTVUxkYXhkS3JiZndMcDgiLCJib2R5IjoiQVd6YWZJSDEyZ2tTQmRjdVplSXRMM3lXVmhZdW1lemppZk9rbFdsV3lnQzVKSXpzMHVxYXFZNnhsdFJzVWJya2N0NGFLazVKSmxwYlBKSXNqTU5qYXZKR1hNSFpOSnQ2SXZ1S1pkUVwvN1RzbjRUUjhmWjNSUXo2aGM3RlpYZENLTUJMVzJHTjAzSDd3aUt1elArZVV4WktCQjFPb2pWaVlSU0Vyc3dUWGwxeWR5cnhcLzM1NVY5MzFCR0N1VlBLVXFuOUJFWCtidVFhYms3YWZYdEVCOUI2YTZGSnFCZXVGcHdcLzlDdWpwYVpXNzNIMmswTmxkNjdPMzB5QkZEM3RuNmtiaXZ3MzNjN0l2Uk9EYlwvQnFTS1NGSlJoMUE2eU1leTAyeHZkNkJpRkxja1FRQk9LXC9ROW5ZOExoM2VlS2FNNTQ3cVV3XC9qUEE0ZGE5TzI0RkJUXC9ON2NvR2dBOVkrN1pvZ0syQ3YzbDZCNG9CN0xyTCtrVlRWTHJ2MFA1aDF3YklrUm10YWRLQmxiTjVvK3RnVUZ5VnVNcWFXQVJ6QnBJNlVDajVaZ0JJVWJWM3N0enVwUXpYU0E5OVBWV3hXOTE2dz09In0='; 13 | final plaintext = decrypt(privateKey, content); 14 | final result = utf8.decode(plaintext, allowMalformed: true); 15 | const r = 16 | '{"session_id":"304745a1-45af-4045-bd16-ba4d42a03a4e","platform":"iOS","user_id":"f59b9309-70c2-4b69-8fd8-5773dbd10018","identity_key_public":"BTEcMFj5uvP+32z+avKFOjDOrMvmnoDmwMfPZcuxBT08","identity_key_private":"iG5ilNnI8dkqtslK84NWWmPUhzADyUm6odlwA96isEk=","provisioning_code":"7972"}'; 17 | assert(result == r); 18 | }); 19 | 20 | test('testEncrypt', () { 21 | final privateKey = 22 | base64.decode('v9FTNn2tg40ENCEaoCHstCo5J0wb9wKwgZQ6PYJjf0U='); 23 | final iv = base64.decode('tNp4sPQGKjwzqN0L8tDLDg=='); 24 | const encode = 'l0JO9zrPWzrPg2r53Sjf6g=='; 25 | final ciphertext = aesCbcEncrypt( 26 | privateKey, iv, Uint8List.fromList(utf8.encode('Hello Mixin'))); 27 | assert(encode == base64.encode(ciphertext)); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/ratchet/chain_key_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:libsignal_protocol_dart/src/kdf/hkdf.dart'; 4 | import 'package:libsignal_protocol_dart/src/ratchet/chain_key.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | test('testChainKeyDerivationV2', () { 9 | final seed = Uint8List.fromList([ 10 | 0x8a, 11 | 0xb7, 12 | 0x2d, 13 | 0x6f, 14 | 0x4c, 15 | 0xc5, 16 | 0xac, 17 | 0x0d, 18 | 0x38, 19 | 0x7e, 20 | 0xaf, 21 | 0x46, 22 | 0x33, 23 | 0x78, 24 | 0xdd, 25 | 0xb2, 26 | 0x8e, 27 | 0xdd, 28 | 0x07, 29 | 0x38, 30 | 0x5b, 31 | 0x1c, 32 | 0xb0, 33 | 0x12, 34 | 0x50, 35 | 0xc7, 36 | 0x15, 37 | 0x98, 38 | 0x2e, 39 | 0x7a, 40 | 0xd4, 41 | 0x8f 42 | ]); 43 | 44 | final messageKey = Uint8List.fromList([ 45 | 0x02, 46 | 0xa9, 47 | 0xaa, 48 | 0x6c, 49 | 0x7d, 50 | 0xbd, 51 | 0x64, 52 | 0xf9, 53 | 0xd3, 54 | 0xaa, 55 | 0x92, 56 | 0xf9, 57 | 0x2a, 58 | 0x27, 59 | 0x7b, 60 | 0xf5, 61 | 0x46, 62 | 0x09, 63 | 0xda, 64 | 0xdf, 65 | 0x0b, 66 | 0x00, 67 | 0x82, 68 | 0x8a, 69 | 0xcf, 70 | 0xc6, 71 | 0x1e, 72 | 0x3c, 73 | 0x72, 74 | 0x4b, 75 | 0x84, 76 | 0xa7 77 | ]); 78 | 79 | final macKey = Uint8List.fromList([ 80 | 0xbf, 81 | 0xbe, 82 | 0x5e, 83 | 0xfb, 84 | 0x60, 85 | 0x30, 86 | 0x30, 87 | 0x52, 88 | 0x67, 89 | 0x42, 90 | 0xe3, 91 | 0xee, 92 | 0x89, 93 | 0xc7, 94 | 0x02, 95 | 0x4e, 96 | 0x88, 97 | 0x4e, 98 | 0x44, 99 | 0x0f, 100 | 0x1f, 101 | 0xf3, 102 | 0x76, 103 | 0xbb, 104 | 0x23, 105 | 0x17, 106 | 0xb2, 107 | 0xd6, 108 | 0x4d, 109 | 0xeb, 110 | 0x7c, 111 | 0x83 112 | ]); 113 | 114 | final nextChainKey = Uint8List.fromList([ 115 | 0x28, 116 | 0xe8, 117 | 0xf8, 118 | 0xfe, 119 | 0xe5, 120 | 0x4b, 121 | 0x80, 122 | 0x1e, 123 | 0xef, 124 | 0x7c, 125 | 0x5c, 126 | 0xfb, 127 | 0x2f, 128 | 0x17, 129 | 0xf3, 130 | 0x2c, 131 | 0x7b, 132 | 0x33, 133 | 0x44, 134 | 0x85, 135 | 0xbb, 136 | 0xb7, 137 | 0x0f, 138 | 0xac, 139 | 0x6e, 140 | 0xc1, 141 | 0x03, 142 | 0x42, 143 | 0xa2, 144 | 0x46, 145 | 0xd1, 146 | 0x5d 147 | ]); 148 | 149 | final chainKey = ChainKey(HKDF.createFor(2), seed, 0); 150 | 151 | expect(chainKey.key, seed); 152 | expect(chainKey.getMessageKeys().getCipherKey(), messageKey); 153 | expect(chainKey.getMessageKeys().getMacKey(), macKey); 154 | expect(chainKey.getNextChainKey().key, nextChainKey); 155 | expect(chainKey.index, 0); 156 | expect(chainKey.getMessageKeys().getCounter(), 0); 157 | expect(chainKey.getNextChainKey().index, 1); 158 | expect(chainKey.getNextChainKey().getMessageKeys().getCounter(), 1); 159 | }); 160 | 161 | test('testChainKeyDerivationV3()', () { 162 | final seed = Uint8List.fromList([ 163 | 0x8a, 164 | 0xb7, 165 | 0x2d, 166 | 0x6f, 167 | 0x4c, 168 | 0xc5, 169 | 0xac, 170 | 0x0d, 171 | 0x38, 172 | 0x7e, 173 | 0xaf, 174 | 0x46, 175 | 0x33, 176 | 0x78, 177 | 0xdd, 178 | 0xb2, 179 | 0x8e, 180 | 0xdd, 181 | 0x07, 182 | 0x38, 183 | 0x5b, 184 | 0x1c, 185 | 0xb0, 186 | 0x12, 187 | 0x50, 188 | 0xc7, 189 | 0x15, 190 | 0x98, 191 | 0x2e, 192 | 0x7a, 193 | 0xd4, 194 | 0x8f 195 | ]); 196 | 197 | final messageKey = Uint8List.fromList([ 198 | /* 0x02*/ 199 | 0xbf, 200 | 0x51, 201 | 0xe9, 202 | 0xd7, 203 | 0x5e, 204 | 0x0e, 205 | 0x31, 206 | 0x03, 207 | 0x10, 208 | 0x51, 209 | 0xf8, 210 | 0x2a, 211 | 0x24, 212 | 0x91, 213 | 0xff, 214 | 0xc0, 215 | 0x84, 216 | 0xfa, 217 | 0x29, 218 | 0x8b, 219 | 0x77, 220 | 0x93, 221 | 0xbd, 222 | 0x9d, 223 | 0xb6, 224 | 0x20, 225 | 0x05, 226 | 0x6f, 227 | 0xeb, 228 | 0xf4, 229 | 0x52, 230 | 0x17 231 | ]); 232 | 233 | final macKey = Uint8List.fromList([ 234 | 0xc6, 235 | 0xc7, 236 | 0x7d, 237 | 0x6a, 238 | 0x73, 239 | 0xa3, 240 | 0x54, 241 | 0x33, 242 | 0x7a, 243 | 0x56, 244 | 0x43, 245 | 0x5e, 246 | 0x34, 247 | 0x60, 248 | 0x7d, 249 | 0xfe, 250 | 0x48, 251 | 0xe3, 252 | 0xac, 253 | 0xe1, 254 | 0x4e, 255 | 0x77, 256 | 0x31, 257 | 0x4d, 258 | 0xc6, 259 | 0xab, 260 | 0xc1, 261 | 0x72, 262 | 0xe7, 263 | 0xa7, 264 | 0x03, 265 | 0x0b 266 | ]); 267 | 268 | final nextChainKey = Uint8List.fromList([ 269 | 0x28, 270 | 0xe8, 271 | 0xf8, 272 | 0xfe, 273 | 0xe5, 274 | 0x4b, 275 | 0x80, 276 | 0x1e, 277 | 0xef, 278 | 0x7c, 279 | 0x5c, 280 | 0xfb, 281 | 0x2f, 282 | 0x17, 283 | 0xf3, 284 | 0x2c, 285 | 0x7b, 286 | 0x33, 287 | 0x44, 288 | 0x85, 289 | 0xbb, 290 | 0xb7, 291 | 0x0f, 292 | 0xac, 293 | 0x6e, 294 | 0xc1, 295 | 0x03, 296 | 0x42, 297 | 0xa2, 298 | 0x46, 299 | 0xd1, 300 | 0x5d 301 | ]); 302 | 303 | final chainKey = ChainKey(HKDF.createFor(3), seed, 0); 304 | 305 | expect(chainKey.key, seed); 306 | expect(chainKey.getMessageKeys().getCipherKey(), messageKey); 307 | expect(chainKey.getMessageKeys().getMacKey(), macKey); 308 | expect(chainKey.getNextChainKey().key, nextChainKey); 309 | expect(chainKey.index, 0); 310 | expect(chainKey.getMessageKeys().getCounter(), 0); 311 | expect(chainKey.getNextChainKey().index, 1); 312 | expect(chainKey.getNextChainKey().getMessageKeys().getCounter(), 1); 313 | }); 314 | } 315 | -------------------------------------------------------------------------------- /test/ratchet/ratcheting_session_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; 4 | import 'package:libsignal_protocol_dart/src/ecc/ec_key_pair.dart'; 5 | import 'package:libsignal_protocol_dart/src/identity_key.dart'; 6 | import 'package:libsignal_protocol_dart/src/identity_key_pair.dart'; 7 | import 'package:libsignal_protocol_dart/src/ratchet/bob_signal_protocol_parameters.dart'; 8 | import 'package:libsignal_protocol_dart/src/ratchet/ratcheting_session.dart'; 9 | import 'package:libsignal_protocol_dart/src/state/session_state.dart'; 10 | import 'package:optional/optional.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | void main() { 14 | test('testRatchetingSessionAsBob', () { 15 | final bobPublic = Uint8List.fromList([ 16 | 0x05, 17 | 0x2c, 18 | 0xb4, 19 | 0x97, 20 | 0x76, 21 | 0xb8, 22 | 0x77, 23 | 0x02, 24 | 0x05, 25 | 0x74, 26 | 0x5a, 27 | 0x3a, 28 | 0x6e, 29 | 0x24, 30 | 0xf5, 31 | 0x79, 32 | 0xcd, 33 | 0xb4, 34 | 0xba, 35 | 0x7a, 36 | 0x89, 37 | 0x04, 38 | 0x10, 39 | 0x05, 40 | 0x92, 41 | 0x8e, 42 | 0xbb, 43 | 0xad, 44 | 0xc9, 45 | 0xc0, 46 | 0x5a, 47 | 0xd4, 48 | 0x58 49 | ]); 50 | 51 | final bobPrivate = Uint8List.fromList([ 52 | 0xa1, 53 | 0xca, 54 | 0xb4, 55 | 0x8f, 56 | 0x7c, 57 | 0x89, 58 | 0x3f, 59 | 0xaf, 60 | 0xa9, 61 | 0x88, 62 | 0x0a, 63 | 0x28, 64 | 0xc3, 65 | 0xb4, 66 | 0x99, 67 | 0x9d, 68 | 0x28, 69 | 0xd6, 70 | 0x32, 71 | 0x95, 72 | 0x62, 73 | 0xd2, 74 | 0x7a, 75 | 0x4e, 76 | 0xa4, 77 | 0xe2, 78 | 0x2e, 79 | 0x9f, 80 | 0xf1, 81 | 0xbd, 82 | 0xd6, 83 | 0x5a 84 | ]); 85 | 86 | final bobIdentityPublic = Uint8List.fromList([ 87 | 0x05, 88 | 0xf1, 89 | 0xf4, 90 | 0x38, 91 | 0x74, 92 | 0xf6, 93 | 0x96, 94 | 0x69, 95 | 0x56, 96 | 0xc2, 97 | 0xdd, 98 | 0x47, 99 | 0x3f, 100 | 0x8f, 101 | 0xa1, 102 | 0x5a, 103 | 0xde, 104 | 0xb7, 105 | 0x1d, 106 | 0x1c, 107 | 0xb9, 108 | 0x91, 109 | 0xb2, 110 | 0x34, 111 | 0x16, 112 | 0x92, 113 | 0x32, 114 | 0x4c, 115 | 0xef, 116 | 0xb1, 117 | 0xc5, 118 | 0xe6, 119 | 0x26 120 | ]); 121 | 122 | final bobIdentityPrivate = Uint8List.fromList([ 123 | 0x48, 124 | 0x75, 125 | 0xcc, 126 | 0x69, 127 | 0xdd, 128 | 0xf8, 129 | 0xea, 130 | 0x07, 131 | 0x19, 132 | 0xec, 133 | 0x94, 134 | 0x7d, 135 | 0x61, 136 | 0x08, 137 | 0x11, 138 | 0x35, 139 | 0x86, 140 | 0x8d, 141 | 0x5f, 142 | 0xd8, 143 | 0x01, 144 | 0xf0, 145 | 0x2c, 146 | 0x02, 147 | 0x25, 148 | 0xe5, 149 | 0x16, 150 | 0xdf, 151 | 0x21, 152 | 0x56, 153 | 0x60, 154 | 0x5e 155 | ]); 156 | 157 | final aliceBasePublic = Uint8List.fromList([ 158 | 0x05, 159 | 0x47, 160 | 0x2d, 161 | 0x1f, 162 | 0xb1, 163 | 0xa9, 164 | 0x86, 165 | 0x2c, 166 | 0x3a, 167 | 0xf6, 168 | 0xbe, 169 | 0xac, 170 | 0xa8, 171 | 0x92, 172 | 0x02, 173 | 0x77, 174 | 0xe2, 175 | 0xb2, 176 | 0x6f, 177 | 0x4a, 178 | 0x79, 179 | 0x21, 180 | 0x3e, 181 | 0xc7, 182 | 0xc9, 183 | 0x06, 184 | 0xae, 185 | 0xb3, 186 | 0x5e, 187 | 0x03, 188 | 0xcf, 189 | 0x89, 190 | 0x50 191 | ]); 192 | 193 | final aliceEphemeralPublic = Uint8List.fromList([ 194 | 0x05, 195 | 0x6c, 196 | 0x3e, 197 | 0x0d, 198 | 0x1f, 199 | 0x52, 200 | 0x02, 201 | 0x83, 202 | 0xef, 203 | 0xcc, 204 | 0x55, 205 | 0xfc, 206 | 0xa5, 207 | 0xe6, 208 | 0x70, 209 | 0x75, 210 | 0xb9, 211 | 0x04, 212 | 0x00, 213 | 0x7f, 214 | 0x18, 215 | 0x81, 216 | 0xd1, 217 | 0x51, 218 | 0xaf, 219 | 0x76, 220 | 0xdf, 221 | 0x18, 222 | 0xc5, 223 | 0x1d, 224 | 0x29, 225 | 0xd3, 226 | 0x4b 227 | ]); 228 | 229 | final aliceIdentityPublic = Uint8List.fromList([ 230 | 0x05, 231 | 0xb4, 232 | 0xa8, 233 | 0x45, 234 | 0x56, 235 | 0x60, 236 | 0xad, 237 | 0xa6, 238 | 0x5b, 239 | 0x40, 240 | 0x10, 241 | 0x07, 242 | 0xf6, 243 | 0x15, 244 | 0xe6, 245 | 0x54, 246 | 0x04, 247 | 0x17, 248 | 0x46, 249 | 0x43, 250 | 0x2e, 251 | 0x33, 252 | 0x39, 253 | 0xc6, 254 | 0x87, 255 | 0x51, 256 | 0x49, 257 | 0xbc, 258 | 0xee, 259 | 0xfc, 260 | 0xb4, 261 | 0x2b, 262 | 0x4a 263 | ]); 264 | 265 | final bobSignedPreKeyPublic = Uint8List.fromList([ 266 | 0x05, 267 | 0xac, 268 | 0x24, 269 | 0x8a, 270 | 0x8f, 271 | 0x26, 272 | 0x3b, 273 | 0xe6, 274 | 0x86, 275 | 0x35, 276 | 0x76, 277 | 0xeb, 278 | 0x03, 279 | 0x62, 280 | 0xe2, 281 | 0x8c, 282 | 0x82, 283 | 0x8f, 284 | 0x01, 285 | 0x07, 286 | 0xa3, 287 | 0x37, 288 | 0x9d, 289 | 0x34, 290 | 0xba, 291 | 0xb1, 292 | 0x58, 293 | 0x6b, 294 | 0xf8, 295 | 0xc7, 296 | 0x70, 297 | 0xcd, 298 | 0x67 299 | ]); 300 | 301 | final bobSignedPreKeyPrivate = Uint8List.fromList([ 302 | 0x58, 303 | 0x39, 304 | 0x00, 305 | 0x13, 306 | 0x1f, 307 | 0xb7, 308 | 0x27, 309 | 0x99, 310 | 0x8b, 311 | 0x78, 312 | 0x03, 313 | 0xfe, 314 | 0x6a, 315 | 0xc2, 316 | 0x2c, 317 | 0xc5, 318 | 0x91, 319 | 0xf3, 320 | 0x42, 321 | 0xe4, 322 | 0xe4, 323 | 0x2a, 324 | 0x8c, 325 | 0x8d, 326 | 0x5d, 327 | 0x78, 328 | 0x19, 329 | 0x42, 330 | 0x09, 331 | 0xb8, 332 | 0xd2, 333 | 0x53 334 | ]); 335 | 336 | final senderChain = Uint8List.fromList([ 337 | 0x97, 338 | 0x97, 339 | 0xca, 340 | 0xca, 341 | 0x53, 342 | 0xc9, 343 | 0x89, 344 | 0xbb, 345 | 0xe2, 346 | 0x29, 347 | 0xa4, 348 | 0x0c, 349 | 0xa7, 350 | 0x72, 351 | 0x70, 352 | 0x10, 353 | 0xeb, 354 | 0x26, 355 | 0x04, 356 | 0xfc, 357 | 0x14, 358 | 0x94, 359 | 0x5d, 360 | 0x77, 361 | 0x95, 362 | 0x8a, 363 | 0x0a, 364 | 0xed, 365 | 0xa0, 366 | 0x88, 367 | 0xb4, 368 | 0x4d 369 | ]); 370 | 371 | final bobIdentityKeyPublic = IdentityKey.fromBytes(bobIdentityPublic, 0); 372 | final bobIdentityKeyPrivate = Curve.decodePrivatePoint(bobIdentityPrivate); 373 | final bobIdentityKey = 374 | IdentityKeyPair(bobIdentityKeyPublic, bobIdentityKeyPrivate); 375 | final bobEphemeralPublicKey = Curve.decodePoint(bobPublic, 0); 376 | final bobEphemeralPrivateKey = Curve.decodePrivatePoint(bobPrivate); 377 | final bobEphemeralKey = 378 | ECKeyPair(bobEphemeralPublicKey, bobEphemeralPrivateKey); 379 | // ignore: unused_local_variable 380 | final bobBaseKey = bobEphemeralKey; 381 | final bobSignedPreKey = ECKeyPair( 382 | Curve.decodePoint(bobSignedPreKeyPublic, 0), 383 | Curve.decodePrivatePoint(bobSignedPreKeyPrivate)); 384 | 385 | final aliceBasePublicKey = Curve.decodePoint(aliceBasePublic, 0); 386 | // ignore: unused_local_variable 387 | final aliceEphemeralPublicKey = Curve.decodePoint(aliceEphemeralPublic, 0); 388 | final aliceIdentityPublicKey = 389 | IdentityKey.fromBytes(aliceIdentityPublic, 0); 390 | 391 | final parameters = BobSignalProtocolParameters( 392 | ourIdentityKey: bobIdentityKey, 393 | ourSignedPreKey: bobSignedPreKey, 394 | ourRatchetKey: bobEphemeralKey, 395 | ourOneTimePreKey: const Optional.empty(), 396 | theirIdentityKey: aliceIdentityPublicKey, 397 | theirBaseKey: aliceBasePublicKey, 398 | ); 399 | 400 | final session = SessionState(); 401 | 402 | RatchetingSession.initializeSessionBob(session, parameters); 403 | 404 | expect( 405 | 0, 406 | session 407 | .getLocalIdentityKey() 408 | .publicKey 409 | .compareTo(bobIdentityKey.getPublicKey().publicKey)); 410 | expect( 411 | 0, 412 | session 413 | .getRemoteIdentityKey() 414 | ?.publicKey 415 | .compareTo(aliceIdentityPublicKey.publicKey)); 416 | expect(session.getSenderChainKey().key, senderChain); 417 | }); 418 | } 419 | -------------------------------------------------------------------------------- /test/ratchet/root_key_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; 4 | import 'package:libsignal_protocol_dart/src/ecc/ec_key_pair.dart'; 5 | import 'package:libsignal_protocol_dart/src/kdf/hkdf.dart'; 6 | import 'package:libsignal_protocol_dart/src/ratchet/root_key.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | test('testRootKeyDerivationV2', () { 11 | final rootKeySeed = Uint8List.fromList([ 12 | 0x7b, 13 | 0xa6, 14 | 0xde, 15 | 0xbc, 16 | 0x2b, 17 | 0xc1, 18 | 0xbb, 19 | 0xf9, 20 | 0x1a, 21 | 0xbb, 22 | 0xc1, 23 | 0x36, 24 | 0x74, 25 | 0x04, 26 | 0x17, 27 | 0x6c, 28 | 0xa6, 29 | 0x23, 30 | 0x09, 31 | 0x5b, 32 | 0x7e, 33 | 0xc6, 34 | 0x6b, 35 | 0x45, 36 | 0xf6, 37 | 0x02, 38 | 0xd9, 39 | 0x35, 40 | 0x38, 41 | 0x94, 42 | 0x2d, 43 | 0xcc 44 | ]); 45 | 46 | final alicePublic = Uint8List.fromList([ 47 | 0x05, 48 | 0xee, 49 | 0x4f, 50 | 0xa6, 51 | 0xcd, 52 | 0xc0, 53 | 0x30, 54 | 0xdf, 55 | 0x49, 56 | 0xec, 57 | 0xd0, 58 | 0xba, 59 | 0x6c, 60 | 0xfc, 61 | 0xff, 62 | 0xb2, 63 | 0x33, 64 | 0xd3, 65 | 0x65, 66 | 0xa2, 67 | 0x7f, 68 | 0xad, 69 | 0xbe, 70 | 0xff, 71 | 0x77, 72 | 0xe9, 73 | 0x63, 74 | 0xfc, 75 | 0xb1, 76 | 0x62, 77 | 0x22, 78 | 0xe1, 79 | 0x3a 80 | ]); 81 | 82 | final alicePrivate = Uint8List.fromList([ 83 | 0x21, 84 | 0x68, 85 | 0x22, 86 | 0xec, 87 | 0x67, 88 | 0xeb, 89 | 0x38, 90 | 0x04, 91 | 0x9e, 92 | 0xba, 93 | 0xe7, 94 | 0xb9, 95 | 0x39, 96 | 0xba, 97 | 0xea, 98 | 0xeb, 99 | 0xb1, 100 | 0x51, 101 | 0xbb, 102 | 0xb3, 103 | 0x2d, 104 | 0xb8, 105 | 0x0f, 106 | 0xd3, 107 | 0x89, 108 | 0x24, 109 | 0x5a, 110 | 0xc3, 111 | 0x7a, 112 | 0x94, 113 | 0x8e, 114 | 0x50 115 | ]); 116 | 117 | final bobPublic = Uint8List.fromList([ 118 | 0x05, 119 | 0xab, 120 | 0xb8, 121 | 0xeb, 122 | 0x29, 123 | 0xcc, 124 | 0x80, 125 | 0xb4, 126 | 0x71, 127 | 0x09, 128 | 0xa2, 129 | 0x26, 130 | 0x5a, 131 | 0xbe, 132 | 0x97, 133 | 0x98, 134 | 0x48, 135 | 0x54, 136 | 0x06, 137 | 0xe3, 138 | 0x2d, 139 | 0xa2, 140 | 0x68, 141 | 0x93, 142 | 0x4a, 143 | 0x95, 144 | 0x55, 145 | 0xe8, 146 | 0x47, 147 | 0x57, 148 | 0x70, 149 | 0x8a, 150 | 0x30 151 | ]); 152 | 153 | // ignore: unused_local_variable 154 | final nextRoot = Uint8List.fromList([ 155 | 0xb1, 156 | 0x14, 157 | 0xf5, 158 | 0xde, 159 | 0x28, 160 | 0x01, 161 | 0x19, 162 | 0x85, 163 | 0xe6, 164 | 0xeb, 165 | 0xa2, 166 | 0x5d, 167 | 0x50, 168 | 0xe7, 169 | 0xec, 170 | 0x41, 171 | 0xa9, 172 | 0xb0, 173 | 0x2f, 174 | 0x56, 175 | 0x93, 176 | 0xc5, 177 | 0xc7, 178 | 0x88, 179 | 0xa6, 180 | 0x3a, 181 | 0x06, 182 | 0xd2, 183 | 0x12, 184 | 0xa2, 185 | 0xf7, 186 | 0x31 187 | ]); 188 | 189 | // ignore: unused_local_variable 190 | final nextChain = Uint8List.fromList([ 191 | 0x9d, 192 | 0x7d, 193 | 0x24, 194 | 0x69, 195 | 0xbc, 196 | 0x9a, 197 | 0xe5, 198 | 0x3e, 199 | 0xe9, 200 | 0x80, 201 | 0x5a, 202 | 0xa3, 203 | 0x26, 204 | 0x4d, 205 | 0x24, 206 | 0x99, 207 | 0xa3, 208 | 0xac, 209 | 0xe8, 210 | 0x0f, 211 | 0x4c, 212 | 0xca, 213 | 0xe2, 214 | 0xda, 215 | 0x13, 216 | 0x43, 217 | 0x0c, 218 | 0x5c, 219 | 0x55, 220 | 0xb5, 221 | 0xca, 222 | 0x5f 223 | ]); 224 | 225 | final alicePublicKey = Curve.decodePoint(alicePublic, 0); 226 | final alicePrivateKey = Curve.decodePrivatePoint(alicePrivate); 227 | final aliceKeyPair = ECKeyPair(alicePublicKey, alicePrivateKey); 228 | 229 | final bobPublicKey = Curve.decodePoint(bobPublic, 0); 230 | final rootKey = RootKey(HKDF.createFor(2), rootKeySeed); 231 | 232 | final rootKeyChainKeyPair = rootKey.createChain(bobPublicKey, aliceKeyPair); 233 | // ignore: unused_local_variable 234 | final nextRootKey = rootKeyChainKeyPair.$1; 235 | // ignore: unused_local_variable 236 | final nextChainKey = rootKeyChainKeyPair.$2; 237 | 238 | expect(rootKey.getKeyBytes(), rootKeySeed); 239 | // TODO 240 | // expect(nextRootKey.getKeyBytes(), nextRoot); 241 | // expect(nextChainKey.getKey(), nextChain); 242 | }); 243 | } 244 | -------------------------------------------------------------------------------- /test/state/impl/in_memory_identity_key_store_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; 2 | import 'package:libsignal_protocol_dart/src/identity_key.dart'; 3 | import 'package:libsignal_protocol_dart/src/identity_key_pair.dart'; 4 | import 'package:libsignal_protocol_dart/src/signal_protocol_address.dart'; 5 | import 'package:libsignal_protocol_dart/src/state/impl/in_memory_identity_key_store.dart'; 6 | import 'package:libsignal_protocol_dart/src/util/key_helper.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | void main() { 10 | test('should implement interface successfully', () async { 11 | final keyPair = Curve.generateKeyPair(); 12 | final identityKey = IdentityKey(keyPair.publicKey); 13 | final identityKeyPair = IdentityKeyPair(identityKey, keyPair.privateKey); 14 | final registrationId = generateRegistrationId(false); 15 | final store = InMemoryIdentityKeyStore(identityKeyPair, registrationId); 16 | const address = SignalProtocolAddress('address-1', 123); 17 | 18 | // getIdentityKeyPair 19 | expect(await store.getIdentityKeyPair(), identityKeyPair); 20 | 21 | // getLocalRegistrationId 22 | expect(await store.getLocalRegistrationId(), registrationId); 23 | 24 | // getIdentity 25 | // TODO 26 | // expect(store.getIdentity(address), null); 27 | 28 | // saveIdentity & getIdentity 29 | expect(await store.saveIdentity(address, identityKey), true); 30 | expect( 31 | await store.getIdentity(address).then((value) => value?.getFingerprint()), 32 | identityKey.getFingerprint(), 33 | ); 34 | expect(await store.saveIdentity(address, identityKey), false); 35 | 36 | // isTrustedIdentity 37 | expect(await store.isTrustedIdentity(address, identityKey, null), true); 38 | // expect(store.isTrustedIdentity(null, identityKey, null), true); 39 | final newIdentityKey = IdentityKey(Curve.generateKeyPair().publicKey); 40 | expect(await store.isTrustedIdentity(address, newIdentityKey, null), false); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/state/impl/in_memory_pre_key_store_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('should implement interface successfully', () async { 6 | final store = InMemoryPreKeyStore(); 7 | final preKeys = generatePreKeys(1, 2); 8 | 9 | // storePreKey 10 | await store.storePreKey(1, preKeys[0]); 11 | await store.storePreKey(2, preKeys[1]); 12 | 13 | // containsPreKey 14 | expect(await store.containsPreKey(1), true); 15 | expect(await store.containsPreKey(2), true); 16 | expect(await store.containsPreKey(3), false); 17 | 18 | // loadPreKey 19 | expect(await store.loadPreKey(2).then((value) => value.serialize()), 20 | preKeys[1].serialize()); 21 | 22 | // removePreKey & loadPreKey 23 | await store.removePreKey(2); 24 | expect(() => store.loadPreKey(2), throwsA(isA())); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /test/state/impl/in_memory_session_store_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('should implement interface successfully', () async { 7 | const address1 = SignalProtocolAddress('address-1', 123); 8 | const address2a = SignalProtocolAddress('address-2', 123); 9 | const address2b = SignalProtocolAddress('address-2', 456); 10 | final store = InMemorySessionStore(); 11 | 12 | // containsSession & loadSession 13 | expect(await store.containsSession(address1), false); 14 | final sessionRecord1 = await store.loadSession(address1); 15 | await store.storeSession(address1, sessionRecord1); 16 | expect(await store.containsSession(address1), true); 17 | 18 | // loadSession & storeSession 19 | final sessionRecord2 = await store.loadSession(address1); 20 | await store.storeSession(address2a, sessionRecord2); 21 | await store.storeSession(address2b, sessionRecord2); 22 | 23 | // getSubDeviceSessions 24 | final subDeviceSessions1 = 25 | await store.getSubDeviceSessions(address1.getName()); 26 | expect(subDeviceSessions1.length, 1); 27 | expect(subDeviceSessions1, [123]); 28 | final subDeviceSessions2 = 29 | await store.getSubDeviceSessions(address2a.getName()); 30 | expect(subDeviceSessions2.length, 2); 31 | expect( 32 | const SetEquality().equals( 33 | subDeviceSessions2.toSet(), 34 | {123, 456}.toSet(), 35 | ), 36 | true); 37 | 38 | // deleteSession & containsSession 39 | expect(await store.containsSession(address2a), true); 40 | await store.deleteSession(address2a); 41 | expect(await store.containsSession(address2a), false); 42 | 43 | // deleteAllSessions & containsSession 44 | expect(await store.containsSession(address2b), true); 45 | await store.deleteAllSessions(address2b.getName()); 46 | expect(await store.containsSession(address2b), false); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/state/impl/in_memory_signal_protocol_store_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'package:fixnum/fixnum.dart'; 4 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import '../../test_in_memory_signal_protocol_store.dart'; 8 | 9 | void main() { 10 | test('should encrypt/decrypt message from Alice, to Bob and back again', 11 | () async { 12 | final aliceStore = TestInMemorySignalProtocolStore(); 13 | final bobStore = TestInMemorySignalProtocolStore(); 14 | const msgOrig = "L'homme est condamné à être libre"; 15 | 16 | const aliceAddress = SignalProtocolAddress('alice', 1); 17 | const bobAddress = SignalProtocolAddress('bob', 1); 18 | 19 | // Bob creates his keys to store locally and to publish public to cloud 20 | final bobPreKeyPair = Curve.generateKeyPair(); 21 | final bobSignedPreKeyPair = Curve.generateKeyPair(); 22 | final bobSignedPreKeySignature = Curve.calculateSignature( 23 | await bobStore 24 | .getIdentityKeyPair() 25 | .then((value) => value.getPrivateKey()), 26 | bobSignedPreKeyPair.publicKey.serialize(), 27 | ); 28 | 29 | // 30 | // Alice to encrypt and send to Bob... 31 | // 32 | 33 | // Pretend that Alice has downloaded Bob's pre key bundle from the cloud 34 | final bobPreKey = PreKeyBundle( 35 | await bobStore.getLocalRegistrationId(), 36 | 1, 37 | 31337, 38 | bobPreKeyPair.publicKey, 39 | 22, 40 | bobSignedPreKeyPair.publicKey, 41 | bobSignedPreKeySignature, 42 | await bobStore.getIdentityKeyPair().then((value) => value.getPublicKey()), 43 | ); 44 | 45 | await SessionBuilder.fromSignalStore(aliceStore, bobAddress) 46 | .processPreKeyBundle(bobPreKey); 47 | 48 | final aliceSessionCipher = SessionCipher.fromStore(aliceStore, bobAddress); 49 | final msgAliceToBob = await aliceSessionCipher 50 | .encrypt(Uint8List.fromList(utf8.encode(msgOrig))); 51 | 52 | // Pretend that Alice has now sent the message to Bob 53 | 54 | // 55 | // Bob to decrypt... 56 | // 57 | 58 | await bobStore.storePreKey( 59 | 31337, 60 | PreKeyRecord(bobPreKey.getPreKeyId()!, bobPreKeyPair), 61 | ); 62 | await bobStore.storeSignedPreKey( 63 | 22, 64 | SignedPreKeyRecord( 65 | 22, 66 | Int64(DateTime.now().millisecondsSinceEpoch), 67 | bobSignedPreKeyPair, 68 | bobSignedPreKeySignature, 69 | ), 70 | ); 71 | 72 | final msgIn = PreKeySignalMessage(msgAliceToBob.serialize()); 73 | expect( 74 | msgIn.getType(), 75 | CiphertextMessage.prekeyType, 76 | ); 77 | 78 | final bobSessionCipher = SessionCipher.fromStore(bobStore, aliceAddress); 79 | var msgDecrypted = await bobSessionCipher.decrypt(msgIn); 80 | var msgDecoded = utf8.decode(msgDecrypted, allowMalformed: true); 81 | expect(msgDecoded, msgOrig); 82 | 83 | // 84 | // Bob to encrypt and send to Alice... 85 | // 86 | 87 | final msgBobToAlice = await bobSessionCipher 88 | .encrypt(Uint8List.fromList(utf8.encode(msgDecoded))); 89 | expect( 90 | msgBobToAlice.getType(), 91 | CiphertextMessage.whisperType, 92 | ); 93 | 94 | // 95 | // Alice to decrypt... 96 | // 97 | 98 | msgDecrypted = await aliceSessionCipher.decryptFromSignal( 99 | SignalMessage.fromSerialized(msgBobToAlice.serialize()), 100 | ); 101 | msgDecoded = utf8.decode(msgDecrypted, allowMalformed: true); 102 | expect(msgDecoded, msgOrig); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /test/state/impl/in_memory_signed_pre_key_store_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('should implement interface successfully', () async { 6 | final store = InMemorySignedPreKeyStore(); 7 | final signedPreKeyRecord1 = _generateSignedPreKey(1); 8 | final signedPreKeyRecord2 = _generateSignedPreKey(2); 9 | 10 | // storeSignedPreKey 11 | await store.storeSignedPreKey(1, signedPreKeyRecord1); 12 | await store.storeSignedPreKey(2, signedPreKeyRecord2); 13 | 14 | // containsSignedPreKey 15 | expect(await store.containsSignedPreKey(1), true); 16 | expect(await store.containsSignedPreKey(2), true); 17 | expect(await store.containsSignedPreKey(3), false); 18 | 19 | // loadSignedPreKey 20 | expect(await store.loadSignedPreKey(1).then((value) => value.id), 1); 21 | expect(await store.loadSignedPreKey(2).then((value) => value.id), 2); 22 | 23 | // loadSignedPreKey 24 | expect(() => store.loadSignedPreKey(10), 25 | throwsA(isA())); 26 | 27 | // loadSignedPreKeys 28 | final signedPreKeys = await store.loadSignedPreKeys(); 29 | expect(signedPreKeys.length, 2); 30 | expect(signedPreKeys[0].id, 1); 31 | expect(signedPreKeys[1].id, 2); 32 | 33 | // removeSignedPreKey & containsSignedPreKey 34 | await store.removeSignedPreKey(1); 35 | expect(await store.containsSignedPreKey(1), false); 36 | expect(await store.containsSignedPreKey(2), true); 37 | }); 38 | } 39 | 40 | SignedPreKeyRecord _generateSignedPreKey(int signedPreKeyId) => 41 | generateSignedPreKey(generateIdentityKeyPair(), signedPreKeyId); 42 | -------------------------------------------------------------------------------- /test/test_in_memory_identity_key_store.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; 2 | import 'package:libsignal_protocol_dart/src/identity_key.dart'; 3 | import 'package:libsignal_protocol_dart/src/identity_key_pair.dart'; 4 | import 'package:libsignal_protocol_dart/src/state/impl/in_memory_identity_key_store.dart'; 5 | import 'package:libsignal_protocol_dart/src/util/key_helper.dart'; 6 | 7 | class TestInMemoryIdentityKeyStore extends InMemoryIdentityKeyStore { 8 | TestInMemoryIdentityKeyStore() 9 | : super(_generateIdentityKeyPair(), _generateRegistrationId()); 10 | 11 | static IdentityKeyPair _generateIdentityKeyPair() { 12 | final identityKeyPairKeys = Curve.generateKeyPair(); 13 | 14 | return IdentityKeyPair(IdentityKey(identityKeyPairKeys.publicKey), 15 | identityKeyPairKeys.privateKey); 16 | } 17 | 18 | static int _generateRegistrationId() => generateRegistrationId(false); 19 | } 20 | -------------------------------------------------------------------------------- /test/test_in_memory_signal_protocol_store.dart: -------------------------------------------------------------------------------- 1 | import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; 2 | import 'package:libsignal_protocol_dart/src/identity_key.dart'; 3 | import 'package:libsignal_protocol_dart/src/identity_key_pair.dart'; 4 | import 'package:libsignal_protocol_dart/src/state/impl/in_memory_signal_protocol_store.dart'; 5 | import 'package:libsignal_protocol_dart/src/util/key_helper.dart'; 6 | 7 | class TestInMemorySignalProtocolStore extends InMemorySignalProtocolStore { 8 | TestInMemorySignalProtocolStore() 9 | : super(_generateIdentityKeyPair(), _generateRegistrationId()); 10 | 11 | static IdentityKeyPair _generateIdentityKeyPair() { 12 | final identityKeyPairKeys = Curve.generateKeyPair(); 13 | 14 | return IdentityKeyPair(IdentityKey(identityKeyPairKeys.publicKey), 15 | identityKeyPairKeys.privateKey); 16 | } 17 | 18 | static int _generateRegistrationId() => generateRegistrationId(false); 19 | } 20 | -------------------------------------------------------------------------------- /test/util/byte_util_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:libsignal_protocol_dart/src/util/byte_util.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | test('ByteUtil.combime() combile multi Uintlist8 into 1', () { 8 | final first = Uint8List.fromList([1]); 9 | final second = Uint8List.fromList([2, 3]); 10 | final third = Uint8List.fromList([4, 5]); 11 | final other = Uint8List.fromList([1, 2, 3, 4, 5]); 12 | expect(ByteUtil.combine([first, second, third]), other); 13 | }); 14 | 15 | test('ByteUtil.shortToByteArray() convert short to UintList8', () { 16 | const value1 = 100; 17 | expect(ByteUtil.shortToByteArray(value1), Uint8List.fromList([0, 100])); 18 | const value2 = 1024; 19 | expect(ByteUtil.shortToByteArray(value2), Uint8List.fromList([4, 1024])); 20 | }); 21 | 22 | test('ByteUtil.intToByteArray() convert int to UintList8', () { 23 | const value1 = 100; 24 | expect(ByteUtil.intToByteArray(value1), Uint8List.fromList([0, 0, 0, 100])); 25 | const value2 = 1024; 26 | expect(ByteUtil.intToByteArray(value2), Uint8List.fromList([0, 0, 4, 0])); 27 | }); 28 | 29 | test('ByteUtil.trim() sublist Uint8List from 0 to specification length', () { 30 | final input = Uint8List.fromList([1, 2, 3, 4, 5]); 31 | expect(ByteUtil.trim(input, 3), Uint8List.fromList([1, 2, 3])); 32 | expect(ByteUtil.trim(input, 0), Uint8List.fromList([])); 33 | }); 34 | 35 | test('ByteUtil.split() splits the string to three length', () { 36 | final input = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 37 | final first = Uint8List.fromList([1]); 38 | final second = Uint8List.fromList([2, 3]); 39 | final third = Uint8List.fromList([4, 5, 6, 7, 8, 9]); 40 | expect(ByteUtil.split(input, 1, 2, 6), [first, second, third]); 41 | }); 42 | 43 | test('ByteUtil.splitTwo() splits the string to two length', () { 44 | final input = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 45 | final first = Uint8List.fromList([1]); 46 | final second = Uint8List.fromList([2, 3, 4, 5, 6, 7]); 47 | expect(ByteUtil.splitTwo(input, 1, 6), [first, second]); 48 | }); 49 | 50 | test('String.trim() removes surrounding whitespace', () { 51 | const string = ' foo '; 52 | expect(string.trim(), equals('foo')); 53 | }); 54 | 55 | test('ByteUtil.intsToByteHighAndLow()', () { 56 | const highValue1 = 4; 57 | const lowValue1 = 2; 58 | expect(ByteUtil.intsToByteHighAndLow(highValue1, lowValue1), 66); 59 | const highValue2 = 2; 60 | const lowValue2 = 4; 61 | expect(ByteUtil.intsToByteHighAndLow(highValue2, lowValue2), 36); 62 | const highValue3 = 3; 63 | const lowValue3 = 3; 64 | expect(ByteUtil.intsToByteHighAndLow(highValue3, lowValue3), 51); 65 | }); 66 | 67 | test('ByteUtil.highBitsToInt', () { 68 | const int1 = 16; 69 | expect(ByteUtil.highBitsToInt(int1), 1); 70 | const int2 = 35; 71 | expect(ByteUtil.highBitsToInt(int2), 2); 72 | const int3 = 100; 73 | expect(ByteUtil.highBitsToInt(int3), 6); 74 | }); 75 | 76 | test('ByteUtil.lowBitsToInt', () { 77 | const int1 = 16; 78 | expect(ByteUtil.lowBitsToInt(int1), 0); 79 | const int2 = 35; 80 | expect(ByteUtil.lowBitsToInt(int2), 3); 81 | const int3 = 100; 82 | expect(ByteUtil.lowBitsToInt(int3), 4); 83 | }); 84 | 85 | test('ByteUtil.highBitsToMedium', () { 86 | const int1 = 16; 87 | expect(ByteUtil.highBitsToMedium(int1), 0); 88 | const int2 = 35; 89 | expect(ByteUtil.highBitsToMedium(int2), 0); 90 | const int3 = 100; 91 | expect(ByteUtil.highBitsToMedium(int3), 0); 92 | const int4 = 10000; 93 | expect(ByteUtil.highBitsToMedium(int4), 2); 94 | }); 95 | 96 | test('ByteUtil.lowBitsToMedium', () { 97 | const int1 = 16; 98 | expect(ByteUtil.lowBitsToMedium(int1), 16); 99 | const int2 = 35; 100 | expect(ByteUtil.lowBitsToMedium(int2), 35); 101 | const int3 = 100; 102 | expect(ByteUtil.lowBitsToMedium(int3), 100); 103 | const int4 = 10000; 104 | expect(ByteUtil.lowBitsToMedium(int4), 1808); 105 | }); 106 | 107 | test('ByteUtil.byteArray5ToLong', () { 108 | // ignore: unused_local_variable 109 | final input = Uint8List.fromList( 110 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 111 | // TODO 112 | }); 113 | } 114 | --------------------------------------------------------------------------------