diff --git a/.gitmodules b/.gitmodules index 04c430b..b6ecc2e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,11 +6,6 @@ url = https://github.com/waku-org/nwaku.git ignore = untracked branch = master -[submodule "vendor/nim-sds"] - path = vendor/nim-sds - url = https://github.com/jazzz/nim-sds.git - ignore = untracked - branch = master [submodule "vendor/nim-protobuf-serialization"] path = vendor/nim-protobuf-serialization url = https://github.com/status-im/nim-protobuf-serialization.git @@ -26,26 +21,6 @@ url = https://github.com/narimiran/blake2.git ignore = untracked branch = master -[submodule "vendor/illwill"] - path = vendor/illwill - url = https://github.com/johnnovak/illwill.git - ignore = untracked - branch = master -[submodule "vendor/nim_chacha20_poly1305"] - path = vendor/nim_chacha20_poly1305 - url = https://github.com/lantos-lgtm/nim_chacha20_poly1305.git - ignore = untracked - branch = master -[submodule "vendor/constantine"] - path = vendor/constantine - url = https://github.com/mratsim/constantine.git - ignore = untracked - branch = master -[submodule "vendor/nim-ffi"] - path = vendor/nim-ffi - url = https://github.com/logos-messaging/nim-ffi.git - ignore = untracked - branch = master [submodule "vendor/libchat"] path = vendor/libchat url = https://github.com/logos-messaging/libchat.git diff --git a/protos/conversations/group_v1.proto b/protos/conversations/group_v1.proto deleted file mode 100644 index fde7013..0000000 --- a/protos/conversations/group_v1.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto3"; - -package wap.convos.group_v1; - -import "base.proto"; -import "common_frames.proto"; - - - -message ConversationInvite_GroupV1 { - repeated string participants = 1; -} - - - -message GroupV1Frame { - // SDS like information: Message ID and channel_id extracted for utility - string message_id = 2; - string channel_id = 3; // Channel_id is associated with a set of participants - // This conflicts with conversation based encryption, - // need to ensure the derived sender is a valid participant - base.ReliabilityInfo reliability_info = 10; - - oneof frame_type { - common_frames.ContentFrame content = 100; - // ... - } -} diff --git a/protos/encryption.proto b/protos/encryption.proto deleted file mode 100644 index cda0fb0..0000000 --- a/protos/encryption.proto +++ /dev/null @@ -1,26 +0,0 @@ -syntax = "proto3"; - -package wap.encryption; - - -// TODO: This also encompasses plaintexts, is there a better name? -// Alternatives: ??? -message EncryptedPayload { - - oneof encryption { - encryption.Plaintext plaintext = 1; - encryption.Doubleratchet doubleratchet = 2; - } -} - -message Plaintext { - bytes payload=1; -} - -message Doubleratchet { - bytes dh = 1; // 32 byte array - uint32 msgNum = 2; - uint32 prevChainLen = 3; - bytes ciphertext = 4; - string aux = 5; -} diff --git a/protos/envelope.proto b/protos/envelope.proto deleted file mode 100644 index 6a961d4..0000000 --- a/protos/envelope.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package wap.envelope; - - -/////////////////////////////////////////////////////////////////////////////// -// Payload Framing Messages -/////////////////////////////////////////////////////////////////////////////// - -message WapEnvelopeV1 { - - string conversation_hint = 1; - uint64 salt = 2; - - bytes payload = 5; -} diff --git a/protos/inbox.proto b/protos/inbox.proto deleted file mode 100644 index 438b977..0000000 --- a/protos/inbox.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; - -package wap.inbox; - -import "invite.proto"; - -message Note{ - string text = 1; -} - -message InboxV1Frame { - string recipient = 1; - oneof frame_type { - invite.InvitePrivateV1 invite_private_v1 = 10; - Note note = 11; - } -} diff --git a/protos/invite.proto b/protos/invite.proto deleted file mode 100644 index da9f560..0000000 --- a/protos/invite.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; - -package wap.invite; - -import "encryption.proto"; - -message InvitePrivateV1 { - bytes initiator = 1; - bytes initiator_ephemeral = 2; - bytes participant = 3; - int32 participant_ephemeral_id= 4; - string discriminator = 5; - encryption.EncryptedPayload initial_message = 6; -} diff --git a/protos/private_v1.proto b/protos/private_v1.proto deleted file mode 100644 index 7dd69ff..0000000 --- a/protos/private_v1.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; - -package wap.convos.private_v1; - - -message Placeholder { - uint32 counter = 1; -} - -message PrivateV1Frame { - string conversation_id = 1; - bytes sender = 2; - int64 timestamp = 3; // Sender reported timestamp - oneof frame_type { - bytes content = 10; - Placeholder placeholder = 11; - // .... - } -} diff --git a/protos/reliability.proto b/protos/reliability.proto deleted file mode 100644 index f71f9b2..0000000 --- a/protos/reliability.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; - -package wap.reliability; - -/////////////////////////////////////////////////////////////////////////////// -// SDS Payloads -/////////////////////////////////////////////////////////////////////////////// - -message HistoryEntry { - string message_id = 1; // Unique identifier of the SDS message, as defined in `Message` - bytes retrieval_hint = 2; // Optional information to help remote parties retrieve this SDS - // message; For example, A Waku deterministic message hash or routing payload hash - } - - message ReliablePayload { - string message_id = 2; - string channel_id = 3; - int32 lamport_timestamp = 10; - repeated HistoryEntry causal_history = 11; - bytes bloom_filter = 12; - // Optional field causes errors in nim protobuf generation. Removing for now as optional is implied anways. - bytes content = 20; - } diff --git a/src/naxolotl.nim b/src/naxolotl.nim deleted file mode 100644 index f19bbaf..0000000 --- a/src/naxolotl.nim +++ /dev/null @@ -1,7 +0,0 @@ -import naxolotl/[ - naxolotl, - curve25519, - errors -] - -export naxolotl, curve25519, NaxolotlError \ No newline at end of file diff --git a/src/naxolotl/chacha.nim b/src/naxolotl/chacha.nim deleted file mode 100644 index a20bab9..0000000 --- a/src/naxolotl/chacha.nim +++ /dev/null @@ -1,63 +0,0 @@ -import nim_chacha20_poly1305/[common, chacha20_poly1305, poly1305] -import std/[sysrand] -import results -import strformat - -import types -import errors - - -proc encryptWithChaCha20Poly1305*(msgKey: MessageKey, plaintext: var openArray[byte], associatedData: openArray[byte]) : (Nonce, CipherText) = - - var nonce : Nonce - discard urandom(nonce) - - var tag: Tag - var ciphertext = newSeq[byte](plaintext.len + tag.len) - - var counter : Counter = 0 - - # TODO: check plaintext mutability requirement - chacha20_aead_poly1305_encrypt( - Key(msgKey), - nonce, - counter, - associatedData, - plaintext, - ciphertext.toOpenArray(0, plaintext.high), - tag - ) - - # Combine tag with cipherkey for ease of transport and consistency with other implementations - copyMem(addr ciphertext[plaintext.len], unsafeAddr tag[0], tag.len) - (nonce, ciphertext) - - -proc decryptWithChaCha20Poly1305*(msgKey: MessageKey, nonce: Nonce, ciphertext: var openArray[byte], associatedData: openArray[byte]) : Result[seq[byte], NaxolotlError] = - var tag : Tag - if ciphertext.len <= tag.len: - return err(NaxolotlError(code: errInvalidInput, context: fmt"ciphertext is less than {tag.len} bytes. Expected `ciphertext || tag`" )) - - copyMem(addr tag[0], unsafeAddr ciphertext[^tag.len], tag.len) - - var plaintext = newSeq[byte](ciphertext.len - tag.len) - - var computedTag: Tag - var counter : Counter = 0 - - chacha20_aead_poly1305_decrypt( - Key(msgKey), - nonce, - counter, - associatedData, - plaintext, - ciphertext.toOpenArray(0,ciphertext.high - tag.len), - computedTag - ) - - if not poly1305_verify(tag, computedTag): - return err(NaxolotlError(code: errMessageAuthentication, context: fmt"Got Tag: {tag} expected: {computedTag}")) - - - ok(plaintext) - \ No newline at end of file diff --git a/src/naxolotl/curve25519.nim b/src/naxolotl/curve25519.nim deleted file mode 100644 index 140b928..0000000 --- a/src/naxolotl/curve25519.nim +++ /dev/null @@ -1,123 +0,0 @@ -# See https://github.com/vacp2p/nim-libp2p/blob/master/libp2p/crypto/curve25519.nim - -import bearssl/[ec, rand] -import results -from stew/assign2 import assign -export results - -const Curve25519KeySize* = 32 - -type - Curve25519* = object - Curve25519Key* = array[Curve25519KeySize, byte] - Curve25519Error* = enum - Curver25519GenError - -proc intoCurve25519Key*(s: openArray[byte]): Curve25519Key = - assert s.len == Curve25519KeySize - assign(result, s) - -proc getBytes*(key: Curve25519Key): seq[byte] = - @key - -proc byteswap(buf: var Curve25519Key) {.inline.} = - for i in 0 ..< 16: - let x = buf[i] - buf[i] = buf[31 - i] - buf[31 - i] = x - -proc mul*(_: type[Curve25519], point: var Curve25519Key, multiplier: Curve25519Key) = - let defaultBrEc = ecGetDefault() - - # multiplier needs to be big-endian - var multiplierBs = multiplier - multiplierBs.byteswap() - let res = defaultBrEc.mul( - addr point[0], - Curve25519KeySize, - addr multiplierBs[0], - Curve25519KeySize, - EC_curve25519, - ) - assert res == 1 - -proc mulgen(_: type[Curve25519], dst: var Curve25519Key, point: Curve25519Key) = - let defaultBrEc = ecGetDefault() - - var rpoint = point - rpoint.byteswap() - - let size = - defaultBrEc.mulgen(addr dst[0], addr rpoint[0], Curve25519KeySize, EC_curve25519) - - assert size == Curve25519KeySize - -proc public*(private: Curve25519Key): Curve25519Key = - Curve25519.mulgen(result, private) - -proc random*(_: type[Curve25519Key], rng: var HmacDrbgContext): Curve25519Key = - var res: Curve25519Key - let defaultBrEc = ecGetDefault() - let len = ecKeygen( - PrngClassPointerConst(addr rng.vtable), defaultBrEc, nil, addr res[0], EC_curve25519 - ) - # Per bearssl documentation, the keygen only fails if the curve is - # unrecognised - - doAssert len == Curve25519KeySize, "Could not generate curve" - - res - -const FieldElementSize* = Curve25519KeySize - -type FieldElement* = Curve25519Key - -# Convert bytes to FieldElement -proc bytesToFieldElement*(bytes: openArray[byte]): Result[FieldElement, string] = - if bytes.len != FieldElementSize: - return err("Field element size must be 32 bytes") - ok(intoCurve25519Key(bytes)) - -# Convert FieldElement to bytes -proc fieldElementToBytes*(fe: FieldElement): seq[byte] = - fe.getBytes() - -# Generate a random FieldElement -proc generateRandomFieldElement*(): Result[FieldElement, string] = - let rng = HmacDrbgContext.new() - if rng.isNil: - return err("Failed to creat HmacDrbgContext with system randomness") - ok(Curve25519Key.random(rng[])) - -# Generate a key pair (private key and public key are both FieldElements) -proc generateKeyPair*(): Result[tuple[privateKey, publicKey: FieldElement], string] = - let privateKeyRes = generateRandomFieldElement() - if privateKeyRes.isErr: - return err(privateKeyRes.error) - let privateKey = privateKeyRes.get() - - let publicKey = public(privateKey) - ok((privateKey, publicKey)) - -# # Multiply a given Curve25519 point with a set of scalars -# proc multiplyPointWithScalars*( -# point: FieldElement, scalars: openArray[FieldElement] -# ): FieldElement = -# var res = point -# for scalar in scalars: -# Curve25519.mul(res, scalar) -# res - -# # Multiply the Curve25519 base point with a set of scalars -# proc multiplyBasePointWithScalars*( -# scalars: openArray[FieldElement] -# ): Result[FieldElement, string] = -# if scalars.len <= 0: -# return err("Atleast one scalar must be provided") -# var res: FieldElement = public(scalars[0]) # Use the predefined base point -# for i in 1 ..< scalars.len: -# Curve25519.mul(res, scalars[i]) # Multiply with each scalar -# ok(res) - -# # Compare two FieldElements -# proc compareFieldElements*(a, b: FieldElement): bool = -# a == b diff --git a/src/naxolotl/errors.nim b/src/naxolotl/errors.nim deleted file mode 100644 index 57ac784..0000000 --- a/src/naxolotl/errors.nim +++ /dev/null @@ -1,11 +0,0 @@ - -type - NaxolotlError* = object of CatchableError - code*: ErrorCode - context*: string - - ErrorCode* = enum - errDecryption - errMessageAuthentication - errInvalidInput - errProgram \ No newline at end of file diff --git a/src/naxolotl/naxolotl.nim b/src/naxolotl/naxolotl.nim deleted file mode 100644 index d9058fd..0000000 --- a/src/naxolotl/naxolotl.nim +++ /dev/null @@ -1,210 +0,0 @@ -import curve25519 -import results -import chronicles -import nim_chacha20_poly1305/common -import strformat -import strutils -import sequtils -import tables - -import chacha -import types -import utils -import errors - -const maxSkip = 10 - - -type Doubleratchet* = object - dhSelf: PrivateKey - dhRemote: PublicKey - - rootKey: RootKey - chainKeySend: ChainKey - chainKeyRecv: ChainKey - - msgCountSend: MsgCount - msgCountRecv: MsgCount - prevChainLen: MsgCount - - # TODO: SkippedKeys - skippedMessageKeys: Table[(PublicKey,MsgCount), MessageKey] - -const DomainSepKdfRoot = "DoubleRatchetRootKey" - - -type DrHeader* = object - dhPublic*: PublicKey - msgNumber*: uint32 - prevChainLen*: uint32 - - - -func keyId(dh:PublicKey, recvCount: MsgCount ): KeyId = - (dh, recvCount) - -func hex(a: openArray[byte]) : string = - a.mapIt(&"{it:02X}").join("") - -proc `$`*(x: DrHeader): string = - "DrHeader(pubKey=" & hex(x.dhPublic) & ", msgNum=" & $x.msgNumber & ", msgNum=" & $x.prevChainLen & ")" - - -proc `$`*(key: array[32, byte]): string = - let byteStr = hex(key) - fmt"{byteStr[0..5]}..{byteStr[^6 .. ^1]}" - - -proc generateDhKey() : PrivateKey = - result = generateKeypair().get()[0] - -################################################# -# Kdf -################################################# - - -func kdfRoot(rootKey: RootKey, dhOutput:DhDerivedKey): (RootKey, ChainKey) = - - var salt = rootKey - var ikm = dhOutput - let info = cast[seq[byte]](DomainSepKdfRoot) - - hkdfSplit(salt, ikm, info) - -func kdfChain(chainKey: ChainKey): (MessageKey, ChainKey) = - - let msgKey = hkdfExtract(chainKey, [0x01u8]) - let chainKey = hkdfExtract(chainKey, [0x02u8]) - - return(msgKey, chainKey) - -func dhRatchetSend(self: var Doubleratchet) = - # Perform DH Ratchet step when receiving a new peer key. - let dhOutput : DhDerivedKey = dhExchange(self.dhSelf, self.dhRemote).get() - let (newRootKey, newChainKeySend) = kdfRoot(self.rootKey, dhOutput) - self.rootKey = newRootKey - self.chainKeySend = newChainKeySend - self.msgCountSend = 0 - -proc dhRatchetRecv(self: var Doubleratchet, remotePublickey: PublicKey ) = - self.prevChainLen = self.msgCountSend - self.msgCountSend = 0 - self.msgCountRecv = 0 - - self.dhRemote = remotePublickey - - let dhOutputPre = self.dhSelf.dhExchange(self.dhRemote).get() - let (newRootKey, newChainKeyRecv) = kdfRoot(self.rootKey, dhOutputPre) - self.rootKey = newRootKey - self.chainKeyRecv = newChainKeyRecv - - self.dhSelf = generateDhKey() - - let dhOutputPost = self.dhSelf.dhExchange(self.dhRemote).get() - (self.rootKey, self.chainKeySend) = kdfRoot(self.rootKey, dhOutputPost) - - -proc skipMessageKeys(self: var Doubleratchet, until: MsgCount): Result[(), string] = - - if self.msgCountRecv + maxSkip < until: - return err("Too many skipped messages") - - while self.msgCountRecv < until: - let (msgKey, chainKey) = kdfChain(self.chainKeyRecv) - self.chainKeyRecv = chainKey - - let keyId = keyId(self.dhRemote, self.msgCountRecv) - self.skippedMessageKeys[keyId] = msgKey - inc self.msgCountRecv - - ok(()) - -proc encrypt(self: var Doubleratchet, plaintext: var seq[byte], associatedData: openArray[byte]): (DrHeader, CipherText) = - - let (msgKey, chainKey) = kdfChain(self.chainKeySend) - self.chainKeySend = chainKey - let header = DrHeader( - dhPublic: self.dhSelf.public, #TODO Serialize - msgNumber: self.msgCountSend, - prevChainLen: self.prevChainLen) - - self.msgCountSend = self.msgCountSend + 1 - - - var (nonce, ciphertext) = encryptWithChaCha20Poly1305(msgKey, plaintext, associatedData) - - # TODO: optimize copies - var output : seq[byte] - output.add(nonce) - output.add(ciphertext) - - (header, output) - - -proc decrypt*(self: var Doubleratchet, header: DrHeader, ciphertext: CipherText, associatedData: openArray[byte] ) : Result[seq[byte], NaxolotlError] = - - let peerPublic = header.dhPublic - var msgKey : MessageKey - - # Check Skipped Keys - let keyId = keyId(header.dhPublic, header.msgNumber) - if self.skippedMessageKeys.hasKey(keyId): - debug "detected skipped message", keyId = keyId - msgKey = self.skippedMessageKeys[keyId] - else: - if (peerPublic != self.dhRemote): - let r = self.skipMessageKeys(header.prevChainLen) - if r.isErr: - error "skipMessages", error = r.error() - self.dhRatchetRecv(peerPublic) - let r = self.skipMessageKeys(header.msgNumber) - if r.isErr: - error "skipMessages", error = r.error() - - (msgKey, self.chainKeyRecv) = kdfChain(self.chainKeyRecv) - inc self.msgCountRecv - - var nonce : Nonce - copyMem(addr nonce[0], unsafeAddr ciphertext[0], Nonce.len) - var cipherTag = ciphertext[Nonce.len .. ^1] - - result = decryptWithChaCha20Poly1305(msgKey,nonce, cipherTag, associatedData ) - - if result.isOk: - # TODO: persist chainKey state changes - self.skippedMessageKeys.del(keyId) - - -proc encrypt*(self: var Doubleratchet, plaintext: var seq[byte]) : (DrHeader, CipherText) = - encrypt(self, plaintext,@[]) - - -proc initDoubleratchetSender*(sharedSecret: array[32, byte], dhRemote: PublicKey): Doubleratchet = - - result = Doubleratchet( - dhSelf: generateDhKey(), - dhRemote: dhRemote, - rootKey: RootKey(sharedSecret), - msgCountSend: 0, - msgCountRecv: 0, - prevChainLen: 0, - skippedMessageKeys: initTable[(PublicKey, MsgCount), MessageKey]() - ) - - # Update RK, CKs - result.dhRatchetSend() - -proc initDoubleratchetRecipient*(sharedSecret: array[32, byte], dhSelf: PrivateKey): Doubleratchet = - - result = Doubleratchet( - dhSelf: dhSelf, - #dhRemote: None, - rootKey: RootKey(sharedSecret), - msgCountSend: 0, - msgCountRecv: 0, - prevChainLen: 0, - skippedMessageKeys: initTable[(PublicKey, MsgCount), MessageKey]() - ) - -func dhSelfPublic*(self: Doubleratchet): PublicKey = - self.dhSelf.public \ No newline at end of file diff --git a/src/naxolotl/types.nim b/src/naxolotl/types.nim deleted file mode 100644 index a4504f4..0000000 --- a/src/naxolotl/types.nim +++ /dev/null @@ -1,18 +0,0 @@ -import curve25519 - -type PrivateKey* = Curve25519Key -type PublicKey* = Curve25519Key - -type RootKey* = array[32, byte] -type ChainKey* = array[32, byte] -type MessageKey* = array[32, byte] -type DhDerivedKey* = array[32, byte] - -type GenericArray* = array[32, byte] - -type CipherText* = seq[byte] - -type MsgCount* = uint32 -type KeyId* = (PublicKey, MsgCount) - -const KeyLen* = 32 \ No newline at end of file diff --git a/src/naxolotl/utils.nim b/src/naxolotl/utils.nim deleted file mode 100644 index 4ec9964..0000000 --- a/src/naxolotl/utils.nim +++ /dev/null @@ -1,55 +0,0 @@ -import constantine/hashes -import constantine/kdf/kdf_hkdf -import curve25519 -import results - -import errors -import types - - -func hkdfExtract*(key: openArray[byte], seed: openArray[byte]) : GenericArray = - - assert GenericArray.len == sha256.digestSize() - - var ctx{.noInit.}: HKDF[sha256] - var prk{.noInit.}: array[sha256.digestSize(), byte] - ctx.hkdfExtract(prk, key, seed) - - return prk - - - -func hkdfExtractExpand*(output: var openArray[byte], salt: openArray[byte], ikm: openArray[byte], info: openArray[byte] ) = - var ctx{.noInit.}: HKDF[sha256] - var prk{.noInit.}: array[sha256.digestSize(), byte] - ctx.hkdfExtract(prk, salt, ikm) - ctx.hkdfExpand(output, prk, info, true) - - -func hkdfSplit*(salt: GenericArray, ikm: GenericArray, info: openArray[byte] ) : (RootKey, ChainKey) = - - var output : array[KeyLen*2 , byte] - - hkdfExtractExpand(output, salt, ikm, info) - - var out1 : array[KeyLen, byte] - var out2 : array[KeyLen, byte] - - # Unsafe memcopy - copyMem(addr out1[0], addr output[0], KeyLen) - copyMem(addr out2[0], addr output[32], KeyLen) - - result = (out1,out2) - - - -func dhExchange*(a: PrivateKey, b: PublicKey): Result[DhDerivedKey, NaxolotlError] = - var dhOuput = b - - try: - Curve25519.mul(dhOuput, a) - except CatchableError as e: - return err(NaxolotlError( code: errProgram, context: e.msg)) - ok(DhDerivedKey(dhOuput)) - - diff --git a/vendor/constantine b/vendor/constantine deleted file mode 160000 index d6aae1e..0000000 --- a/vendor/constantine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6aae1eca3775d6317e11b169edef0249162ce22 diff --git a/vendor/illwill b/vendor/illwill deleted file mode 160000 index 99a120f..0000000 --- a/vendor/illwill +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99a120f7f69868b94f5d35ce7e21dd12535de70c diff --git a/vendor/nim-ffi b/vendor/nim-ffi deleted file mode 160000 index 06111de..0000000 --- a/vendor/nim-ffi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 06111de155253b34e47ed2aaed1d61d08d62cc1b diff --git a/vendor/nim-sds b/vendor/nim-sds deleted file mode 160000 index 6a05cfd..0000000 --- a/vendor/nim-sds +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6a05cfd2c954886ebbe46adb222ecc1dc9117fd0 diff --git a/vendor/nim_chacha20_poly1305 b/vendor/nim_chacha20_poly1305 deleted file mode 160000 index ad06aff..0000000 --- a/vendor/nim_chacha20_poly1305 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ad06aff319bdb5d61cebd56d2d0e31c3516112c6