Sharing

Class Name Sharing
Extends Logger
Source sharing.ts
Examples sharing.spec.ts

For getting a better understanding about how Sharings and Multikeys work, have a look at Security in the evan.network wiki.

Following is a sample for a sharing info with these properties:

  • three users
    • 0x01 - owner of a contract
    • 0x02 - member of a contract
    • 0x03 - another member with differing permissions
  • two timestamps
    • block 82745 - first sharing
    • block 90000 - splitting data, update sharings
  • three sections
    • * generic “catch all” used in first sharing
    • secret area - available for all members
    • super secret area - available for 0x03

Keep in mind, that an actual sharings object only stores the sha3-hashes of every property. For example, sharings for the user 0x01 were actually to be found at the property “0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2”. For the sake of understanding, the following sample uses clear text properties. For an example of an actual sharings object, please refer to the getSharings example section.

{
  "0x01": {
    "*": {
      "82745": {
        "private": "secret for 0x01, starting from block 82745 for all data",
        "cryptoInfo": {
          "originator": "0x01,0x01",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    },
    "secret area": {
      "90000": {
        "private": "secret for 0x01, starting from block 90000 for 'secret area'",
        "cryptoInfo": {
          "originator": "0x01,0x01",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    },
    "super secret area": {
      "90000": {
        "private": "secret for 0x01, starting from block 90000 for 'super secret area'",
        "cryptoInfo": {
          "originator": "0x01,0x01",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    }
  },
  "0x02": {
    "*": {
      "82745": {
        "private": "secret for 0x02, starting from block 82745 for all data",
        "cryptoInfo": {
          "originator": "0x01,0x02",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    },
    "secret area": {
      "90000": {
        "private": "secret for 0x02, starting from block 90000 for 'secret area'",
        "cryptoInfo": {
          "originator": "0x01,0x02",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    },
    "super secret area": {
      "90000": {
        "private": "secret for 0x02, starting from block 90000 for 'super secret area'",
        "cryptoInfo": {
          "originator": "0x01,0x02",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    }
  },
  "0x03": {
    "secret area": {
      "90000": {
        "private": "secret for 0x03, starting from block 90000 for 'secret area'",
        "cryptoInfo": {
          "originator": "0x01,0x03",
          "keyLength": 256,
          "algorithm": "aes-256-cbc"
        }
      }
    }
  }
}

More information about sharings can be found at the evan.network wiki.

There are two functions to share keys with another user:

  • addSharing is used for easily sharing keys to another user. There is no need to explicitly share hash keys to this other user as this is automatically covered here. This approach make up to two transaction (1 for hash key and 1 for the content key), which may sum up to a whole bunch of transactions when sharing multiple keys to multiple users.
  • extendSharing is used to edit a sharings configuration that has been pulled or “checked out” with getSharingsFromContract. Hash keys have to be shared manually, if required. extendSharing make no transaction, so the contract isn’t updated - this has to be done with saveSharingsToContract. See function documentation below for an example with hash key and storing updates.

Be careful when performing multiple updates to sharings synchronously. As sharings are retrieved as a single file from a smart contract, updated and then saved back to it, doing two or more updates in parallel may overwrite each other and lead to unexpected and most probably undesired results.

Perform sharing updates for the same contracts one after another, this goes for addSharing and for extendSharing. When wishing to speed things up, extendSharing can be used, but its updates need to be performed synchronously as well. Keep in mind, that single updates will be made off-chain and therefore be performed much faster than multiple updates with addSharing.


constructor

new Sharing(options);

Creates a new Sharing instance.

Parameters

  1. options - SharingOptions: options for Sharing constructor.

Returns

Sharing instance

Example

const sharing = new Sharing({
  contractLoader,
  cryptoProvider,
  description,
  executor,
  dfs,
  keyProvider,
  nameResolver,
  defaultCryptoAlgo: 'aes',
});

addSharing

sharing.addSharing(address, originator, partner, section, block, sharingKey[, context, isHashKey, sharingId]);

Add a sharing to a contract or an ENS address.

This function is primarily used for sharing single keys with one other users, when sharing multiple keys and/or sharing with multiple users, have a look at extendSharing.

Parameters

  1. address - string: contract address or ENS address
  2. originator - string: Ethereum account id of the sharing user
  3. partner - string: identity or account for which key shall be added
  4. section - string: data section the key is intended for or ‘*’
  5. block - number|string: starting with this block, the key is valid
  6. sharingKey - string: key to share
  7. context - string (optional): context to share key in
  8. isHashKey - bool (optional): indicates if given key already is a hash key, defaults to false
  9. sharingId - string (optional): id of a sharing (when multi-sharings is used)

Returns

Promise returns void: resolved when done

Example

// two sample users, user1 wants to share a key with user2
const user1 = '0x0000000000000000000000000000000000000001';
const user2 = '0x0000000000000000000000000000000000000002';
// create a sample contract
// usually you would have an existing contract, for which you want to manage the sharings
const contract = await executor.createContract('Shared', [], { from: user1, gas: 500000, });
// user1 shares the given key with user2
// this key is shared for all contexts ('*') and valid starting with block 0
await sharing.addSharing(contract.options.address, user1, user2, '*', 0, 'i am the secred that will be shared');

extendSharing

sharing.extendSharing(address, originator, partner, section, block, sharingKey[, context, isHashKey]);

Extend an existing sharing info with given key.

This is done on a sharings object and does not perform a transaction on its own. This function extends a sharing object retrieved from getSharingsFromContract and does not update sharings at the smart contract. For updating smart contracts sharing use saveSharingsToContract.

This function is primarily used to prepare updates for multiple keys and/or multiple users and submitting the result in one single transaction. For simpler sharing scenarios have a look at addSharing.

Parameters

  1. sharings - any: object with sharings info
  2. originator - string: identity or account of the sharing user
  3. partner - string: identity or account for which key shall be added
  4. section - string: data section the key is intended for or ‘*’
  5. block - number|string: starting with this block, the key is valid
  6. sharingKey - string: key to share
  7. context - string (optional): context to share key in

Returns

Promise returns any: updated sharings info

Example

// two sample users, user1 wants to share a key with user2
const user1 = '0x0000000000000000000000000000000000000001';
const user2 = '0x0000000000000000000000000000000000000002';

// get current sharings
const sharings = await sharing.getSharingsFromContract(contract);

// if receiver of sharing hasn't been added to the contract yet, share hash key as well
const hashKeyToShare = await sharing.getHashKey(contract.options.address, user1);
await sharing.extendSharings(sharings, user1, user2, '*', 'hashKey', hashKeyToShare, null);

// get current block number, keys will be available starting from this block
const blockNr = await web3.eth.getBlockNumber();

// get current key for field or in this case fallback '*'
const contentKey = sharing.getKey(contract.options.address, user1, '*', blockNr);

// share this key
await sharing.extendSharings(sharings, user1, user2, '*', blockNr, contentKey);

// finally store to contract
await sharing.saveSharingsToContract(contract.options.address, sharings, user1);

trimSharings

sharing.trimSharings(sharings, partner[, partner, section, block);

Removes properties from given sharing. If a block is given, the specific blocks key is removed, if no block is given, all keys for this section are removed. The same goes for section and partner. Note that only the last properties can be omitted and not properties in between can be set to null. So for example it is not possible to remove the same field for all identities or accounts by just setting partner to null.

Parameters

  1. sharings - any: sharings to trim
  2. partner - string: identity or account to remove keys for
  3. section - string: data section the key is intended for or ‘*’
  4. block - number|string: block to remove keys for

Returns

Promise returns void: resolved when done

Example

// this sample will undo undo the changes from the last example (extendSharings)
// two sample users, user1 wants to share a key with user2
const user1 = '0x0000000000000000000000000000000000000001';
const user2 = '0x0000000000000000000000000000000000000002';

// get current sharings
const sharings = await sharing.getSharingsFromContract(contract);

// remove key from last time
await sharing.trim(sharings, user2, '*', blockNr);

// finally store to contract
await sharing.saveSharingsToContract(contract.options.address, sharings, user1);

bumpSharings

sharing.bumpSharings(address, originator, partners, section, block, sharingKey);

Bump keys for given identity or account by adding given key to it’s sharings. This is basically a shorthand version for adding the new key for every identity or account in the partners array in a single transaction.

context, hashKeys and sharingId are currently not supported.

Parameters

  1. address - string: contract address or ENS address
  2. originator - string: identity or account of the sharing user
  3. partner - string: identity or account for which key shall be added
  4. section - string: data section the key is intended for or ‘*’
  5. block - number|string: starting with this block, the key is valid
  6. sharingKey - string: key to share

Returns

Promise returns void: resolved when done

Example

// two sample users, user1 wants to bump keys for user2 and user3
const user1 = '0x0000000000000000000000000000000000000001';
const user2 = '0x0000000000000000000000000000000000000002';
const user3 = '0x0000000000000000000000000000000000000003';
// assume we have a contract with sharings for those accounts
const contractId = '0x00000000000000000000000000000000c027rac7';
await sharing.bumpSharings(contractId, user1, [ user2, user3 ], '*', 0, 'i am a bump key');

getKey

sharing.getKey(address, partner, section[, block, sharingId]);

Get a content key from the sharing of a contract.

Parameters

  1. address - string: contract address or ENS address
  2. partner - string: identity or account for which key shall be retrieved
  3. section - string: data section the key is intended for or ‘*’
  4. block - number|string (optional): starting with this block, the key is valid, defaults to Number.MAX_SAFE_INTEGER
  5. sharingId - string (optional): id of a sharing (when multi-sharings is used), defaults to null

Returns

Promise returns string: matching key

Example

// a sample user
const user2 = '0x0000000000000000000000000000000000000002';
// user2 wants to read a key after receiving a sharing
// the key requested should be valid for all contexts ('*') and valid up to and including block 100
const key = await sharing.getKey(contract.options.address, user2, '*', 100);

getKeyHistory

sharing.getKeyHistory(address, partner, section[, sharingId]);

Get history of keys for an identity or account and a section.

Parameters

  1. address - string: contract address or ENS address
  2. partner - string: Ethereum account id for which key shall be retrieved
  3. section - string: data section the key is intended for or ‘*’
  4. sharingId - string (optional): id of a sharing (when multi-sharings is used), defaults to null

Returns

Promise returns any: object with key: blockNr, value: key

Example

// a sample user
const user2 = '0x0000000000000000000000000000000000000002';
// user2 wants to retrieve all keys for '*'
const keyHistory = await sharing.getKeyHistory(contract.options.address, user2, '*');

ensureHashKey

sharing.ensureHashKey(address, originator, partner, hashKey[, context, sharingId]);

Give hash key “hashKey” to identity or account “partner”, if this identity or account does not have a hash key already.

Parameters

  1. address - string: contract address or ENS address
  2. originator - string: identity or account of the sharing user
  3. partner - string: identity or account for which key shall be added
  4. hashKey - string: key for DFS hashes
  5. context - string (optional): context to share key in
  6. sharingId - string (optional): id of a sharing (when multi-sharings is used)

Returns

Promise returns void: resolved when done

Example

const hashCryptor = cryptoProvider.getCryptorByCryptoAlgo('aesEcb');
const hashKey = await hashCryptor.generateKey();
await sharing.ensureHashKey(contract.options.address, accounts[0], accounts[1], hashKey);

getHashKey

sharing.getHashKey(address, partner[, sharingid]);

Function description

Parameters

  1. address - string: contract address or ENS address
  2. partner - string: identity or account for which key shall be retrieved
  3. sharingId - string (optional): id of a sharing (when multi-sharings is used)

Returns

Promise returns string: matching key

Example

const hashCryptor = cryptoProvider.getCryptorByCryptoAlgo('aesEcb');
const hashKey = await hashCryptor.generateKey();
await sharing.ensureHashKey(contract.options.address, accounts[0], accounts[1], hashKey);
const retrieved = sharing.getHashKey(contract.options.address, accounts[1]);
console.log(hashKey === retrieved);
// Output:
// true

getSharings

sharing.getSharings(address[, _partner, _section, _block, sharingId]);

Get sharing from a contract, if _partner, _section, _block matches.

Sharings can also be retrieved using ENS address.

Parameters

  1. address - string: contract address or ENS address
  2. _partner - string (optional): identity or account for which key shall be retrieved
  3. _section - string (optional): data section the key is intended for or ‘*’
  4. _block - number (optional): starting with this block, the key is valid
  5. sharingId - string (optional): id of a sharing (when multi-sharings is used)

Returns

Promise returns any: sharings as an object. For more details, refer to the example at the top of the page.

Example

const randomSecret = `super secret; ${Math.random()}`;
await sharing.addSharing(testAddress, accounts[1], accounts[0], '*', 0, randomSecret);
await sharing.addSharing(testAddress, accounts[1], accounts[0], 'test', 100, randomSecret);
const sharings = await sharing.getSharings(contract.options.address, null, null, null, sharingId);
/* Output:
{
'0x2260228fd705cd9420a07827b8e64e808daba1b6675c3956783cc09fcc56a327': { // sha3(contract owner)
  '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] },
  '0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658': { '0': [Object] }, // sha3('test')
  '0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0': { '0': [Object] } // additional unshared field
},
'0xb45ce1cd2e464ce53a8102a5f855c112a2a384c36923fe5c6e249c2a9286369e': { // sha3(accounts[1])
  '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] }, // '*'
  '0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658': { // sha3('test')
      '100': [Object] // Valid from block 100
  }
}
*/

removeSharing

sharing.removeSharing(address, originator, partner, section[, sharingId]);

Remove a sharing key from a contract with sharing info.

Parameters

  1. address - string: contract address or ENS address
  2. originator - string: identity or account of the sharing user
  3. partner - string: identity or account for which key shall be removed
  4. section - string: data section of the key
  5. sharingId - string (optional): id of a sharing (when multi-sharings is used), defaults to null

Returns

Promise returns void: resolved when done

Example

await sharing.addSharing(contract.options.address, accounts[0], accounts[1], '*', 0, randomSecret);

let sharings = await sharing.getSharings(contract.options.address);
console.log(Object.keys(sharings[nameResolver.soliditySha3(accounts[1])]).length);
// Output:
// 1

await sharing.removeSharing(contract.options.address, accounts[0], accounts[1], '*');

let sharings = await sharing.getSharings(contract.options.address);
console.log(Object.keys(sharings[nameResolver.soliditySha3(accounts[1])]).length);
// Output:
// 0

getSharingsFromContract

sharing.getSharingsFromContract(contract[, sharingId]);

Get encrypted sharings from smart contract.

The encrypted sharings are usually used in combination with other functions for purposes of adding, removing, extending sharings etc. For Example: This can be used in combination with saveSharingsToContract to bulk editing sharing info.

Parameters

  1. contact - any: contract with sharing info
  2. sharingId - string (optional): id of a sharing in mutlisharings, defaults to null

Returns

Promise returns any: sharings as an object

Example

// get sharings (encrypted)
const sharings = await sharing.getSharingsFromContract(serviceContract, callIdHash);
// Output:
{ '0x6760305476495b089868ae42c2293d5e8c1c7bf9bfe51a9ad85b36d85f4113cb':
 { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] } }

// make changes to sharing
await sharing.extendSharings(sharings, accountId, target, section, 0, contentKeyToShare, null);
await sharing.extendSharings(sharings, accountId, target, '*', 'hashKey', hashKeyToShare, null);

// commit changes
await sharing.saveSharingsToContract(serviceContract.options.address, sharings, accountId, callIdHash);

saveSharingsToContract

sharing.saveSharingsToContract(contract, sharings, originator[, sharingId]);

Save sharings object with encrypted keys to contract.

This can be used to pull sharings, edit them offline and commit changes in a bulk. See example section for usage.

Parameters

  1. contract - string|any: contract address or instance
  2. sharings - any: sharings object with encrypted keys
  3. originator - string: identity or account of the sharing user
  4. sharingId - string (optional): id of a sharing (when multi-sharings is used)

Returns

Promise returns void: resolved when done

Example

// get sharings (encrypted)
const sharings = await sharing.getSharingsFromContract(serviceContract, callIdHash);

// make changes to sharing
await sharing.extendSharings(sharings, accountId, target, section, 0, contentKeyToShare, null);
await sharing.extendSharings(sharings, accountId, target, '*', 'hashKey', hashKeyToShare, null);

// commit changes
await sharing.saveSharingsToContract(serviceContract.options.address, sharings, accountId, callIdHash);

addHashToCache

sharing.addHashToCache(address, sharingHash[, sharingId]);

Add a hash to to cache, can be used to speed up sharing key retrieval, when sharings hash is already known.

Parameters

  1. address - string: contract address
  2. sharingHash - string: bytes32 hash of a sharing
  3. sharingId - string (optional): id of a multisharing, defaults to null

Example

sharing.addHashToCache(contract.options.address, sharingHash, sharingId);

clearCache

sharing.clearCache();

Clear caches and fetch new hashes and sharing on next request.

When sharings are fetched and not all results could be read, the result would stay the same in following requests due to the internal caching mechanism, even if a proper key has been shared with the user later on. To prevent such old values from showing up, the cache can be cleared.

Example

sharing.clearCache();