stacks-network / gaia

A decentralized high-performance storage system
MIT License
762 stars 149 forks source link

enable posting to gaia hub with multi-sig JWTs #118

Closed bodymindarts closed 4 years ago

bodymindarts commented 6 years ago

In order to upload a profile that belongs to a blockstack id that is backed by a multi-sig address gaia should accept writes that are authenticated via multi-party signature.

I'm not sure this formulation is as precise as I can make it. It is an issue that came up in a conversation with @jcnelson, hopefully he can clarify.

kantai commented 6 years ago

That's a good description, @bodymindarts, thanks.

I think the plan to move forward for this would be to:

  1. Add support in our JWT library (https://github.com/blockstack/jsontokens-js) for multi-sig
  2. Make the changes required in the verifier on the gaia hub to support that.

Though I would be interested in understanding the use cases for this. There's a lot going on I think beyond just supporting this on the gaia hub --- pushing data to the hub from a multi-sig address seems like a pretty hard usability problem to me (different public key holders would have to do independent signatures of the JWT and then one person who holds that JWT would then need to push the data) -- but the JWT used in gaia auth is also generic -- so it would allow a push to any path for that address. So we would probably want to add support for a "path" specifier in the JWT claim.

There's some other dependent libraries that would probably need to be updated to support this as well (blockstack.js's sign+verify in the file routines, probably the putFile would need to accept an optional JWT).

jcnelson commented 6 years ago

The idea we discussed is to update Gaia's write authentication to require multiple signatures over the challenge text. This would be enforced by ensuring that the set of public keys provided alongside the signatures continue to hash to the multisig address.

The motivation for this is that in Misthos, there exists the notion of a "shared" record (i.e. a venture) that multiple different people have to sign to consider it valid. An alternative approach to this that would work today is to have the requisite set of people each sign a shared record, and replicate their signatures to their respective Gaia hubs. Then, alice.id could check that bob.id and carol.id signed the venture she wrote and shared with them by checking their Gaia hubs for their signatures over the shared venture.

kantai commented 6 years ago

Then, alice.id could check that bob.id and carol.id signed the venture she wrote and shared with them by checking their Gaia hubs for their signatures over the shared venture.

Irrespective of the work (which is significant) required to implement the alternative, the above approach actually sounds better -- this allows, say, alice.id to re-key and still participate in the venture, but also it actually matches the model proposed for Misthos more closely (i.e., you want to actually ensure that the claimed data is signed by the participants -- this is not a guarantee you get from just a vanilla gaia write).

Before we commit to trying to solve this problem through a bunch of changes in the developer tooling, we should understand what's involved solving this problem with a multi-sig name owner, the changes required are:

  1. signature verifications in blockstack.js must be changed to enable multisig verification (that's us explicitly writing new signature verification scheme, btw, not a standardized approach)
  2. what does encryption now look like in blockstack.js?
  3. implement specific multi-sig address checks for P2SH, P2WH, P2SHWH style multisig addresses in jsontokens
  4. add support for the above jsontokens implementations into gaia hubs.

That seems like a lot of things to change (with a lot of custom cryptographic code) and a lot of new interfaces to commit to for a use-case which it's not obvious is best solved by this approach.

bodymindarts commented 6 years ago

I understand that this requires a lot of code (which I am happy to contribute). And I am open to alternative implementation approaches, however I haven't yet seen (or perhaps understood) a suggestion that offers 3rd parties a publicly discoverable, independently verifiable and always consistent way to discover data about a venture that does not necessitate the revealing of the ids of participants.

And even when relaxing the last requirement (optional anonymity) things don't look much better. It would require a custom indexer that either reads profile data of all users, or looks for persisted files of all users. I have concerns regarding robustness, scalability and liveness of this approach. Its also a lot of application specific code that cannot be reused by other eco-system participants.

bodymindarts commented 6 years ago

As far as I undestood this forum post multi-sig was on the roadmap anyhow.

kantai commented 6 years ago

Okay, I think I see now.

So -- the idea here would be:

  1. A name, say aaron-co.venture is owned by a multi-sig address <multi-sig-address>
  2. aaron-co.venture has an app-specific address <multi-sig-app-address>, which is used to construct and store a gaia hub URL in the apps field of aaron-co.venture's profile.
  3. participants in the venture construct authentication tokens for writing to the above gaia hub (this should probably be a path-specific token)
  4. participants collaborate to write a signed message to a path 'foo.json' in the above gaia hub using that authentication token.
  5. if I want to read from that venture as another user, I can do a simple getFile('foo.json', { decrypt: false, user: 'aaron-co.venture' }) and I have some way over verifying a signature on that file.

The (non-anonymous) alternative would be:

  1. A name, say aaron-co.venture is owned by a multi-sig address <multi-sig-address>
  2. aaron-co.venture stores a Gaia URL in its apps field
  3. That gaia URL has just one required file (participants.json)
  4. Participants write to foo.json within their own gaia hub.
  5. If I want to read from that venture as another user, I do getFile('participants.json', { user: 'aaron-co.venture' }) and then iterate through the participants, performing getFile on foo.json for each.

I think these have non-trivial trade-offs. The first approach will be much faster for readers, but requires a lot of coordination -- in particular, when it comes to constructing the authentication token, the participants would have to each independently perform their signatures and then secretly communicate it between the participants, because those signatures are being used for authentication. And there's a question about how long that authentication should be valid, and which client actually performs the write. The second approach has an admittedly slow read path and doesn't allow for more anonymous participation, but allows for independent writes.

bodymindarts commented 6 years ago

The first approach will be much faster for readers, but requires a lot of coordination -- in particular, when it comes to constructing the authentication token, the participants would have to each independently perform their signatures and then secretly communicate it between the participants, because those signatures are being used for authentication.

This is solved in Misthos already for signing bitcoin transactions. The solution is sufficiently generic that it is fairly simple to add additional use cases for signing other payloads.

And there's a question about how long that authentication should be valid, and which client actually performs the write.

The first point is certainly a valid question, ideally if all files being uploaded are additionally signed by a quorum of participants the time of validity isn't a huge security issue, as non-signed files would be rejected by clients reading them anyhow. As to which client is uploading, as long as the writes are idempotent this shouldn't matter. Basically all clients could upload the file once it has been sufficiently signed and shared.

As to the 2nd approach, what do you mean with ... but allows for independent writes.? The setup which includes setting participants.json would require just as much coordination to upload that file, or?

kantai commented 6 years ago

The first point is certainly a valid question, ideally if all files being uploaded are additionally signed by a quorum of participants the time of validity isn't a huge security issue, as non-signed files would be rejected by clients reading them anyhow.

True, though the authentication token could be used to delete (write an empty buffer to) the file.

As to the 2nd approach, what do you mean with ... but allows for independent writes.? The setup which includes setting participants.json would require just as much coordination to upload that file, or?

Yes, it requires just as much coordination to upload that file, but wouldn't require that coordination on every write to other files.

bodymindarts commented 6 years ago

True, though the authentication token could be used to delete (write an empty buffer to) the file.

hmm... this is a tough one. Would it be possible to scope the token to permissible actions? Eg. write but not overwrite, or something like that?

bodymindarts commented 6 years ago

the more think about it the more I like the benefits of the approach with participants.json. Still need to solve the same problems though to get that one persisted. Would it make a difference if the participants got persisted directly into the profile.json instead of another file references from the profile?

bodymindarts commented 6 years ago

True, though the authentication token could be used to delete (write an empty buffer to) the file.

Perhaps authentication could be scoped not only to a /path but also a hash of a file to change / upload.

kantai commented 6 years ago

the more think about it the more I like the benefits of the approach with participants.json. Still need to solve the same problems though to get that one persisted. Would it make a difference if the participants got persisted directly into the profile.json instead of another file references from the profile?

Wouldn't make too much of a difference, though it would require a custom lookup -- basically instead of a getFile you'd call lookupProfile and then fetch the participants entry out of there.

Perhaps authentication could be scoped not only to a /path but also a hash of a file to change / upload.

That could work -- though right now, the gaia hub doesn't actually need to read the uploaded file before passing it to the backend (which improves performance a lot). Alternatively, I suppose the authentication could provide write but not a delete (once that op is supported), and then the higher level signature checks on the data would fail if say, one of the participants tried to write empty data.

bodymindarts commented 6 years ago
  1. signature verifications in blockstack.js must be changed to enable multisig verification (that's us explicitly writing new signature verification scheme, btw, not a standardized approach)
  2. what does encryption now look like in blockstack.js?
  3. implement specific multi-sig address checks for P2SH, P2WH, P2SHWH style multisig addresses in jsontokens
  4. add support for the above jsontokens implementations into gaia hubs.
kantai commented 6 years ago

Doesn't 3 cover most of 1 + 4? As in: verification is all in the library and blockstack.js + gaia just call the library?

blockstack.js's signature verification doesn't use jsontokens because we wanted the property that if the file was unencrypted, that the raw contents of the file remained the same, and the signature was stored in a metadata file. So we'd have to port that regime over to the blockstack.js library. So, yeah, it'd end up being very similar code, but would have to be fit into two different places.

bodymindarts commented 6 years ago

A point that is still an issue for me:

- A name, say aaron-co.venture is owned by a multi-sig address <multi-sig-address>
- aaron-co.venture has an app-specific address <multi-sig-app-address>, which is used to construct and store a gaia hub URL in the apps field of aaron-co.venture's profile.

Is it a must that we have an apps field? I was thinking the owner address could be the same gaia hub URL. Again this use case is for public-only data so no need to segregate the storage space of the apps. I was thinking the claim field of a multi-sig address could look like:

{
  "@type": "Venture",
  "@context": "http://schema.org",
  "pubKeys": [<pubKey1>,<pubKey2>],
  "nSignersMin": 2,
  "multiSigType": "witness",
  "partnerIds": [<blockstackId1>,<blockstackId2>],
  "gaiaURL": "https://gaia.blockstack.org/hub/<ownerAddress>/",
  }

Or something like that. pubKeys would probably also show up outside the claim field under issuer and subject as it is now.

kantai commented 6 years ago

Is it a must that we have an apps field? I was thinking the owner address could be the same gaia hub URL. Again this use case is for public-only data so no need to segregate the storage space of the apps.

Yeah, that would work -- though some of the functions in blockstack.js may behave kind of strangely, because they assume that they are writing on behalf of a specific application (so they look for that apps entry in the profile).

So in this case, the participants.json file would be uploaded to the owner's gaia address -- meaning a multi-sig address, right?

markmhendrickson commented 6 years ago

@kantai Is this issue currently awaiting further discussion before its precise specification is clear?

kantai commented 6 years ago

No, I think a spec for multi-sig auth in Gaia would be fairly clear.

zone117x commented 4 years ago

Closing this issue due to lack of activity. Please feel free to comment if this should be re-opened.