Archive
Workspace Signature Chain

Workspace Signature Chain

In order to determine the list of active team members a chain of signed events is used. In addition they can share encrypted data with each other without the central service being able to read it.

There is a prototype available here (opens in a new tab), but yet it's not implemented in the application.

Goal

The project is inspired by web of trust and blockchains to aim for the following behaviour:

  • Enable asynchronous exchange of data (participants don't have to be online at the same time)
  • Only one verification is necessary to establish trust between everyone in the workspace
  • Workspace access to a member can be revoked instantly
  • A workspace can't be manipulated in hindsight
  • "Efficiently" be able download the current state by a client (e.g. not running a full blockchain node)

High Level Architecture

To achieve the defined goals the signature chain relies on a central service in combination with asymmetric cryptography.

The current state of a workspace can be constructed from a series of events (chain) and multiple encrypted state entries (encrypted state).

Chain

The purpose of the chain is determine who is part of the organization and what permissions does this member have.

The central service as well as the members must have full access to this information.

EncryptedState

The purpose of the encrypted-state is contain information like usernames to hide away from the central service. Only members have access to the content of the encrypted state.

Design Decisions

Why a central server?

In decentralized systems there are two issues that didn't align with the goals because:

  1. A change e.g. adding or removing a participant from a group needs to propagate to all participants before the take effect and therefor state is only eventual consistent (opens in a new tab).
  2. Afaik asynchronous exchange and strong consistency is conflicting. For example blockchains require a lot of online nodes to verify the chain and to achieve a certain gurantee that the current version is the one that will be used and not another fork. There are some ideas and proposal how to tackle it though e.g. local-first-web/auth discussion (opens in a new tab)

The three main objectives for the server are:

  1. Be an always online instance for members to asynchronously exchange data
  2. Prevent members to submit updates based on outdated state which ensures a correct data integrity (for the unencrypted state).
  3. Instantly revoke access to removed members.

How does it work?

For example the encrypted state has a logical clock in the public, but authenticated data submitted by a user. This way the server can throw an error in case the user didn't have the latest state. The user's client can then fetch the latest information and retry.

Note: Depending on the UX the user might want to re-review their changes. This might vary from case to case.

Known Attack Vectors

  • Member
  • Admin
  • Server
  • Member collaborating with the central service
  • Admin collaborating with the central service

Meta Data

Since the chain is public all the meta data about who has access to the group and all their permissions are visible to the central service. This is a known trade-off and possibly an evolution of the protocol using zero knowledge proofs (like the Signal Private Group System (opens in a new tab)) could reduce the meta data visible to the server while keep the functionality.

Security Properties

Trust Chain Package

Trust Chain Events

  • create-chain

    • Usually created by one author, but can be more as well.
  • add-member

    • Members can only add another member if they have the permission canAddMember. A member can not add another member as admin.
    • Admins can add members and set the permissions canAddMember and canRemoveMember.
  • remove-member

    • Members can only remove another member if they have the permission canAddMember. A member can not remove another member as admin.
    • Admins can add members and set the permissions canAddMember and canRemoveMember.
  • update-member

    • Only admins can update the authorization info isAdmin, canAddMember and canRemoveMember of other members.

EncryptedState Structure

Example structure of the encrypted state:

[
  {
    // identified for symmetric key that's used
    "keyId": "43b89b1c-6c7b-48a6-839c-20cc495d3f97",
    "ciphertext": "yYV1_uUtoFPRBRBCUtyv-jwPLfQI2UFYMovnUUFJh5UXALMkq69KM73YI3WaqZGrcclwTE7jMYAsYKR5rU0d-Q7yprXOS_1hXZ8TeKyQ-vSxpgZmgJVUK5zP_HDyFB4ackgBRdws6IyOMxc1Kz_HB-SCqC8Vk26H7YhGaIQWs4MXNpqgTg17cIo7Q2TL2shYN_VHSHmAeojByOfyDpUP3kpXK3zduIZZEKG3tuwjnzN0JG83BCPel8Iyrh91_UnBpctnXQUEhkoQ_ZJ_6YXpGYTnhTFur40NhX5nDjpIImbCiMoVAVIU_KZEcQ4bzGZ1_eM8AeRKTiie",
    "nonce": "YOnlwPnEtwnfEQa0nEQR41RvQ7Dxt2NB",
    // global logical clock to order the encrypted states (to ensure deterministic data resolving)
    "publicData": "{\"clock\":2}",
    "author": {
      "publicKey": "pkcVysaH_mC-TpXzZEAAeB47rIqsWwubaM4stZQu-B4",
      "signature": "XTNuX2mqcpRF4nLnhHURAYblpE68ftPsaNW3ZsdZY1Se7BlLTjJSnADrV9Rn4AhM4MIjAsU8mj003vQCJUtjAA"
    },
    "lockbox": {
      "keyId": "43b89b1c-6c7b-48a6-839c-20cc495d3f97",
      "receiverSigningPublicKey": "pkcVysaH_mC-TpXzZEAAeB47rIqsWwubaM4stZQu-B4",
      "senderLockboxPublicKey": "7CVtpWbqKFJvZO7hso3dOmrrwva_3uDgm3erquuTFRQ",
      "ciphertext": "f3_AVc5xIxC8ejhBflv_qhAjtZoTveMroA9H4uATn51yKKpToegWn_dB-P-oYG8IK_CKDt4mfmGD-43M5KVSeAykPDiPIxAIBAgNZfjVJMwhiYUFMt4U26WTlHX4aBFa75-VztyF2TcYmkhEbFw-Gf0fVVs",
      "nonce": "HLDYyAc4GMEcCqHSE2a_N-l1oaRkWfCL"
    }
  }
]

Error Philosophy

The chain will not accept any invalid input. It will not ignore them, but rather throw an Error. When creating an event there is no validation.

Here an example for clarification:

const event = createChain(…); // does not throw errors
const state = resolveState([event]); // throws errors (internally uses applyEvent)
 
const event2 = addMember(…); // does not throw errors
const newState = applyEvent(state, event2); // throws errors

Known Issues

  • Remove member (based on a event proposal does not work)
  • Chain/Encryped State related
    • when promoting someone to an admin then set the name in the encrypted state
    • when removing a member, take over the encrypted state updates from them
    • when removing permissions from a member, take over the encrypted state updates
  • Admin actions need to be in sync, meaning I can't vote on two admin interaction at the same time. Any additional chain event will invalidate them. See future improvements for a possible solution.

Future Improvements

  • Exhaustive TS Matching
  • Functions should not mutate incoming parameters