NAV Navbar
ruby go

Introduction

What is Self?

Self is a free to use service that lets you prove you’re a real, live person and are who you say you are, all without revealing the detail of your identity. Self keeps your data safe, secure and anonymous.

How does it work?

You download the Self mobile app for free and tell it some facts about you. Then you ask friends, family, colleagues or a recognised organisation (eg: a bank) to verify that your facts are true. Your name, age, location etc…

The facts are stored in your secret locker (personal data store) and are never shared with anyone, but when someone needs to know your age, Self can help you provide them with a verified answer, all while keeping your data safe, secure and anonymous.

Getting Started

Requirements

To build an app on self-network its required to have our app installed so you can log in to the Self Developer Portal.

Please visit the official app stores to download our official apps for Android and iOS

Create an account

Visit Self Developer Portal and follow the steps to register. You'll need a Self Identifier account to proceed.

App creation

Self-apps are autonomous self identities able to interact with other identities on the Self-network.

A developer can create an app through the developer portal. This app will be identified by a SELF_APP_ID and a SELF_APP_DEVICE_SECRET.

SELF_APP_ID is your public identifier on the network and you can share it with other peers. You must keep SELF_APP_DEVICE_SECRET in a secure place.

You’ll see below how to use these two strings to initialize your client and start interacting with the Self-network.

Environments

Self provides a sandbox so you can try your app code before moving to production. You can register for a free developer account and create new test apps on the Sandbox developer portal here.

Once your app is ready you can create a new one on production here.

Initializing your client

Install your client

$ gem install "selfsdk"
$ go get github.com/joinself/self-go-sdk

We currently offer support for some basic clients. We are always happy to receive contributions to review - send us a PR, or contact us at info@joinself.com to share what you have built with us!

Language URL
Go https://github.com/joinself/self-go-sdk
Ruby https://github.com/joinself/self-ruby-sdk/
Typescript https://github.com/joinself/self-typescript-sdk/

Referencing the SDK

require "selfsdk"
import "github.com/joinself/self-go-sdk"

SelfSDK is referenced like any other library.

Storage Key Generation

Self-SDK locally persists session and account information needed for end to end encryption. A SELF_STORAGE_KEY is required to securely encrypt this information. It is recommended that you use a large random string for your SELF_STORAGE_KEY. You can generate a random string through the command line with:

$ LC_ALL=C tr -dc '[:alnum:]' < /dev/urandom | head -c64

Keep that key in a secure place as you’ll need it to initialize a connection.

Basic connection

@client = SelfSDK::App.new(ENV["SELF_APP_ID"], ENV["SELF_APP_DEVICE_SECRET"], ENV["SELF_STORAGE_KEY"], ENV["SELF_STORAGE_DIR"])
client, err := selfsdk.New(selfsdk.Config{
    SelfAppD:            os.Getenv("SELF_APP_ID"),
    SelfAppDeviceSecret: os.Getenv("SELF_APP_DEVICE_SECRET"),
    StorageKey:          os.Getenv("SELF_STORAGE_KEY"),
    StorageDir:          os.Getenv("SELF_STORAGE_DIR"),
})

A basic connection to Self network only requires your SELF_APP_ID, SELF_APP_DEVICE_SECRET, SELF_STORAGE_KEY and SELF_STORAGE_DIR.

You may want to add SELF_APP_ID, SELF_APP_DEVICE_SECRET, SELF_STORAGE_KEY and SELF_STORAGE_DIR as environment variables.

Connection to a custom environment

client = SelfSDK::App.new(
    ENV["SELF_APP_ID"],
    ENV["SELF_APP_DEVICE_SECRET"],
    ENV["SELF_STORAGE_KEY"],
    ENV["SELF_STORAGE_DIR"],
    environmnent: :sandbox)
client, err := selfsdk.New(selfsdk.Config{
    SelfAppID:           os.Getenv("SELF_APP_ID"),
    SelfAppDeviceSecret: os.Getenv("SELF_APP_DEVICE_SECRET"),
    StorageKey:          os.Getenv("SELF_STORAGE_KEY"),
    StorageDir:          os.Getenv("SELF_STORAGE_DIR"),
    Environment:           "sandbox"
})

When you’re debugging your app, you may want to point to the sandbox environment instead of production to run your tests. You can define the environment by passing additional parameters to the Self client initialization.

Automatic reconnection

client = SelfSDK::App.new(
    ENV["SELF_APP_ID"],
    ENV["SELF_APP_DEVICE_SECRET"],
    ENV["SELF_STORAGE_KEY"],
    ENV["SELF_STORAGE_DIR"],
    auto_reconnect: false)
client, err := selfsdk.New(selfsdk.Config{
    SelfID:     os.Getenv("SELF_APP_ID"),
    PrivateKey: os.Getenv("SELF_APP_DEVICE_SECRET"),
    StorageKey: os.Getenv("SELF_STORAGE_KEY"),
    StorageDir: os.Getenv("SELF_STORAGE_DIR"),
    ReconnectionAttempts: -1,
})

Self-SDK keeps a websocket connection open to self-messaging, but eventually, that connection may drop. By default Self-SDK will try to reconnect, but you can override this behaviour by passing custom parameters to initialization.

Disconnecting

@client.close
client.Close()

You can gracefully manage the client disconnection by calling close method.

If a connection is not gracefully closed Self messaging servers will kill that connection after one minute.

Connections

This feature allows you to manage your connections by permitting and revoking incoming messages from specific identities.

During the app creation process you can define if your app permits connections from everyone, or just you. You can tweak this behaviour as shown below.

Permit specific connections

@client.messaging.permit_connection "1234567890"
err := selfsdk.MessagingService().PermitConnection("1234567890")

Allows incoming messages from specified identities.

Permit global connections

@client.messaging.permit_connection "*"
err := selfsdk.MessagingService().PermitConnection("*")

You can permit connections from everyone using an “*”.

Revoke specific connections

Revokes an existing permission ruby @client.messaging.revoke_connection "1234567890" go err := client.MessagingService().RevokeConnection("1234567890") Blocks incoming messages from specific identities.

Revoke global connections

@client.messaging.revoke_connection "*"
err := client.MessagingService().RevokeConnection("*")

Revoke connection permissions from everyone using an “*”.

Listing connections

@client.messaging.allowed_connections.each do |self_id|
  p "- '#{self_id}'"
end
connections, _ := client.MessagingService().ListConnections()
log.Println("connected to:", connections)

You can list your app allowed connections.

Identities, Users and Apps

Self network is formed of different entities with the ability to interact between them.

We name these entities identities and they own a self_id, an array of device ids and an array of public keys.

An identity can only communicate with another identity if IdentityA is a connection of IdentityB. If this connection is in place IdentityA will be able to request the IdentityB properties (device_ids and public keys) to interact with it.

Identities can be divided into users and apps.

Public keys

@user = @client.identity.public_keys "1112223334"
#  will be [{"1":"xxxxxxxxxxxxxx"}]
identity, _ := client.IdentityService().GetPublicKeys("1112223334")

Every single identity on the Self network has at least one public key assigned to it. Lets see how you can get the public keys related to a Self user.

Devices

@devices = @client.identity.devices "1112223334"
# @devices will be ["1"]
devices _ := client.IdentityService().GetDevices("1112223334")

Same as with public keys each identity has at least one device.

Authentication

One of the core features of Self is the ability to authenticate identities.

There are different ways to approach authentication. Lets see how we can achieve this.

Blocking Request

begin
  @app.authentication.request("1112223334").accepted?
rescue => e # An exception will be raised in case of a timeout or internal error
  return false
end
if err != client.AuthenticationService().Request("1112223334") {
    println("authentication rejected")
    return
}
println("authenticated")

This approach sends an authentication request and waits for the user to respond before continuing the execution.

This approach is useful when you want to wait for a user to respond to the authentication before continuing, and you don’t have timeout limitations on your server. An example could be authenticating a user on your command line script.

Lets see how it works, the functions below show a full cycle for a blocking authentication.

Non-blocking Request

# Request lets you pass a block to be executed once a response is received
@client.authentication.request selfid do |auth|
  return auth.accepted? # The user has rejected the authentication
end
// Go language provides you with goroutines to
// implement a non-blocking approach
go func() {
  if err != client.AuthenticationService().Request(selfid) {
    println("authentication rejected")
    return
  }
  println("authenticated")
}()

In contrast to the previous example, there are situations where you just want to continue your execution line and set up an observer to be executed as soon as a response is received.

Let's say you have a conventional registration process where you let your users register to your application by its email address but you delay the email confirmation until they get back to you. This allows your users to go through a quick registration process, while you delay the data confirmation process.

Asynchronous Request

cid = @client.authentication.request(selfid, async: true)
client.AuthenticationService().RequestAsync(selfid, "conversation_id")

This scenario is similar to non-blocking authentication, however, it’s not restricted to only one user.

Sending an asynchronous authentication request is pretty straightforward, you can do it with the async option.

This will return a conversation id identifying the authentication conversation, you should store it and catch it on a subscription, check Receiving authentication response - Subscribe section on how to manage this.

Custom Requests

CID
@client.authentication.request(selfid, cid: "conversation_id")
client.AuthenticationService().RequestAsync(selfid, "conversation_id")

Providing a cid allows you to override the randomly generated conversation id with your own, this is useful to keep track of conversations with a preset identifier, let’s see the asynchronous example using a custom cid.

Instead of using the randomly generated conversation identifier, we can force the system to use our own unique id.

Response subscription

@client.authentication.subscribe do |auth_res|
  puts resp.status
end
client.Messaging().Subscribe("identities.authenticate.req", func(m *messaging.Message)) {
  // manage the response
}

Users can respond to authentication requests by accepting or rejecting them. On the other hand, you won’t receive asynchronous notifications for requests which have timed out.

You can subscribe to authentication responses with this snippet

Dealing with response

if resp.accepted?
  p "accepted"
elsif resp.rejected?
  p "rejected"
elsif resp.unauthorized?
  p "unauthorized"
elsif resp.errored?
  p "errored"
else
  p "unkonwn status"
end

# Or simply access the status string
P resp.status
// Go SDK will return an error for unsuccessful
// responses
err = authService.Request("1112223334")
if err != nil {
    log.Fatal("auth returned with: ", err)
}

log.Println("authentication succeeded")

The interesting field in an authentication response is status. Valid values for status are:

accepted

The user has accepted the authentication request, so you can proceed authenticating it on your app.

rejected

The user has rejected the authentication request.

unauthorized

You’re unauthorized to interact with this user, let it know it needs to be connected to your app before continuing with an authentication process.

errored

An internal error happened.

Depending on the SDK there are different ways you can deal with different status, see some examples below

Mastering Facts

A Facts is a statement about an identity. Verified facts are facts that have been attested to by trusted issuer(s).

A single fact can be verified by multiple issuers and so have multiple assertions. Verified facts are held by identities and can be shared with other identities (relying parties) who require them.

Facts and related attestations are stored on the user’s device, so the user has full control over who can access its information.

Each fact has the following properties:

Sources

A fact’s source represents where this fact comes from, for example a DOB can come from different sources such as a passport or a driving license. By default this field is set with a wildcard “*” and the user will select the source on their device. Same happens if you specify multiple sources.

At the moment there are three different sources user_specified, passport and driving_license.

As this list is always growing, you can check the possible values on your SDK ruby-sdk and go-sdk.

Fact

This is the name of the fact you want to request like display_name, email_address or phone_number.

The list of values by source is:

Attestations

A list of verified attestations signed by an issuer. This only has content on fact request responses, and it contains a list of attestations for the current fact.

Expected value

When you send an intermediary fact check you expect the value of a user’s fact to be compared with an expected value, this field is where you will provide that value.

Operator

On an intermediary fact check this field contains the operator used to compare the expected value against the expected value. Valid operators can be found here

Fact requests

Fact requests allow your app to request specific facts from identities on the Self-network.

Only verified facts can be requested

The approaches to request facts are similar to authentication, so let's have a look at them.

Blocking fact request

def get_email(selfid)
  res = @client.facts.request(selfid, [:email_address])
  return "" unless res.accepted?
  res.attestation_values_for(:email_address).first
  # Refer to Receiving fact response - Deal with request for more info
rescue => e # An exception will be raised in case of a timeout or internal error
  return ""
end
req := fact.FactRequest{
SelfID:      selfID,
    Description: "info",
    Facts: []fact.Fact{{ Fact: fact.FactEmail }},
    Expiry: time.Minute * 5,
}

resp, err := client.FactService().Request(&req)
if err != nil {
    return "", err
}


aa, err := resp.AttestaionValuesFor(fact.FactEmail)
if err != nil {
    return "", err
}

println(aa[0])

Same as with authentication there are situations where you want to block your execution line until the user responds to your fact request. For example, you may want to block a user's access to a certain space of your site until you verify its passport number.

This function blocks the execution line until the user responds with its verified email address.

Non-blocking fact request

def get_email_in_background(selfid)
  @client.facts.request(user, [:email_address]) do |res|
    return "" unless res.accepted?
    return res.attestation_values_for(:email_address).first
    # Refer to Receiving fact response - Deal with request for more info
  end
rescue => e # An exception will be raised in case of a timeout or internal error
  return ""
end
// Use goroutines to build a non-blocking example based on the blocking one
go func() {
    req := fact.FactRequest{
    SelfID:      selfID,
        Description: "info",
        Facts: []fact.Fact{{ Fact: fact.FactEmail }},
        Expiry: time.Minute * 5,
    }

    resp, err := client.FactService().Request(&req)
    if err != nil {
        return "", err
    }


    aa, err := resp.AttestaionValuesFor(fact.FactEmail)
    if err != nil {
        return "", err
    }

    println(aa[0])

  if err != nil {
    // failed
  }
  // do something with email
}

The sdk also allows us to send a fact request without blocking the execution line.

Same registration example we used for authentication works here, if you want to get a user verified fact but you don’t want the user to be blocked on the registration form, you can use this approach to continue with the execution line, and process the response as soon as it gets back.

Let’s see how we can request a verified email address with a non-blocking request.

Asynchronous fact request

cid = @client.facts.request(selfid, [SelfSDK::FACT_EMAIL], async: true)
req := fact.FactRequest{
SelfID:          selfID,
    Description: "info",
    Facts:       []fact.Fact{{ Fact: fact.FactEmail }},
    Expiry:      time.Minute * 5,
    Async:       true,
    CID:         "conversation_id",
}

resp, err := client.FactService().RequestAsync(&req)
if err != nil {
    return "", err
}

The asynchronous approach can be used in scenarios like the previous one, however it has a subtle difference, you have a single observer for all information requests, which can be useful in some situations as you can have all the logic centralized on a single point.

Custom CID

The conversation id to be used. Use this option in case you want to override the default random uuid.

Custom EXP_TIMEOUT

Timeout in seconds after which the request will expire. Useful if you want your fact request to be valid just for a specific period of time. It defaults to 900 seconds.

Subscribe to fact response

As the user also has the option to accept or reject a fact request, the response also comes with a status property and some helpers like the authentication response.

Additionally a fact response comes with a list of attestations for the requested fact.

Subscribing to a facts response is similar to authentication. ruby @client.facts.subscribe do |resp| # GOTO Deal with the response for details end go // ...

Deal with the response

resp.facts.each do |fact|
  p fact.name
  fact.attestations do |a|
    p "received #{a.value} from #{a.source}/#{a.fact} signed by #{a.origin}"
  end
end
for _, f := range resp.Facts {
    log.Println(f.Fact, ":", f.AttestedValues())
}

Even though they’re not described here, the fact response will come with a status and the same list of helpers as we described on authentication to deal with it.

Additionally each fact of the response has a list of attestations let’s see how to process them.

Intermediary fact checks

Intermediary fact checks are useful when you want to run a check of a user’s verified fact without having to gain access to that fact.

Eg. You want to verify a user is over 18, but you don’t want to deal with their personal information. An intermediary fact check allows you to run a zero knowledge check on that information via a trusted intermediary.

This is useful both for you and your users because you can prove a fact without ever directly interacting to them.

Blocking

facts = [{ sources: [SelfSDK::SOURCE_USER_SPECIFIED],
           fact: SelfSDK::FACT_EMAIL,
           operator: '==',
           expected_value: 'test@test.org' }]
res = @client.facts.request_via_intermediary(selfid, facts)
  # GOTO Receiving fact response - Deal with the response
req := fact.IntermediaryFactRequest{
    SelfID:       selfID,
    Intermediary: intermediary,
    Description:  "info",
    Facts: []fact.Fact{
        {
            Fact:          fact.FactDateOfBirth,
            Sources:       []string{fact.SourceUserSpecified},
            Operator:      ">=",
            ExpectedValue: time.Now().Format(time.RFC3339),
        },
    },
    Expiry: time.Minute * 5,
}

resp, err := client.FactService().RequestViaIntermediary(&req)

Non-blocking

@client.facts.request_via_intermediary(selfid, facts) do |res|
  # GOTO Receiving fact response - Deal with the response
end
// ...

Asynchronous

res = @client.facts.request_via_intermediary(selfid, facts, async: true)
req := fact.IntermediaryFactRequest{
    SelfID:       os.Args[1],
    Intermediary: intermediary,
    Description:  "info",
    Facts: []fact.Fact{
        {
            Fact:          fact.FactEmail,
            Sources:       []string{fact.SourceUserSpecified},
            Operator:      "==",
            ExpectedValue: "test@example.com",
        },
    },
    Expiry: time.Minute * 5,
}

factService := client.FactService()

resp, err := factService.RequestViaIntermediary(&req)
if err != nil {
    log.Fatal("fact request returned with: ", err)
}

for _, f := range resp.Facts {
    if f.Result() != true {
        log.Fatal("intermediary could not verify the required facts")
    }
    log.Printf("Your assertion that %s %s is %t\n", f.Fact, f.Operator, f.Result())
}

This will return a conversation id identifying the fact request conversation, you should store it and catch it on a subscription, as described on the next section.

Subscribe to fact checks

A subscription to an intermediary fact check response behaves exactly the same as a basic fact response.

Deal with the response

This is similar to the fact request, the only difference in this case is that the attestation.value will always be a boolean indicating if your check is accomplished or not.

Notify

@client.messaging.notify "1234567890", "Hello world!"
err := client.MessagingService().Notify("1234567890", "Hello world!")

It will prompt the user with a push notification or badge with a specific message.

FAQ

Where is my personal data stored?

At Self we believe your personal data is yours and yours only. Therefore all personal data about you remains with you and is stored on your mobile device. The data itself is encrypted with a key that only you have access to. This means the only person who can see the data is you and anyone you decide to share it with.

How do I recover my account?

The easiest way to recover an account is to recover from a backup. This can be done by reinstalling the Self application and selecting 'Recover my Account' from the initial welcome screen.

Support

If you have any other questions please do not hesitate to contact support@joinself.com.