Skip to content

Discovery

Overview

The discovery process allows the server to determine who it is communicating with and moves communication from an insecure channel to a secure, encrypted channel.

Key Points:

  • The discovery process is not a direct connection but a method to establish a connection.
  • The main goal is to obtain the address of the user you want to connect with.

How it works

The discovery process is like introducing two strangers who want to talk securely. Here's how it works:

  1. Build Discovery QR Code: The server creates a special code (QR code) that contains information about how to reach it.
  2. Display QR Code: This code is shown to a user, typically on a screen.
  3. Scan QR Code: The user scans the QR code with their phone or device.
  4. Send Address: By scanning the code, the user's device learns how to contact the server securely. The user's device then sends its own contact information back to the server.
  5. Start Conversation: Now that they both know how to reach each other securely, they can start a private, encrypted conversation.
sequenceDiagram
    participant Server
    participant User Device

    Server->>Server: Build discovery QR code
    Server->>User Device: Display QR code
    User Device->>User Device: Scan QR code
    User Device->>Server: Send address
    Server->>User Device: Start conversation

The main point of this process is to make sure that when the server and the user's device start talking, they do it in a way that's safe and private. It's like exchanging secret handshakes before having a confidential chat.

Benefits:

  • It's a safe way for the server and user to meet for the first time.
  • It helps set up a secure way to talk, so others can't eavesdrop.
  • Once this introduction is done, the server and user can have more complex and secure interactions in the future.

In essence, it's all about making sure the right devices are talking to each other and that their conversations are private from the very beginning.

Implementation

Discovery process is implemented both on the server and the user's mobile device.

Server Side Implementation

1. Building a Discovery QR Code

// Get a key package that the responder can use to create an encrypted group
keyPackage, _ := selfAccount.ConnectionNegotiateOutOfBand(
    inboxAddress,
    time.Now().Add(time.Minute*5),
)

// Build the key package into a discovery request
content, _ := message.NewDiscoveryRequest().
    KeyPackage(keyPackage).
    Expires(time.Now().Add(time.Minute * 5)).
    Finish()

// Encode it as a QR code
qrCode, _ := event.NewAnonymousMessage(content).
    EncodeToQR(event.QREncodingUnicode)

fmt.Println(string(qrCode))
// Get a key package and create discovery request
val expires = Timestamp.now() + 3600
val keyPackage = account.connectionNegotiateOutOfBand(inboxAddress, expires)
val discoveryRequest = DiscoveryRequestBuilder()
    .keyPackage(keyPackage)
    .expires(expires)
    .finish()

// Encode it as a QR code
val anonymousMessage = AnonymousMessage.fromContent(discoveryRequest)
val qrCodeBytes = anonymousMessage.encodeQR(QrEncoding.UNICODE)
val qrCodeString = qrCodeBytes.decodeToString()

println("scan the qr code to complete the discovery request")
println(qrCodeString)

val discoveryRequestId = discoveryRequest.id().toHexString()
println("waiting for response to discovery request requestId:${discoveryRequestId}")

2. Managing discovery requests

cfg.Callbacks = account.Callbacks{
    OnWelcome: func(selfAccount *account.Account, wlc *event.Welcome) {
        // Accept the invite to join the encrypted group created by the user
        groupAddress, _ := selfAccount.ConnectionAccept(
            wlc.ToAddress(),
            wlc,
        )
    },
    OnMessage: func(selfAccount *account.Account, msg *event.Message) {
        switch event.ContentType(msg) {
        case event.TypeDiscoveryResponse:
            discoveryResponse, _ := message.DecodeDiscoveryResponse(msg.Content())
            // Process the discovery response...
        }
    },
}
account.configure(
    // ... other config options ...
    onWelcome = { welcome: Welcome ->
        account.connectionAccept(asAddress = welcome.toAddress(), welcome = welcome) { status: SelfStatus, groupAddress: PublicKey ->
            println("accepted connection encrypted group status:${status.code()} - from:${welcome.fromAddress().encodeHex()} - group:${groupAddress.encodeHex()}")
        }
    },
    onMessage = { message: Message ->
        val content = message.content()
        val contentType = content.contentType()
        when (contentType) {
            ContentType.DISCOVERY_RESPONSE -> {
                val discoveryResponse = DiscoveryResponse.decode(content)
                val responseTo = discoveryResponse.responseTo().toHexString()
                println("received response to discovery request from:${message.fromAddress().encodeHex()} - requestId:$responseTo - messageId:${message.id().toHexString()}")

                if (responseTo != discoveryRequestId) {
                    println("received response to unknown request requestId:$responseTo")
                }
            }
        }
    }
)

Mobile App Implementation

While QR code scanning is a common method for discovery, there are alternative approaches for connecting mobile apps to servers using Self:

  1. Direct Connection: If the mobile app knows the server's address, developers can use the connectionNegotiate method from the mobile SDK to connect directly to the server. This approach bypasses the need for QR code scanning, making the discovery process implicit.

  2. QR Code Scanning: The traditional method where users scan a QR code displayed by the server to initiate the connection.

  3. Dynamic Links: Self does not support dynamic links for the discovery process yet, but this feature may be considered for future updates.

// After scanning the QRCode image, parse the data bytes from 
// the QR code and handle the UI accordingly
val discoveryData = Account.qrCode(data = qrCodeBytes)

// Configure an account with sandbox parameter using the data 
// from parsing a QR code.
val account = Account.Builder()
    .setContext(androidContext)
    .setEnvironment(Environment)
    .setSandbox(discoveryData.sandbox)
    .setStoragePath(androidContext.filesDir.absolutePath + "/account1")
    .build()

// After that, send a discovery request to the server
account.connectWith(qrCodeBytes)

Call this below method from Account instance.

// After scanning the QRCode image, parse the data bytes from the 
// QR code and handle the UI accordingly
let discoveryData = Account.qrCode(data: qrCodeBytes)

// Configure an account with sandbox parameter using the data from 
// parsing a QR code.
let groupId = "group.com.joinself.app.developer"
guard let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupId) else {
    return nil
}
let storagePath = appGroupURL.appendingPathComponent("/account1")
let account = Account.Builder()
    .withEnvironment(Environment.preview)
    .withSandbox(discoveryData.sandbox)
    .withGroupId(groupId)
    .withStoragePath(storagePath)
    .build()

// After that, send a discovery request to the server
account.connectWith(qrCode: qrCodeBytes)

Conclusion

The discovery process allows users to securely discover and connect on the Self network, preserving necessary information for ongoing communication.