Mobile connectivity is not an object we can hold in our hands. It is a service provided by Mobile Network Operators (MNO) in the form of radio wave allocation, bandwidth and consumption over a defined period of time… Or at least this is the way it is currently being sold to consumers.
OXIO’s vision is that connectivity should possess a raw form, much like a currency that can be sold, traded, lent, gifted and consumed. For telecom actors to adopt such a protocol, we need a data store that can be trusted by all. We chose the Stellar blockchain for its maturity, speed and off-chain simple contracts–which we believe to be safer than on-chain Turing complete contracts because updating or shutting down a service is simpler and faster than to hotfix a smart contract.
Our solution is to create a connectivity asset/token to represent bandwidth as a currency that can be consumed by anyone or anything. This asset can be sold to consumers, traded on exchanges, lent or gifted.
For such a concept to work universally, we need a way for the same asset to represent different values at different times with different actors. E.g. 10 tokens of connectivity may be worth 10 GiB of Wi-Fi in NYC on provider A but 1.5 GiB of 4G in Mexico City on provider B. Moreover, some capacity providers will want to limit how long those 10 tokens are valid for. We need a system on the blockchain for each of these individual representations: escrow accounts.
We will go over how we designed a model that uses escrow accounts on the Stellar blockchain to enable the use of a universal connectivity asset placed in holding between actors in order to cover any connectivity use case.
Technical implementation
In a previous article, we described our way of representing non-fungible asset types on Stellar, some of the concepts are going to be re-used here.
In this article we will use the term “capacity provider” to describe any bandwidth owner or seller, may it be an MNO or a Wi-Fi/cable provider, as the line between each of those becomes blurry when a provider starts offering services on more than one type of network.
To represent a promise of connectivity on the blockchain, an escrow account needs to be created on the blockchain with the following properties:
- Balance of the connectivity token
- Identity of the capacity provider
- Identity of the beneficiary, which is the entity that can either consume the connectivity or resell/gift/lend it to another entity
- Definition of the promise of connectivity, containing the following:
- Medium: can be LTE, Wi-Fi, etc.
- Network: an identifier for the physical network
- Location: country, city, neighborhood where the connectivity can be consumed
- Start/end dates: useful to simulate post-paid plans as we know them
- Ratio of connectivity token per KiB: used to calculate how much connectivity is left using the account balance
- Minimum split size (KiB): used to create sub-escrow accounts in order to resell/gift/lend a portion of the escrow account to someone else–more on this in the section about “child” accounts
Note: in order to support post-paid subscription plans, the end date is set on the escrow account as well as a rule defining what becomes of the unconsumed connectivity. It can go to the provider, in which case it is used as collateral. It can also go back to the consumer to trade, sell, or use on another network. Or it could follow a more complex policy defining percentages for who gets how much.
In order to attach those properties to an escrow account, we are using Stellar’s data entry capability. It is essentially a key-value store available on each Stellar account, with the following properties:
- The key is a 64-byte string
- The value is a 64-byte binary
Below is described how we store our different properties inside escrow accounts using data entries.
Escrow account data entries
escrow_def
This entry contains everything related to the definition, stored in a binary structure. The value is versioned and zero-padded to support evolutions to the protocol.
escrow_ids
This entry contains the identity (public key) of both the capacity provider and beneficiary.
Public keys on Stellar are 56-char long strings, that we can then convert to base 32 and strip of their meta-data. The meta-data consists of a version to know whether it is a public key, a secret key, or a transaction ID, because those all use the same format. We are left with a 32-byte binary value that represents a public key. Example of encoding/decoding using the Stellar Go SDK:
import "github.com/stellar/go/strkey"
// Converting string public key to 32-byte value
publicKey := "GARHU23JFNSBZF7VRFZMNLVDH5SD5RUE62U2RMKHOZOWIVL2MWCXC6TP"
decodedPublicKey, err := strkey.Decode(strkey.VersionByteAccountID, pk)
if err != nil {
panic(err)
}
// Converting 32-byte value to string public key
publicKey, err = strkey.Encode(strkey.VersionByteAccountID, decodedPublicKey)
if err != nil {
panic(err)
}
We can then fit the two public keys in the data entry by concatenating them in a consistent order.
escrow_sig
Anyone could create a Stellar account following the protocol as described above. In order to prove that an escrow account is “valid” or “trusted”, one needs to know the public key of a capacity provider and have a way to verify that the escrow account has been approved or signed in some capacity using this public key.
Digging into the Stellar mechanism that enables transaction signing, we noticed that we could sign any byte array and produce a 64-byte signature (how convenient!) that we can verify using the signed value and the public key corresponding to the secret key it was signed with.
To that end, we used the same technique as for the escrow_def
entry: a binary structure containing all the information required to prove an account’s authenticity. The list of fields we used is as follows:
- The binary value of
escrow_def
- The escrow account’s public key, to prevent bad actors from simply copying an account with its data entries
- The source1 account’s sequence number2 at the escrow account’s creation (see definitions below) because it is unique and cannot change or be copied
Account balance
In order to receive a payment made of a non-native token (asset) on Stellar, the destination account needs to trust that asset beforehand. The process of “trusting” an asset is referred to as opening a “trustline” on the destination account for a pair of asset code and asset issuer public key.
If we used trustlines as is, any connectivity token holder (consumers, traders, etc.) could exploit the protocol by issuing a payment to an escrow account with particularly interesting rates. This is where the trust “limit” comes into play. It is a trustline property that enables the destination account of a payment operation to block the incoming asset if its value is set to the current asset balance.
Here are the implementation rules to follow in order to keep secure escrow accounts:
- Escrow account is created with an open trustline on the connectivity token with a limit equal to the first payment meant to “fill up” the account
- Each time the capacity provider records consumption, it issues a payment from the escrow account to its own account and updates the limit to match the balance
It is important to note that all actors should consider escrow accounts with a balance that is not equal to the trust limit invalid.
Going deeper…
Escrow accounts like they are described above are what we call “genesis” escrow accounts. They’re accounts created by capacity providers directly. We need a way to split genesis accounts into smaller accounts in a way that does not require capacity providers to be a part of every single transaction happening when connectivity is sold, traded or gifted to minimize the friction between actors.
Power of Attorney for capacity providers
Stellar offers multi-signature capabilities by allowing to add “signers” that have different signing “weights”. Operations inside of each transaction have an associated “threshold” which is either low, medium or high. Those three thresholds can be configured per account to allow only certain public keys to sign for certain operations. The Stellar network checks that the sum of weights for a given transaction is enough to validate all operations inside that transaction, given operation thresholds and account threshold values.
OXIO or other third parties can offer “Power of Attorney” capabilities as a service to actors that wish to use the escrow account model without requiring blockchain knowledge or integration right away.
The capacity provider for an escrow account is by default given access to the high threshold so it can issue payments and close an account once its balance has reached zero.
Adding signers to support a “Power of Attorney”-like way of functioning adds the following complications:
- Account thresholds and signer weights need to be carefully considered
- Extra signers need to be entities the capacity provider trusts to be its proxy
- The
escrow_sig
data entry now needs to contain the extra signers and their original weight in a defined order, as the capacity provider’s signature is not required to create an escrow account on the blockchain
It is worth noting that in all cases, the escrow account’s own public key must be set to have a weight of 0, in order to prevent whoever created the account to hold on to the secret key and tinker with the account balance. This means that to properly verify an account’s validity, it is necessary to check that the account master weight (Stellar lingo) has been set to 0 in the account creation transaction.
Child accounts and account splits
Now that we’ve defined a model in which capacity providers do not need to be directly involved in every step of the way, let’s tackle how we handle account splits.
An escrow account beneficiary might own a multi-terabyte escrow account bought directly from a capacity provider. In order to sell it for parts to consumers, it is going to need to split it into smaller accounts that we call child accounts.
Data entries
In child accounts, the escrow_def
entry is replaced by an entry called escrow_src
, which contains two public keys, in the same way it is done for escrow_ids
. Those public keys are:
- The public key of the genesis escrow account from which it is being made
- The public key of the account that will sign the
escrow_sig
field: that account needs to be an explicit signer with enough weight to reach the high threshold on the genesis account
Two changes are then required in the way escrow_sig
works:
- The contents of
escrow_def
are replaced by the contents ofescrow_src
in the pre-signature structure - The data entry’s value needs to be verified using the second public key in the
escrow_src
entry, and this is what enables authorized third parties to execute splits on escrow accounts
Note: splits on a child accounts work the same way as on genesis accounts, and use the same definition entry. This means that all child accounts are traceable back to their genesis in a single hop.
Getting around Stellar’s limitations
Minimum balance and XLM funder account
Stellar accounts need to have a minimum balance of XLM (Stellar Lumens, the native Stellar asset). This minimum balance can be calculated as follows:
(2 + # of entries) * base reserve
- Entries are: trustlines, data entries, extra signers
- Base reserve: set to 0.5 XLM at the network level
We included the possibility for a third-party Stellar account to pay for the creation fees and initial balance. This third-party account can be the beneficiary’s to avoid requiring additional signatures from the capacity provider. It can also be the provider’s in order to deliver a lean service to consumers or any other account, enabling a range of use-cases. We call this account the “XLM funder”.
As long as the entity issuing payments from escrow accounts respects the defined protocol, the XLM funder receives a refund of the XLM invested for the minimum account balance as soon as an account is closed. Fees are not recovered as they are collected by validators on the Stellar network. This is called an “account merge” in Stellar, and it happens for escrow accounts whenever the last consumption payment is done.
Handling more than 1 escrow account every 5 seconds
A transaction submitted to the Stellar network is processed in an average of 5 seconds. If we aim to create more than one escrow account every 5 seconds, we need to do so using multiple source accounts, to benefit from using multiple sequence numbers. To that end, we need to maintain a fleet of accounts meant only to be used for account creation. Our solution to that is as follows:
- We can dynamically scale our “creator” account fleet by automatically creating them as needed. They require a balance of 4.501 XLM which is the sum of the minimum account balance, the amount to fund an escrow account and enough to pay the fees for 100 operations, the maximum number of operations per transaction.
- We can then add an extra operation to the account creation transaction for the XLM funder to pay the fees back to the “creator” account. This prevents us from needing to maintain the “creator” account balance and keeps it at 4.501 XLM automatically.
Another type of “funder” account
By default, the beneficiary is the one funding the connectivity token balance (representing the account’s capacity) for a new escrow account. Since this might not fit all use cases, it is also possible to use a “connectivity token funder” account. This is useful in a post-paid subscriber model in which a capacity provider has an existing relationship with its subscribers and desires to represent it on the blockchain.
Conclusion
OXIO thrives to build a model that empowers both consumers in their choice of capacity and providers by enabling them to differentiate themselves in their contract offering to their customers. The escrow account model we’re building on top of the Stellar blockchain is a way to build trust between these actors and strengthen their relationship.
- The source account is the account that originates the transaction. The transaction must be signed by this account, and the transaction fee must be paid by this account. The sequence number of this transaction is based off this account. (Source: Stellar docs) [return]
- Each transaction has a sequence number. Transactions follow a strict ordering rule when it comes to processing of transactions per account. For the transaction to be valid, the sequence number must be 1 greater than the sequence number stored in the source account entry when the transaction is applied. As the transaction is applied, the source account’s stored sequence number is incremented by 1 before applying operations. If the sequence number on the account is 4, then the incoming transaction should have a sequence number of 5. After the transaction is applied, the sequence number on the account is bumped to 5. Note that if several transactions with the same source account make it into the same transaction set, they are ordered and applied according to sequence number. For example, if 3 transactions are submitted and the account is at sequence number 5, the transactions must have sequence numbers 6, 7, and 8. (Source: Stellar docs) [return]