{"id":21165838,"url":"https://github.com/ivrodriguezca/nuntius","last_synced_at":"2025-07-09T17:32:40.980Z","repository":{"id":56932909,"uuid":"99489285","full_name":"ivRodriguezCA/nuntius","owner":"ivRodriguezCA","description":"iOS Framework for end-to-end encrypted messages","archived":false,"fork":false,"pushed_at":"2017-09-27T11:59:09.000Z","size":1730,"stargazers_count":21,"open_issues_count":3,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-30T00:55:07.667Z","etag":null,"topics":["cryptography","diffie-hellman","ecdh","ios","library","libsodium","objcective-c"],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ivRodriguezCA.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-06T13:45:51.000Z","updated_at":"2024-10-21T22:44:05.000Z","dependencies_parsed_at":"2022-08-21T05:20:43.721Z","dependency_job_id":null,"html_url":"https://github.com/ivRodriguezCA/nuntius","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivRodriguezCA%2Fnuntius","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivRodriguezCA%2Fnuntius/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivRodriguezCA%2Fnuntius/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ivRodriguezCA%2Fnuntius/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ivRodriguezCA","download_url":"https://codeload.github.com/ivRodriguezCA/nuntius/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225578174,"owners_count":17491268,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","diffie-hellman","ecdh","ios","library","libsodium","objcective-c"],"created_at":"2024-11-20T14:46:55.370Z","updated_at":"2024-11-20T14:46:56.177Z","avatar_url":"https://github.com/ivRodriguezCA.png","language":"Objective-C","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/ivRodriguezCA/nuntius.svg?branch=master)](https://travis-ci.org/ivRodriguezCA/nuntius)\n[![CocoaPods](https://img.shields.io/cocoapods/v/nuntius.svg)](https://cocoapods.org/pods/nuntius)\n\n# [β] nuntius\nnuntius is an iOS framework that helps iOS developers integrate [end-to-end encryption (e2ee)](https://en.wikipedia.org/wiki/End-to-end_encryption) into their apps with simple APIs. It provides an objc implementation of the Extended Triple Diffie-Hellman (X3DH) and Double Ratchet protocols using [libsodium](https://github.com/jedisct1/libsodium) for most of the crypto operations. nuntius provides Authenticated Encryption with Associated Data (AEAD) via AES-CBC-HMAC-256, it uses Apple's CommonCrypto framework for these operations, but in the future I'll move to libsodium-only crypto and use [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/Poly1305) instead.\n\n## Extended Triple Diffie-Hellman (X3DH)\nAs described [here](https://whispersystems.org/docs/specifications/x3dh/), X3DH is a key agreement protocol that establishes a shared *session* key between two parties that mutually authenticate each other based on public keys. `nuntius` uses:\n- [Curve25519](https://cr.yp.to/ecdh.html) for elliptic curve public key cryptography [(ECDH)](https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman)\n- [Ed25519](https://ed25519.cr.yp.to/) for public-key signatures\n- [SHA256](https://en.wikipedia.org/wiki/SHA-2) hashing algorithm\n- [BLAKE2b](https://blake2.net/) as [KDF](https://en.wikipedia.org/wiki/Key_derivation_function) for key derivation\n\n## Double Ratchet\nAs described [here](https://whispersystems.org/docs/specifications/doubleratchet/), the Double Ratchet protocol is used after a shared *session* key is established between two parties (for example with X3DH) to send and receive encrypted messages. It provides [forward secrecy (FS)](https://en.wikipedia.org/wiki/Forward_secrecy) by deriving new encryption keys after every Double Ratchet message, meaning that if an encryption key is compromised, it cannot be used to decrypt old messages. It provides a symmetric encryption key ratachet and a Diffie-Hellman public key encryption ratachet, this is why is called Double Ratchet. `nuntius` uses:\n- [Curve25519](https://cr.yp.to/ecdh.html) for elliptic curve public key cryptography [(ECDH)](https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman)\n- [BLAKE2b](https://blake2.net/) as [KDF](https://en.wikipedia.org/wiki/Key_derivation_function) for key derivation\n- [AES-256](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) in [CBC mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29) with PKCS#7 padding for symmectric encryption\n- [HMAC-256](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) for Authenticated Encryption\n\n## Importing\n\n### Cocoapods\n```sh\npod \"nuntius\"\n```\nIn Objc import the nuntius header\n```Objc\n#import \u003cnuntius/nuntius.h\u003e\n```\nIn Swift import the nuntius module\n```Swift\nimport nuntius\n```\n\n## Usage\n### Generate Curve25519 key pairs\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\nIRCurve25519KeyPair *keyPair = [IREncryptionService generateKeyPair];\nNSLog(@\"Public Key: %@\", keyPair.publicKey);\nNSLog(@\"Private Key: %@\", keyPair.privateKey);\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\nlet keyPair = IREncryptionService.generateKeyPair()!\nprint(\"Public Key: \\(keyPair.publicKey)\")\nprint(\"Private Key: \\(keyPair.privateKey)\")\n```\n### Sign Curve25519 public key\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\nIRCurve25519KeyPair *signingKeyPair = [IREncryptionService generateKeyPair];\nIRCurve25519KeyPair *signedKeyPair = [IREncryptionService generateKeyPair];\nNSData *signature = [IREncryptionService signData:signedKeyPair.publicKey withKeyPair:signingKeyPair];\n[signedKeyPair addKeyPairSignature:signature];\nNSLog(@\"Signature: %@\", signedKeyPair.signature);\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\nlet signingKeyPair = IREncryptionService.generateKeyPair()!\nlet signedKeyPair = IREncryptionService.generateKeyPair()!\nguard let signature = IREncryptionService.sign(signedKeyPair.publicKey, with: signingKeyPair) else {\n    return\n}\nsignedKeyPair.addSignature(signature)\nprint(\"Signature: \\(signedKeyPair.signature!)\")\n```\n### Verify Curve25519 Signature\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\nIRCurve25519KeyPair *signingKeyPair = //get signing key pair\nIRCurve25519KeyPair *signedKeyPair = //get signed key pair\nBOOL valid = [IREncryptionService verifySignature:signedKeyPair.signature ofRawData:signedKeyPair.publicKey withKeyPair:signingKeyPair];\nif (valid) {\n    NSLog(@\"Valid signature\");\n} else {\n    NSLog(@\"Invalid signature\");\n}\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\nlet signingKeyPair = //get signing key pair\nlet signedKeyPair = //get signed key pair\nlet valid = IREncryptionService.verifySignature(signedKeyPair.signature!, ofRawData: signedKeyPair.publicKey, with: signingKeyPair)\nif valid {\n    print(\"Valid signature\")\n} else {\n    print(\"Invalid signature\")\n}\n```\n### Generate shared secret from 2 Curve25519 keys (ECDH)\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\n//Alice is the sender\nIRCurve25519KeyPair *aliceKeyPair = [IREncryptionService generateKeyPair];\n//Bob is the receiver\nIRCurve25519KeyPair *bobKeyPair = [IREncryptionService generateKeyPair];\nNSData *sharedSecretAlice = [IREncryptionService senderSharedKeyWithRecieverPublicKey:bobKeyPair.publicKey andSenderKeyPair:aliceKeyPair];\nNSData *sharedSecretBob = [IREncryptionService receiverSharedKeyWithSenderPublicKey:aliceKeyPair.publicKey andReceiverKeyPair:bobKeyPair];\nif ([sharedSecretAlice isEqualToData:sharedSecretBob]) {\n    NSLog(@\"Success!\");\n} else {\n    NSLog(@\"Error!\");\n}\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\n//Alice is the sender\nlet aliceKeyPair = IREncryptionService.generateKeyPair()!\n//Bob is the receiver\nlet bobKeyPair = IREncryptionService.generateKeyPair()!\nlet sharedSecretAlice = IREncryptionService.senderSharedKey(withRecieverPublicKey: bobKeyPair.publicKey, andSenderKeyPair: aliceKeyPair)\nlet sharedSecretBob = IREncryptionService.receiverSharedKey(withSenderPublicKey: aliceKeyPair.publicKey, andReceiverKeyPair: bobKeyPair)\nguard sharedSecretAlice != nil, sharedSecretBob != nil, sharedSecretAlice == sharedSecretBob else {\n    return\n}\nprint(\"Success!\")\n```\n### Derive Keys\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\nNSData *sharedSecret = //some shared secret, for example ECDH(alice.privateKey,bob.publicKey)\n/* MIN outputLength: 16, MAX outputLength: 64, Salt: key id */\nNSData *key1 = [IREncryptionService sharedKeyKDFWithSecret:sharedSecret andSalt:1 outputLength:32];\nNSData *key2 = [IREncryptionService sharedKeyKDFWithSecret:sharedSecret andSalt:2 outputLength:64];\nNSLog(@\"Derived Key1: %@\", key1);\nNSLog(@\"Derived Key2: %@\", key2);\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\nlet sharedSecret = //some shared secret, for example ECDH(alice.privateKey,bob.publicKey)\n/* MIN outputLength: 16, MAX outputLength: 64, Salt: key id */\nlet key1 = IREncryptionService.sharedKeyKDF(withSecret: sharedSecret, andSalt: 1, outputLength: 32)\nlet key2 = IREncryptionService.sharedKeyKDF(withSecret: sharedSecret, andSalt: 2, outputLength: 64)\nprint(\"Key 1 \\(key1!)\")\nprint(\"Key 2 \\(key2!)\")\n```\nThere is also:\n```Objc\n- (NSData * _Nullable)rootKeyKDFWithSecret:(NSData * _Nonnull)secret\n                                   andSalt:(uint64_t)salt\n                              outputLength:(NSUInteger)outputLength;\n- (NSData * _Nullable)chainKeyKDFWithSecret:(NSData * _Nonnull)secret\n                                    andSalt:(uint64_t)salt\n                               outputLength:(NSUInteger)outputLength;\n- (NSData * _Nullable)messageKeyKDFWithSecret:(NSData * _Nonnull)secret\n                                      andSalt:(uint64_t)salt\n                                 outputLength:(NSUInteger)outputLength;\n```\nAll of these methods can be used for deriving keys, they are convenience methods for the Double Ratchet protocol. Internally they all use the same method but with different `ctx` (one of libsodium's `crypto_kdf_derive_from_key` parameter)\n### Encrypt and Decrypt\nObjc\n```Objc\nIREncryptionService *IREncryptionService = [IREncryptionService new];\nNSData *data = [@\"my-secret-data\" dataUsingEncoding:NSUTF8StringEncoding];\nNSData *aesKey = //Random AES key, for example key = KDF(sharedSecret, 1, 32)\nNSData *hmacKey = //Random HMAC key, for example key = KDF(sharedSecret, 2, 32)\nNSData *iv = //Random IV, for example key = KDF(sharedSecret, 3, 16)\nNSMutableData *ratchetData = [NSMutableData new];\n//Add Sender Ratchet Public Key\n[ratchetData appendData:senderKeyPair.publicKey];\n//Add Number of Sent Messages\nconst char numberOfSentMessages[1] = {0x0};\n[ratchetData appendBytes:numberOfSentMessages length:sizeof(numberOfSentMessages)];\n//Add Number of Sent Messages in Previous Chain\nconst char numberOfSentMessagesInPreviousChain[1] = {0x0};\n[ratchetData appendBytes:numberOfSentMessagesInPreviousChain length:sizeof(numberOfSentMessagesInPreviousChain)];\nNSError *error = nil;\nNSData *ciphertext = [IREncryptionService aeEncryptData:data: symmetricKey:aesKey hmacKey:hmacKey iv:iv ratchetHeader:ratchetData error:\u0026error];\nif (error == nil) {\n    NSLog(@\"Encrypted data: %@\", ciphertext);\n    //Decrypting\n    NSError *decryptionError = nil;\n    NSData *plaintext = [IREncryptionService aeDecryptData:ciphertext symmetricKey:aesKey hmacKey:hmacKey iv:iv error:\u0026decryptionError];\n    if (decryptionError == nil) {\n        NSLog(@\"Plaintext: %@\",plaintext);\n    } else {\n        NSLog(@\"Error: %@\", decryptionError);\n    }\n    \n} else {\n    NSLog(@\"Error: %@\", error);\n}\n```\nSwift\n```Swift\nlet IREncryptionService = IREncryptionService()\nlet data = \"my-secret-data\".data(using: .utf8)!\nlet aesKey = //Random AES key, for example key = KDF(sharedSecret, 1, 32)\nlet hmacKey = //Random HMAC key, for example key = KDF(sharedSecret, 2, 32)\nlet iv = //Random IV, for example key = KDF(sharedSecret, 3, 16)\nvar ratchetData = Data()\n//Add Sender Ratchet Public Key\nratchetData.append(aliceKeyPair.publicKey)\n//Add Number of Sent Messages\nlet numberOfSentMessages: Int32 = 0x0\nvar beNumberOfSentMessages = numberOfSentMessages.bigEndian\nratchetData.append(UnsafeBufferPointer(start: \u0026beNumberOfSentMessages, count: 1))\n//Add Number of Sent Messages in Previous Chain\nlet numberOfSentMessagesInPreviousChain: Int32 = 0x0\nvar beNumberOfSentMessagesInPreviousChain = numberOfSentMessagesInPreviousChain.bigEndian\nratchetData.append(UnsafeBufferPointer(start: \u0026beNumberOfSentMessagesInPreviousChain, count: 1))\ndo {\n    let ciphertext = try IREncryptionService.aeEncryptData(data, symmetricKey: aesKey, hmacKey: hmacKey, iv: iv, ratchetHeader: ratchetData)\n    print(\"Encrypted data: \\(ciphertext)\")\n    //Decrypting\n    let plaintext = try IREncryptionService.aeDecryptData(ciphertext, symmetricKey: aesKey, hmacKey: hmacKey, iv: iv)\n    print(\"Plaintext: \\(plaintext)\")\n} catch {\n    print(\"Error \\(error)\")\n}\n```\nYou can read more about what the `Ratchet Header` is and why is it needed [here](https://whispersystems.org/docs/specifications/doubleratchet/). For encrypting/decrypting data outside the Double Ratchet protocol there is also:\n```Objc\n- (NSData * _Nullable)aeEncryptSimpleData:(NSData * _Nonnull)plaintextData\n                             symmetricKey:(NSData * _Nonnull)symmetricKey\n                                  hmacKey:(NSData * _Nonnull)hmacKey\n                                       iv:(NSData * _Nonnull)iv\n                                    error:(NSError * _Nullable * _Nullable)error;\n\n- (NSData * _Nullable)aeDecryptSimpleData:(NSData * _Nonnull)cipherData\n                             symmetricKey:(NSData * _Nonnull)symmetricKey\n                                  hmacKey:(NSData * _Nonnull)hmacKey\n                                       iv:(NSData * _Nonnull)iv\n                                    error:(NSError * _Nullable * _Nullable)error;\n```\n### Double Ratchet: setup, send and receive messages\nObjc\n```Objc\n//Alice is the sender and Bob is the receiver\nIRDoubleRatchetService *aliceDoubleRatchet = [IRDoubleRatchetService new];\nNSData *aliceSharedSecret = //some shared secret, for example ECDH(alice.privateKey,bob.publicKey)\n[aliceDoubleRatchet setupRatchetForSendingWithSharedKey:aliceSharedSecret andDHReceiverKey:bobKeyPair];\n\nIRDoubleRatchetService *bobDoubleRatchet = [IRDoubleRatchetService new];\nNSData *bobSharedSecret = //some shared secret, for example ECDH(bob.privateKey,alice.publicKey)\n[bobDoubleRatchet setupRatchetForReceivingWithSharedKey:bobSharedSecret andDHSenderKey:alice.signedPreKeyPair];\n\n//Sending messages\nNSData *message = //some message\nNSError *error = nil;\nNSData *ciphertext = [aliceDoubleRatchet encryptData:message error:\u0026error];\nif (error == nil) {\n    NSLog(@\"Ready to send message: %@\", ciphertext);\n} else {\n    NSLog(@\"Error %@\",error);\n}\n\n//Receiving message\nNSData *plaintext = [bobDoubleRatchet decryptData:ciphertext error:\u0026error];\nif (error == nil) {\n    NSLog(@\"Message received: %@\", plaintext);\n} else {\n    NSLog(@\"Error %@\",error);\n}\n```\nSwift\n```Swift\n//Alice is the sender and Bob is the receiver\nlet aliceDoubleRatchet = IRDoubleRatchetService()\nlet aliceSharedSecret = //some shared secret, for example ECDH(alice.privateKey,bob.publicKey)\naliceDoubleRatchet.setupRatchetForSending(withSharedKey: aliceSharedSecret, andDHReceiverKey: bobKeyPair)\n\nlet bobDoubleRatchet = IRDoubleRatchetService()\nlet bobSharedSecret = //some shared secret, for example ECDH(bob.privateKey,alice.publicKey)\nbobDoubleRatchet.setupRatchetForReceiving(withSharedKey: bobSharedSecret, andDHSenderKey: alice.signedPreKeyPair)\n\n//Sending messages\nlet message = //some message\ndo {\n    let ciphertext = try aliceDoubleRatchet.encryptData(message)\n    print(\"Ready to send message: \\(ciphertext)\")\n\n    //Receiving message\n    let plaintext = try bobDoubleRatchet.decryptData(ciphertext)\n    print(\"Message received \\(plaintext)\")\n} catch {\n    print(\"Error \\(error)\")\n}\n\n```\n### Double Ratchet: setup from stored state\nObjc\n```Objc\nNSDictionary\u003cNSString *, NSString *\u003e *state = [someDoubleRatchet doubleRatchetState];\n//store state in local encrypted db\n\nIRDoubleRatchetService *doubleRatchet = [IRDoubleRatchetService new];\nNSDictionary\u003cNSString *, NSString *\u003e *state = //get double ratchet state from local encrypted DB\n[doubleRatchet setupWithRatchetState:state];\n```\nSwift\n```Swift\nlet state = someDoubleRatchet.doubleRatchetState()\n//store state in local encrypted db\n\nlet doubleRatchet = IRDoubleRatchetService()\nlet state = //get double ratchet state from local encrypted DB\ndoubleRatchet.setup(withRatchetState: state)\n``` \n### Triple Diffie-Hellman: setup, shared secret generation\nObjc\n```Objc\nIRCurve25519KeyPair *identityKeyPair = //generate IRCurve25519KeyPair\nIRCurve25519KeyPair *signedPreKeyPair = //generate IRCurve25519KeyPair\nNSArray\u003cIRCurve25519KeyPair *\u003e *ephemeralKeyPairs = //generate \"X\" IRCurve25519KeyPairs\nIRTripleDHService *tripleDH = [[IRTripleDHService alloc] initWithIdentityKeyPair:identityKeyPair signedPreKeyPair:signedPreKeyPair ephemeralKeys:ephemeralKeyPairs];\n\n//When sending a message\nIRCurve25519KeyPair *rIdentityKey = //get receiver's identity key from server or db\nIRCurve25519KeyPair *rSignedPreKey = //get receiver's signed pre-key from server or db\nIRCurve25519KeyPair *rEphemeralKey = //get one of the receiver's ephemeral keys from server\nNSData *sharedKey = [tripleDH sharedKeyFromReceiverIdentityKey:rIdentityKey receiverSignedPreKey:rSignedPreKey receiverEphemeralKey:rEphemeralKey];\n//Use sharedKey\n\n//When receiving a message\nIRCurve25519KeyPair *sIdentityKey = //get sender's identity key from server or db\nIRCurve25519KeyPair *sEphemeralKey = //get sender's signed pre-key from server or db\nNSString *rEphemeralKeyID = //get ephemeral key id from received message's heeader\nNSData *sharedKey = [tripleDH sharedKeyFromSenderIdentityKey:sIdentityKey senderEphemeralKey:sEphemeralKey receiverEphemeralKeyID:rEphemeralKeyID];\n//Use sharedKey\n\n```\nSwift\n```Swift\nlet identityKeyPair = //generate IRCurve25519KeyPair\nlet signedPreKeyPair = //generate IRCurve25519KeyPair\nlet ephemeralKeyPairs: Array\u003cIRCurve25519KeyPair\u003e = //generate \"X\" IRCurve25519KeyPairs\nlet tripleDH = IRTripleDHService(identityKeyPair: identityKeyPair, signedPreKeyPair: signedPreKeyPair, ephemeralKeys: ephemeralKeyPairs)\n\n//When sending a message\nlet rIdentityKey = //get receiver's identity key from server or db\nlet rSignedPreKey = //get receiver's signed pre-key from server or db\nlet rEphemeralKey = //get one of the receiver's ephemeral keys from server\nlet sharedKey = tripleDH.sharedKey(fromReceiverIdentityKey: rIdentityKey, receiverSignedPreKey: rSignedPreKey, receiverEphemeralKey: rEphemeralKey)\n//Use sharedKey\n\n//When receiving a message\nlet sIdentityKey = //get sender's identity key from server or db\nlet sEphemeralKey = //get sender's signed pre-key from server or db\nlet rEphemeralKeyID: String = //get ephemeral key id from received message's heeader\nlet sharedKey = tripleDH.sharedKey(fromSenderIdentityKey: sIdentityKey, senderEphemeralKey: sEphemeralKey, receiverEphemeralKeyID: rEphemeralKeyID)\n//Use sharedKey\n\n```\n\n## Contributions\nDo you want to contribute? awesome! I'd love to see some PRs opened here.\n\n## TODO\n- [X] Add Examples/Usage\n- [] Add Documentation\n- [] Create wiki\n- [X] Add project to Travis CI\n\n## Disclaimer\n- The Extended Triple Diffie-Hellman and Double Ratchet protocols' implementations where developed from scratch and do not share any source code with existing libraries.\n- This library has no relation and is not backed nor supported by the authors of the X3DH and Double Ratchet protocols.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivrodriguezca%2Fnuntius","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivrodriguezca%2Fnuntius","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivrodriguezca%2Fnuntius/lists"}