Class Name | DataContract |
---|---|
Extends | BaseContract |
Source | data-contract.ts |
Examples | data-contract.spec.ts |
The DataContract is a secured data storage contract for single properties and lists. If created on its own, DataContracts cannot do very much. They rely on their authority to check which entries or lists can be used.
The following functions support the encryptionContext
argument:
- :ref:`addListEntries <data-contract_addListEntries>`
- :ref:`setEntry <data-contract_setEntry>`
- :ref:`setMappingValue <data-contract_setMappingValue>`
If this argument is set, the data key in the data contracts sharing is encrypted by using a context key instead of the communication key between owner and contract member. This allows to omit key exchanges between contract owner and members and therefore enables the owner to write content to the smart contract, that can be used by a group of accounts, which only needs to hold the context key. So the encryptionContext
can be used to address a group of accounts instead of single accounts.
For more information about DataContracts purpose and their authorities see Data Contract in the evan.network wiki.
new DataContract(options);
Creates a new DataContract instance.
options
-DataContractOptions
: options for DataContract constructor.cryptoProvider
-CryptoProvider
:CryptoProvider
instancedefaultCryptoAlgo
-string
(optional): crypto algorith name fromCryptoProvider
, defaults toaes
dfs
-DfsInterface
:DfsInterface
instanceexecutor
-Executor
:Executor
instanceloader
-ContractLoader
:ContractLoader
instancenameResolver
-NameResolver
:NameResolver
instancesharing
-Sharing
:Sharing
instancelog
-Function
(optional): function to use for logging:(message, level) => {...}
logLevel
-LogLevel
(optional): messages with this level will be logged withlog
logLog
-LogLogInterface
(optional): container for collecting log messageslogLogLevel
-LogLevel
(optional): messages with this level will be pushed tologLog
DataContract
instance
const dataContract = new DataContract({
cryptoProvider,
description,
dfs,
executor,
loader,
nameResolver,
sharing,
web3,
});
dataContract.create(factoryName, accountId[, businessCenterDomain, contractDescription, allowConsumerInvite, sharingsHash]);
Create and initialize new contract.
factoryName
-string
: contract factory name, used for ENS lookup; if the factory name contains periods, it is threaded as an absolute ENS domain and used as such, if not it will be used as${factoryName}.factory.${businessCenterDomain}
accountId
-string
: owner of the new contract and transaction executorbusinessCenterDomain
-string
(optional): ENS domain name of the business centercontractDescription
-string|any
(optional): bytes32 hash of DBCP description or a schema objectallowConsumerInvite
-bool
(optional): true if consumers are allowed to invite other consumersharingsHash
-string
(optional): existing sharing to add, defaults tonull
Promise
returns any
: contract instance
Let's say, we want to create a DataContract for a business center at the domain "samplebc.evan" and this business center has a DataContractFactory named "testdatacontract". We want to have two users working in our DataContract, so we get these sample values:
const factoryName = 'testdatacontract';
const businessCenterDomain = 'samplebc.evan';
const accounts = [
'0x0000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000002',
];
Now create a contract with:
const contract = await dataContract.create(factoryName, accounts[0], businessCenterDomain);
Okay, that does not provide a description for the contract. Let's add a description to the process. The definition is a DBCP contract definition and is stored in an Envelope
(see :doc:`Encryption <../encryption/index>`):
const definition: Envelope = {
"public": {
"name": "Data Contract Sample",
"description": "reiterance oxynitrate sat alternize acurative",
"version": "0.1.0",
"author": "evan GmbH",
"dataSchema": {
"list_settable_by_member": {
"$id": "list_settable_by_member_schema",
"type": "object",
"additionalProperties": false,
"properties": {
"foo": { "type": "string" },
"bar": { "type": "integer" }
}
},
"entry_settable_by_member": {
"$id": "entry_settable_by_member_schema",
"type": "integer",
}
}
}
};
definition.cryptoInfo = cryptoProvider.getCryptorByCryptoAlgo('aes').getCryptoInfo(accounts[0]);
const contract = await dataContract.create('testdatacontract', accounts[0], businessCenterDomain, definition);
Now we have a DataContract with a description. This contract is now able to be understood by other components, that understand the dbcp. And on top of that, we provided data schemas for the two properties list_settable_by_member
and entry_settable_by_member
(written for ajv). This means, that when someone adds or sets entries to or in those properties, the incoming data is validated before actually encrypting and storing it.
To allow other users to work on the contract, they have to be invited with:
await dataContract.inviteToContract(businessCenterDomain, contract.options.address, accounts[0], accounts[1]);
Now the user accounts[1]
can use functions from the contract, but to actually store data, the user needs access to the data key for the DataContract. This can be done via updating the contracts sharing:
const blockNr = await web3.eth.getBlockNumber();
const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr);
await sharing.addSharing(contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey);
Now the contract has been created, has a sharing and another user has been granted access to it. Variable names from this section will be used in the rest of the document as example values.
dataContract.createSharing(accountId);
Create initial sharing for contract.
accountId
-string
: owner of the new contract
Promise
returns any
: sharing info with { contentKey, hashKey, sharings, sharingsHash, }
const sharing = await dataContract.createSharing(profileReceiver);
dataContract.setEntry(contract, entryName, value, accountId[, dfsStorage, encryptedHashes, encryption);
Set entry for a key.
contract
-any|string
: contract or contractIdentryName
-string
: entry namevalue
-any
: value to setaccountId
-string
: Ethereum account iddfsStorage
-Function
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): encrypt hashes from values, defaults totrue
encryption
-string
(optional): encryption algorithm to use, defaults todefaultCryptoAlgo
(set in constructor)encryptionContext
-string
(optional): plain text name of an encryption context, defaults toaccountId
Promise
returns void
: resolved when done
const sampleValue = 123;
await DataContract.setEntry(contract, 'entry_settable_by_owner', sampleValue, accounts[0]);
Entries are automatically encrypted before setting it in the contract. If you want to use values as is, without encrypting them, you can add them in raw mode, which sets them as bytes32
values:
const sampleValue = '0x000000000000000000000000000000000000007b';
await dataContract.setEntry(contract, 'entry_settable_by_owner', sampleValue, accounts[0], true);
dataContract.getEntry(contract, entryName, accountId[, dfsStorage, encryptedHashes]);
Return entry from contract.
contract
-any|string
: contract or contractIdentryName
-string
: entry nameaccountId
-string
: Ethereum account iddfsStorage
-Function
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): decrypt hashes from values, defaults totrue
Promise
returns any
: entry
Entries can be retrieved with:
const retrieved = await dataContract.getEntry(contract, 'entry_settable_by_owner', accounts[0]);
Raw values can be retrieved in the same way:
const retrieved = await dataContract.getEntry(contract, 'entry_settable_by_owner', accounts[0], true);
dataContract.addListEntries(contract, listName, values, accountId[, dfsStorage, encryptedHashes, encryption]);
Add list entries to lists.
List entries support the raw mode as well. To use raw values, pass true
in the same way as wehn using the entries functions.
List entries can be added in bulk, so the value argument is an array with values. This array can be arbitrarily large up to a certain degree. Values are inserted on the blockchain side and adding very large arrays this way may take more gas during the contract transaction, than may fit into a single transaction. If this is the case, values can be added in chunks (multiple transactions).
contract
-any|string
: contract or contractIdlistName
-string
: name of the list in the data contractvalues
-any[]
: values to addaccountId
-string
: Ethereum account iddfsStorage
-string
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): encrypt hashes from values, defaults totrue
encryption
-string
(optional): encryption algorithm to use, defaults todefaultCryptoAlgo
(set in constructor)encryptionContext
-string
(optional): plain text name of an encryption context, defaults toaccountId
Promise
returns void
: resolved when done
const sampleValue = {
foo: 'sample',
bar: 123,
};
await dataContract.addListEntries(contract, 'list_settable_by_member', [sampleValue], accounts[0]);
When using lists similar to tagging list entries with metadata, entries can be added in multiple lists at once by passing an array of list names:
const sampleValue = {
foo: 'sample',
bar: 123,
};
await dataContract.addListEntries(contract, ['list_1', 'list_2'], [sampleValue], accounts[0]);
dataContract.getListEntryCount(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);
Return number of entries in the list. Does not try to actually fetch and decrypt values, but just returns the count.
contract
-any|string
: contract or contractIdlistName
-string
: name of the list in the data contract
Promise
returns number
: list entry count
await dataContract.getListEntryCount(contract, 'list_settable_by_member');
dataContract.getListEntries(contract, listName, accountId[, dfsStorage, encryptedHashes, count, offset, reverse]);
Return list entries from contract. Note, that in the current implementation, this function retrieves the entries one at a time and may take a longer time when querying large lists, so be aware of that, when you retrieve lists with many entries.
contract
-any|string
: contract or contractIdlistName
-string
: name of the list in the data contractaccountId
-string
: Ethereum account iddfsStorage
-string
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): decrypt hashes from values, defaults totrue
count
-number
(optional): number of elements to retrieve, defaults to10
offset
-number
(optional): skip this many items when retrieving, defaults to0
reverse
-boolean
(optional): retrieve items in reverse order, defaults tofalse
Promise
returns any[]
: list entries
await dataContract.getListEntries(contract, 'list_settable_by_member', accounts[0]));
dataContract.getListEntry(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);
Return a single list entry from contract.
contract
-any|string
: contract or contractIdlistName
-string
: name of the list in the data contractindex
-number
: list entry id to retrieveaccountId
-string
: Ethereum account iddfsStorage
-string
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): decrypt hashes from values, defaults totrue
Promise
returns any
: list entry
const itemIndex = 0;
await dataContract.getListEntry(contract, 'list_settable_by_member', itemIndex, accounts[0]));
redataContract.moveListEntry(contract, listName, entryIndex, accountId);
Remove list entry from list.
This will reposition last list entry into emptied slot.
contract
-any|string
: contract or contractIdlistName
-string
: name of the list in the data contractindex
-number
: index of the entry to remove from listaccountId
-string
: Ethereum account id
Promise
returns void
: resolved when done
const listName = 'list_removable_by_owner'
const itemIndexInList = 1;
await dataContract.removeListEntry(contract, listNameF, itemIndexInList, accounts[0]);
dataContract.moveListEntry(contract, listNameFrom, entryIndex, listNamesTo, accountId);
Move one list entry to one or more lists.
Note, that moving items requires the executing account to have remove
permissions on the list listNameFrom
. If this isn't the case, the transaction will not be exetured and not updates will be made.
contract
-any|string
: contract or contractIdlistNameFrom
-string
: origin listindex
-number
: index of the entry to move in the origin listlistNamesTo
-string
: lists to move data intoaccountId
-string
: Ethereum account id
Promise
returns void
: resolved when done
const listNameFrom = 'list_removable_by_owner';
const listNameTo = 'list_settable_by_member';
const itemIndexInFromList = 1;
await dataContract.moveListEntry(contract, listNameFrom, itemIndexInFromList, [listNameTo], accounts[0]);
dataContract.setMappingValue(contract, mappingName, entryName, value, accountId[, dfsStorage, encryptedHashes, encryption]);
Set entry for a key in a mapping. Mappings are basically dictionaries in data contracts. They are a single permittable entry, that allows to set any keys to it. This can be used for properties, that should be extended during the contracts life as needed, but without the need to update its permission settings.
contract
-any|string
: contract or contractIdmappingName
-string
: name of a data contracts mapping propertyentryName
-string
: entry name (property in the mapping)value
-any
: value to addaccountId
-string
: Ethereum account iddfsStorage
-string
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): encrypt hashes from values, defaults totrue
encryption
-string
(optional): encryption algorithm to use, defaults todefaultCryptoAlgo
(set in constructor)encryptionContext
-string
(optional): plain text name of an encryption context, defaults toaccountId
Promise
returns void
: resolved when done
await dataContract.setMappingValue(
contract,
'mapping_settable_by_owner',
'sampleKey',
'sampleValue',
accounts[0],
storeInDfs,
);
dataContract.getMappingValue(contract, listName, index, accountId[, dfsStorage, encryptedHashes]);
Return a value from a mapping. Looks up a single key from a mapping and returns its value.
contract
-any|string
: contract or contractIdmappingName
-string
: name of a data contracts mapping propertyentryName
-string
: entry name (property in the mapping)accountId
-string
: Ethereum account iddfsStorage
-string
(optional): store values in dfs, defaults totrue
encryptedHashes
-boolean
(optional): encrypt hashes from values, defaults totrue
encryption
-string
(optional): encryption algorithm to use, defaults todefaultCryptoAlgo
(set in constructor)
Promise
returns any
: mappings value for given key
const value = await dataContract.getMappingValue(
contract,
'mapping_settable_by_owner',
'sampleKey',
accounts[0],
storeInDfs,
);
dataContract.encrypt(toEncrypt, contract, accountId, propertyName, block[, encryption]);
Encrypt incoming envelope.
toEncrypt
-Envelope
: envelope with data to encryptcontract
-any
: contract instance or contract idaccountId
-string
: encrypting accountpropertyName
-string
: property in contract, the data is encrypted forblock
-block
: block the data belongs toencryption
-string
: encryption name, defaults todefaultCryptoAlgo
(set in constructor)
Promise
returns string
: encrypted envelope or hash as string
const data = {
public: {
foo: 'example',
},
private: {
bar: 123,
},
cryptoInfo: cryptor.getCryptoInfo(nameResolver.soliditySha3(accounts[0])),
};
const encrypted = await dataContract.encrypt(data, contract, accounts[0], 'list_settable_by_member', 12345);
dataContract.decrypt(toDecrypt, contract, accountId, propertyName, block[, encryption]);
Decrypt input envelope return decrypted envelope.
toDecrypt
-string
: data to decryptcontract
-any
: contract instance or contract idaccountId
-string
: account id that decrypts the datapropertyName
-string
: property in contract that is decrypted
Promise
returns Envelope
: decrypted envelope
const encrypted = await dataContract.decrypt(encrypted, contract, accounts[0], 'list_settable_by_member');
dataContract.encryptHash(toEncrypt, contract, accountId);
Encrypt incoming hash. This function is used to encrypt DFS file hashes, uses AES ECB for encryption.
toEncrypt
-Envelope
: hash to encryptcontract
-any
: contract instance or contract idaccountId
-string
: encrypting account
Promise
returns string
: hash as string
const hash = '0x1111111111111111111111111111111111111111111111111111111111111111';
const encrypted = await dataContract.encryptHash(hash, contract, accounts[0]);
dataContract.encrypt(toEncrypttoDecrypt, contract, accountId, propertyName, block[, encryption]);
Decrypt input hash, return decrypted hash. This function is used to decrypt encrypted DFS file hashes, uses AES ECB for decryption.
toDecrypt
-Envelope
: hash to decryptcontract
-any
: contract instance or contract idaccountId
-string
: encrypting account
Promise
returns string
: decrypted hash
const encryptedHash = '0x2222222222222222222222222222222222222222222222222222222222222222';
const encrypted = await dataContract.decryptHash(encryptedHash, contract, accounts[0]);