JavaScript dapps: Public and private data on Blockstack

With blockchain and the Blockstack.js library, mixed privacy apps aren’t as tricky as you might think

Over the last few weeks we’ve built up a decentralized application with Blockstack that lets you create and save web application snippets. So far, these snippets have been saved privately to your own personal storage. Not even the creator of the app can access them! But the web is about collaboration, too, so we need to incorporate the ability to make snippets publicly available.

In order to use some of the more sophisticated features of Blockstack persistence in our application, we’ll need to request permission to do so from the user. This is a safety prompt you’re probably used to seeing when using federated authentication and authorization. It will look like this:

blockstack select id IDG

You may not notice a difference from our existing blockstack log-in prompt, but if you look more closely, you’ll see that “publish data stored for this app” is a new permission we’re requesting.

In order to make this request, we simply need to specify that we want the publish_data scope when redirecting to the Blockstack log-in. Scopes are the first argument of the AppConfig object in Blockstack.js, so our new log-in code will require only a one-line change:

const appConfig = new blockstack.AppConfig(
    ['store_write', 'publish_data'],
    undefined,
    '/callback',
  );
  const userSession = new blockstack.UserSession({ appConfig })
  userSession.redirectToSignIn();

The store_write was a default scope we were getting automatically before, but now that we’re adding scopes we need to explicitly define them. The major feature that our newly granted publish_data scope will unlock is the ability to save files without encrypting them.

Saving public files in Blockstack

When we saved our snippets and snippet lists with putFile, we simply used the default settings that persist encrypted versions of our files. The files were actually publicly accessible if you know the URL, but nobody could do anything with them unless they had your private key to decrypt them. The way to create public files is to save them without encryption, which can be done by passing in a configuration object when calling putFile.

blockstack.putFile('snippet.json', JSON.stringify(mySnippet), { encrypt: false });

In the above example, the only change we had to make was the addition of the last argument. In order to prevent the decryption of these files when other users access them, we’ll need to pass the same configuration object when calling getFile on a public file. In addition to the encrypt configuration, we also need to specify the user that the file belongs to. For example, if you wanted to access a public file of mine called snippet.json, you could do it with the following line of code:

blockstack.putFile('snippet.json', { username: 'freethejazz.id', encrypt: false });

Refactoring our Blockstack snippets application

With the power to save and retrieve public files in addition to private ones, we’ll need to evolve the data structure of our application once more. We currently have one file that includes a list of basic data about snippets and separate files for each snippet. Splitting out individual files for each snippet means it will be easy for us to encrypt some and not others.

However, we only have a single file that has a list of all snippets. If we make this public, other users will get summary information for private snippets. We don’t want that, so we’ll need to break our list in two: one for public snippets and one for private ones. Our private list will be encrypted, and our public list will be completely public.

We’ll save these two lists with hard-coded file names so we can easily find them from our application. With that distinction, we’ll upload public-snippets.json without encryption, so anyone using the app can find a list of our public snippets. All they need is that hard-coded file name and our Blockstack ID.

Drawbacks to file-based persistence in Blockstack

As we continue to work with a file-based persistence approach, a significant drawback starts to become obvious. Consider the case in which you want to change the visibility of a snippet from public to private. To do this, you will need to edit and save the whole public-snippets.json file, edit and save the whole private-snippets.json file, and upload the actual snippet file with encryption. If some of these changes succeed but others fail, we’re left in an inconsistent state. This is typically the sort of synchronization handled by database transactions, but we’re left to handle strange error cases ourselves.

The other drawback is that we can’t make targeted changes to our lists with the current approach. If we have a power user of our application who has hundreds of snippets, we’ll start to get larger and larger files that we have to load and save any time any summary data to a snippet changes. We can mitigate this drawback by breaking our snippets lists into smaller lists that we can paginate through, but then we’re stuck writing file-based pagination logic. Either way, it’s a small price to pay to ensure users have full control of their data.

In the coming weeks, I’ll tighten up the app we’ve been working on and will release it to the broader Blockstack ecosystem. Follow me on Twitter if you want to know when it goes live: @freethejazz

Copyright © 2019 IDG Communications, Inc.