Identity Management in Hyperledger Sawtooth Transaction Families

0
3593

This article follows several earlier ones on Hyperledger Sawtooth. It explores the concept of identity and its various ramifications in Hyperledger Sawtooth. It is targeted at open source technology enthusiasts with an interest in blockchain technologies and those who have a working knowledge of the Hyperledger Sawtooth project.

On-chain permissioning is required for transactor key and validator key permissioning. This feature requires that authorised participants for each role be stored. Currently, the only form of identification on the network is the entity’s public key. Since lists of public keys are difficult to manage, an identity namespace is used to streamline the management of identities. The identity system in Hyperledger Sawtooth is an extensible role and policy based system for defining permissions in a way that can be used by other pieces of the architecture. This includes the existing permissioning components for the transactor key and validator key, but in the future, may also be used by transaction family implementations.

The identity namespace
The following list covers what the identity namespace does:

  • Encompasses ways to identify participants based on public keys
  • Stores a set of ‘permit and deny’ rules called policies
  • Stores the roles that those policies apply to

Policies describe a set of identities that have the permission to perform some action, without specifying the logic of the action itself. Roles simply reference a policy, providing a more flexible interface. For example, a policy could be referenced by multiple roles. So if the policy is updated, all roles referencing the policy are updated as well. An example of a role might be named ‘transactor’, and references the policy that controls who is allowed to submit batches and transactions on the network.

State Policies

A policy has a name and a list of entries. Each policy entry has a list of type/key pairs. The type is either a PERMIT_KEY or a DENY_KEY. Each key in a type/key pair is a public key.

message PolicyList {
repeated Policy policies = 1;
}

message Policy {

enum EntryType {
ENTRY_TYPE_UNSET = 0;
PERMIT_KEY = 1;
DENY_KEY = 2;
}

message Entry {
// Whether this is a PERMIT_KEY or DENY_KEY entry
EntryType type = 1;

// This should a public key or * to refer to all participants.
string key = 2;
}

// name of the policy, this should be unique.
string name = 1;

// list of Entries
// The entries will be processed in order from first to last.
repeated Entry entries = 2;
}

Roles
A role is made up of a role name and the name of the policy to be enforced for that role. The data is stored in state at the address described above, using the following protobuf messages:

message RoleList {
repeated Role roles = 1;
}

message Role{
// Role name
string name = 1;

// Name of corresponding policy
string policy_name = 2;
}

Addressing
All identity data is stored under the special namespace “00001d”.
For each policy, the address is formed by concatenating the namespace, the special policy namespace of ‘00’, and the first 62 characters of the SHA-256 hash of the policy name:

>>> "00001d" + "00" + hashlib.sha256(policy_name.encode()).hexdigest()[:62]

Address construction for roles follows a pattern similar to the address construction in the settings namespace. Role names are broken into four parts, where parts of the string are delimited by the “.” character. A short hash is computed for each part. For the first part, the first 14 characters of the SHA-256 hash are used. For the remaining parts, the first 16 characters of the SHA-256 hash are used. The address is formed by concatenating the identity namespace ‘00001d’, the role namespace ‘01’, and the four short hashes.

For example, the address for the role client.query_state is constructed as follows:

>>> “00001d”+ “01” + hashlib.sha256(‘client’.encode()).hexdigest()[:14]+ \
hashlib.sha256(‘query_state’.encode()).hexdigest()[:16]+ \
hashlib.sha256(‘’.encode()).hexdigest()[:16]+ \
hashlib.sha256(‘’.encode()).hexdigest()[:16]

Transaction payload
Identity transaction family payloads are defined by the following protocol buffer code:

message IdentityPayload {
enum IdentityType {
POLICY = 0;
ROLE = 1;
}

// Which type of payload this is for
IdentityType type = 1;

// Serialize bytes of a role or a policy
bytes data = 2;
}

Transaction header

Inputs and outputs
The inputs for identity family transactions must include:

  • The address of the setting sawtooth.identity.allowed_keys
  • The address of the role or policy being changed
  • If setting a role, the address of the policy to assign to the role

The outputs for identity family transactions must include:

  • The address of the role or policy being changed

Dependencies
None.

Family

  • family_name: “sawtooth_identity”
  • family_version: “1.0”

Execution
Initially, the transaction processor gets the current values of sawtooth.identity.allowed_keys from the state. The public key of the transaction signer is checked against the values in the list of allowed keys. If it is empty, no roles or policy can be updated. If the transaction signer is not among the allowed keys, the transaction is invalid. Whether this is a role or a policy transaction is checked by looking at the IdentityType in the payload.

If the transaction is for setting a policy, the data in the payload is parsed to form a policy object. This is then checked to make sure it has a name and at least one entry. If either is missing, the transaction is considered invalid. If the policy is determined to be whole, the address for the new policy is fetched. If there is no data found at the address, a new policy list object is created, the new policy is added, and the policy list is applied to state. If there is data, it is parsed into a policy list. The new policy is added to the policy list, replacing any policy with the same name, and the policy list is applied to state.

If the transaction is for setting a role, the data in the payload is parsed to form a role object, which is then checked to make sure it has a name and a policy_name. If either is missing, the transaction is considered invalid. The policy_name stored in the role must match a policy already stored in state; if no policy is found stored at the address created by the policy_name, the transaction is invalid. If the policy exists, the address for the new role is fetched. If there is no data found at the address, a new RoleList object is created, the new role is added, and the policy list is applied to state. If there is data, it is parsed into a RoleList. The new role is added to the role list, replacing any role with the same name, and the role list is applied to state.

LEAVE A REPLY

Please enter your comment!
Please enter your name here