def compute_pdo_ccl_signature(private_key, enclave_id, enclave_signature, channel_id, contract_id, creator_public_key_pem, contract_code_hash, message_hash, current_state_hash, previous_state_hash, dependency_list): k = crypto.SIG_PrivateKey(private_key) message_byte_array = crypto.string_to_byte_array(enclave_id) message_byte_array += crypto.base64_to_byte_array(enclave_signature) message_byte_array += crypto.string_to_byte_array(channel_id) message_byte_array += crypto.string_to_byte_array(contract_id) message_byte_array += crypto.string_to_byte_array(creator_public_key_pem) message_byte_array += crypto.base64_to_byte_array(contract_code_hash) message_byte_array += crypto.base64_to_byte_array(message_hash) message_byte_array += crypto.base64_to_byte_array(current_state_hash) #in ccl initialize, previous state hash and dependencies are supposed to be empty if previous_state_hash: message_byte_array += crypto.base64_to_byte_array(previous_state_hash) for d in dependency_list: message_byte_array += crypto.string_to_byte_array(d.contract_id) message_byte_array += crypto.string_to_byte_array(d.state_hash) signature = k.SignMessage(message_byte_array) encoded_signature = crypto.byte_array_to_base64(signature) logger.debug("signed message string: " + crypto.byte_array_to_base64(message_byte_array)) logger.debug("signed message hash: " + crypto.byte_array_to_hex( crypto.compute_message_hash(message_byte_array))) logger.debug("signature: %s", encoded_signature) return encoded_signature
def ccl_initialize(self, channel_keys, contract_enclave_id, enclave_signature, contract_id, contract_code_hash, message_hash, current_state_hash, contract_metadata_hash, **extra_params): json_input = JsonPayloadBuilder.build_ccl_transaction_from_data( self.pdo_signer.signing_key, self.pdo_signer.verifying_key, 'initialize', channel_keys.txn_public, contract_enclave_id, enclave_signature, contract_id, crypto.byte_array_to_base64(message_hash), crypto.byte_array_to_base64(current_state_hash), "", # previous_state_hash, "", # encyrpted root block. No longer stored in Sawtooth [], # empty dependency_list crypto.byte_array_to_base64(contract_code_hash), crypto.byte_array_to_base64(contract_metadata_hash)) # contract code hash is necessary for the pdo signature extra_params['key_str'] = channel_keys.txn_private return self.submit_json(json_input, json_input['af'], **extra_params)
def submit_initialize_transaction(self, ledger_config, **extra_params): """submit the initialize transaction to the ledger """ if self.status is False: raise Exception( 'attempt to submit failed initialization transactions') global transaction_dependencies # an initialize operation has no previous state assert not self.old_state_hash initialize_submitter = Submitter(ledger_config['LedgerURL'], key_str=self.channel_keys.txn_private) b64_message_hash = crypto.byte_array_to_base64(self.message_hash) b64_new_state_hash = crypto.byte_array_to_base64(self.new_state_hash) b64_code_hash = crypto.byte_array_to_base64(self.code_hash) txnid = initialize_submitter.submit_ccl_initialize_from_data( self.originator_keys.signing_key, self.originator_keys.verifying_key, self.channel_keys.txn_public, self.enclave_service.enclave_id, self.signature, self.contract_id, b64_message_hash, b64_new_state_hash, self.encrypted_state, b64_code_hash, **extra_params) if txnid: transaction_dependencies.SaveDependency(self.contract_id, b64_new_state_hash, txnid) return txnid
def submit_update_transaction(self, ledger_config, **extra_params): """submit the update transaction to the ledger """ global transaction_dependencies # there must be a previous state hash if this is # an update assert self.old_state_hash update_submitter = Submitter(ledger_config['LedgerURL'], key_str=self.channel_keys.txn_private) b64_message_hash = crypto.byte_array_to_base64(self.message_hash) b64_new_state_hash = crypto.byte_array_to_base64(self.new_state_hash) b64_old_state_hash = crypto.byte_array_to_base64(self.old_state_hash) # convert contract dependencies into transaction dependencies # to ensure that the sawtooth validator does not attempt to # re-order the transactions since it is unaware of the semantics # of the contract dependencies txn_dependencies = set() if extra_params.get('transaction_dependency_list'): txn_dependencies.update( extra_params['transaction_dependency_list']) txnid = transaction_dependencies.FindDependency( ledger_config, self.contract_id, b64_old_state_hash) if txnid: txn_dependencies.add(txnid) for dependency in self.dependencies: contract_id = dependency['contract_id'] state_hash = dependency['state_hash'] txnid = transaction_dependencies.FindDependency( ledger_config, contract_id, state_hash) if txnid: txn_dependencies.add(txnid) if txn_dependencies: extra_params['transaction_dependency_list'] = list( txn_dependencies) # now send off the transaction to the ledger txnid = update_submitter.submit_ccl_update_from_data( self.originator_keys.verifying_key, self.channel_keys.txn_public, self.enclave_service.enclave_id, self.signature, self.contract_id, b64_message_hash, b64_new_state_hash, b64_old_state_hash, self.encrypted_state, self.dependencies, **extra_params) if txnid: transaction_dependencies.SaveDependency(self.contract_id, b64_new_state_hash, txnid) return txnid
def evaluate(self) : """ evaluate the request using the enclave service """ encrypted_session_key = self.__encrypt_session_key() # Encrypt the request serialized_byte_array = crypto.string_to_byte_array(self.__serialize_for_encryption()) encrypted_request_raw = crypto.SKENC_EncryptMessage(self.session_key, serialized_byte_array) encrypted_request = crypto.byte_array_to_base64(encrypted_request_raw) try : self.contract_state.push_state_to_eservice(self.enclave_service) encoded_encrypted_response = self.enclave_service.send_to_contract(encrypted_session_key, encrypted_request) logger.debug("raw response from enclave: %s", encoded_encrypted_response) except Exception as e: logger.warn('contract invocation failed; %s', str(e)) raise InvocationException('contract invocation failed') from e try : decrypted_response = self.__decrypt_response(encoded_encrypted_response) response_string = crypto.byte_array_to_string(decrypted_response) response_parsed = json.loads(response_string[0:-1]) logger.debug("parsed response: %s", response_parsed) contract_response = ContractResponse(self, response_parsed) except Exception as e: logger.warn('contract response is invalid; %s', str(e)) raise InvocationException('contract response is invalid') from e return contract_response
def encrypt(self, message, encoding='raw'): """ encrypt a message to send privately to the enclave :param message: text to encrypt :param encoding: encoding for the encrypted cipher text, one of raw, hex, b64 """ if type(message) is bytes: message_byte_array = message elif type(message) is tuple: message_byte_array = message else: message_byte_array = bytes(message, 'ascii') encrypted_byte_array = self._encryption_key.EncryptMessage( message_byte_array) if encoding == 'raw': encoded_bytes = encrypted_byte_array elif encoding == 'hex': encoded_bytes = crypto.byte_array_to_hex(encrypted_byte_array) elif encoding == 'b64': encoded_bytes = crypto.byte_array_to_base64(encrypted_byte_array) else: raise ValueError('unknown encoding; {0}'.format(encoding)) logger.debug("message: %s", message) logger.debug("encrypted message: %s", encoded_bytes) return encoded_bytes
def __submit_update_transaction__(response, ledger_config, **extra_params): """submit the update transaction to the ledger """ if response.status is False: raise Exception('attempt to submit failed update transaction') # there must be a previous state hash if this is # an update assert response.old_state_hash update_submitter = create_submitter(ledger_config) # now send off the transaction to the ledgerchannel_keys.txn_public, txnid = update_submitter.ccl_update( response.channel_keys, response.enclave_service.enclave_id, response.signature, response.contract_id, response.message_hash, response.new_state_hash, response.old_state_hash, response.dependencies, **extra_params) if txnid: __lock_for_dependencies__.acquire() __dependencies__.SaveDependency(response.contract_id, \ crypto.byte_array_to_base64(response.new_state_hash), txnid) __lock_for_dependencies__.release() return txnid
def sign(self, message, encoding='hex'): """ sign a message from the agent :param message: the message for verification, no encoding :param encoding: the encoding used for the signature; one of raw, hex, b64 """ if type(message) is bytes: message_byte_array = message elif type(message) is tuple: message_byte_array = message else: message_byte_array = bytes(message, 'ascii') signature = self._signing_key.SignMessage(message_byte_array) if encoding == 'raw': encoded_signature = signature elif encoding == 'hex': encoded_signature = crypto.byte_array_to_hex(signature) elif encoding == 'b64': encoded_signature = crypto.byte_array_to_base64(signature) else: raise ValueError('unknown encoding; {0}'.format(encoding)) logger.debug("message: %s", message) logger.debug("signature: %s", encoded_signature) return encoded_signature
def from_transaction_signature_to_id(transaction_signature): """function to transform a hex transaction signature (or transaction identifier) into a base64 id which is used (for instance) for a contract id """ id = crypto.byte_array_to_base64( crypto.compute_message_hash( crypto.hex_to_byte_array(transaction_signature))) return id
def __submit_update_transaction__(response, ledger_config, **extra_params): """submit the update transaction to the ledger """ if response.status is False: raise Exception('attempt to submit failed update transaction') # there must be a previous state hash if this is # an update assert response.old_state_hash update_submitter = Submitter(ledger_config['LedgerURL'], key_str=response.channel_keys.txn_private) b64_message_hash = crypto.byte_array_to_base64(response.message_hash) b64_new_state_hash = crypto.byte_array_to_base64(response.new_state_hash) b64_old_state_hash = crypto.byte_array_to_base64(response.old_state_hash) # get transaction dependency list from the input txn_dependencies = set() if extra_params.get('transaction_dependency_list'): txn_dependencies.update(extra_params['transaction_dependency_list']) raw_state = response.raw_state try: raw_state = raw_state.decode() except AttributeError: pass # now send off the transaction to the ledger txnid = update_submitter.submit_ccl_update_from_data( response.originator_keys.verifying_key, response.channel_keys.txn_public, response.enclave_service.enclave_id, response.signature, response.contract_id, b64_message_hash, b64_new_state_hash, b64_old_state_hash, raw_state, response.dependencies, **extra_params) if txnid: __lock_for_dependencies__.acquire() __dependencies__.SaveDependency(response.contract_id, b64_new_state_hash, txnid) __lock_for_dependencies__.release() return txnid
def evaluate(self): encrypted_session_key = self.__encrypt_session_key() # Encrypt the request serialized_byte_array = crypto.string_to_byte_array( self.__serialize_for_encryption()) encrypted_request_raw = crypto.SKENC_EncryptMessage( self.session_key, serialized_byte_array) encrypted_request = crypto.byte_array_to_base64(encrypted_request_raw) try: # Check and conditionally put the encrypted state into the block store if it is non-empty state_hash_b64 = self.contract_state.getStateHash(encoding='b64') if state_hash_b64: block_store_len = self.enclave_service.block_store_head( state_hash_b64) if block_store_len <= 0: # This block wasn't present in the block store of this enclave service - need to send it logger.debug( "Block store did not contain block '%s' - sending it", state_hash_b64) ret = self.enclave_service.block_store_put( state_hash_b64, self.contract_state.encrypted_state) if ret != True: logger.exception("block_store_put failed for key %s", state_hash_b64) raise encoded_encrypted_response = self.enclave_service.send_to_contract( encrypted_session_key, encrypted_request) if encoded_encrypted_response == None: logger.exception( "send_to_contract failed but no exception was thrown") raise logger.debug("raw response from enclave: %s", encoded_encrypted_response) except: logger.exception('contract invocation failed') raise try: decrypted_response = self.__decrypt_response( encoded_encrypted_response) response_string = crypto.byte_array_to_string(decrypted_response) response_parsed = json.loads(response_string[0:-1]) logger.debug("parsed response: %s", response_parsed) contract_response = ContractResponse(self, response_parsed) except Exception as e: logger.exception('contract response is invalid: ' + str(e)) raise return contract_response
def make_channel_keys(self, ledger_type=os.environ.get('PDO_LEDGER_TYPE')): if ledger_type == 'sawtooth': self.channel_keys = keys.TransactionKeys() self.channel_id = self.channel_keys.txn_public elif ledger_type == 'ccf': self.channel_keys = crypto.random_bit_string(64) # byte array self.channel_id = crypto.byte_array_to_base64( crypto.compute_message_hash(self.channel_keys)) else: raise Exception( "Invalid Ledger Type. Must be either sawtooth or ccf.")
def mark_as_failed(self): # mark txn_id as None in the dependency cache __lock_for_dependencies__.acquire() __dependencies__.SaveDependency( self.commit_id[0], crypto.byte_array_to_base64(self.commit_id[1]), None) __lock_for_dependencies__.release() # mark as failed in the request itself so that the application may query the status. Also, mark the task as completed self.is_failed = True self.mark_as_completed()
def compute_hash(self, encoding = 'raw') : serialized = self.__serialize_for_hashing() code_hash = crypto.compute_message_hash(crypto.string_to_byte_array(serialized)) if encoding == 'raw' : return code_hash elif encoding == 'hex' : return crypto.byte_array_to_hex(code_hash) elif encoding == 'b64' : return crypto.byte_array_to_base64(code_hash) else : raise ValueError('unknown hash encoding; {}'.format(encoding))
def make_channel_keys(self, ledger_type=os.environ.get('PDO_LEDGER_TYPE')): if ledger_type=='sawtooth': self.channel_keys = keys.TransactionKeys() self.channel_id = self.channel_keys.txn_public elif ledger_type=='ccf': ## the channel keys for CCF are really just a uniquifier since we don't ## need to hide the submitters ID (since CCF runs inside SGX) seed = crypto.random_bit_string(32) self.channel_id = crypto.byte_array_to_base64(seed) self.channel_keys = crypto.string_to_byte_array(self.channel_id) else: raise Exception("Invalid Ledger Type. Must be either sawtooth or ccf.")
def compute_pdo_signature(private_key, tx_signer_public_key, contract_code_hash, provisioning_service_ids_array): k = crypto.SIG_PrivateKey(private_key) message = tx_signer_public_key + contract_code_hash for s in provisioning_service_ids_array: message += s message_byte_array = bytes(message, 'ascii') signature = k.SignMessage(message_byte_array) encoded_signature = crypto.byte_array_to_base64(signature) logger.debug("signed message string:" + message) logger.debug("signature: %s", encoded_signature) return encoded_signature
def ccl_update(self, channel_keys, contract_enclave_id, enclave_signature, contract_id, message_hash, current_state_hash, previous_state_hash, dependency_list, **extra_params): json_input = JsonPayloadBuilder.build_ccl_transaction_from_data( "", #no creator private key, so no pdo signature included "", #no need for creator public key for update txns 'update', channel_keys.txn_public, contract_enclave_id, enclave_signature, contract_id, crypto.byte_array_to_base64(message_hash), crypto.byte_array_to_base64(current_state_hash), crypto.byte_array_to_base64(previous_state_hash), "", #encyrpted root block. No longer stored in Sawtooth dependency_list, "") #no contract hash because there is no pdo sign extra_params['key_str'] = channel_keys.txn_private return self.submit_json(json_input, json_input['af'], **extra_params)
def compute_hash(encrypted_state, encoding='raw'): """ compute the hash of the encrypted state """ state_byte_array = crypto.base64_to_byte_array(encrypted_state) state_hash = crypto.compute_message_hash(state_byte_array) if encoding == 'raw': return state_hash elif encoding == 'b64': return crypto.byte_array_to_base64(state_hash) elif encoding == 'hex': return crypto.byte_array_to_hex(state_hash) raise ValueError('unknown encoding; {}'.format(encoding))
def compute_hash(raw_state, encoding='raw'): """ compute the hash of the contract state :param raw_state string: root block of contract state, json string """ state_hash = crypto.compute_message_hash(raw_state) if encoding == 'raw': return state_hash elif encoding == 'b64': return crypto.byte_array_to_base64(state_hash) elif encoding == 'hex': return crypto.byte_array_to_hex(state_hash) raise ValueError('unknown encoding; {}'.format(encoding))
def register_contract(self, contract_code_hash, provisioning_service_ids, **extra_params): txn_keys = keys.TransactionKeys() json_input = JsonPayloadBuilder.build_contract_registration_from_data( self.pdo_signer.signing_key, self.pdo_signer.verifying_key, txn_keys.txn_public, crypto.byte_array_to_base64(contract_code_hash), provisioning_service_ids) extra_params['key_str'] = txn_keys.txn_private return self.submit_json(json_input, json_input['af'], **extra_params)
def __submit_initialize_transaction__(response, ledger_config, **extra_params): """submit the initialize transaction to the ledger """ if response.status is False: raise Exception('attempt to submit failed initialization transactions') # an initialize operation has no previous state assert not response.old_state_hash initialize_submitter = Submitter(ledger_config['LedgerURL'], key_str=response.channel_keys.txn_private) b64_message_hash = crypto.byte_array_to_base64(response.message_hash) b64_new_state_hash = crypto.byte_array_to_base64(response.new_state_hash) b64_code_hash = crypto.byte_array_to_base64(response.code_hash) raw_state = response.raw_state try: raw_state = raw_state.decode() except AttributeError: pass txnid = initialize_submitter.submit_ccl_initialize_from_data( response.originator_keys.signing_key, response.originator_keys.verifying_key, response.channel_keys.txn_public, response.enclave_service.enclave_id, response.signature, response.contract_id, b64_message_hash, b64_new_state_hash, raw_state, b64_code_hash, **extra_params) if txnid: __lock_for_dependencies__.acquire() __dependencies__.SaveDependency(response.contract_id, b64_new_state_hash, txnid) __lock_for_dependencies__.release() return txnid
def __init__(self, encryption_key = None, hash_identity = None) : """initialize the key value store encryption_key -- base64 encoded AES encryption key hash_identity -- base64 encoded hash of the root block of the kv store """ if not __block_store_initialized__ : KeyValueInitialize() if encryption_key is None : encryption_key = pcrypto.byte_array_to_base64(pcrypto.SKENC_GenerateKey()) self.encryption_key = encryption_key self.hash_identity = hash_identity self.__handle__ = None
def mark_as_completed(self): __condition_variable_for_completed_transactions__.acquire() self.is_completed = True # add a transaction id field that the application may query for if not self.is_failed: self.txn_id = __dependencies__.FindDependency( self.ledger_config, self.commit_id[0], crypto.byte_array_to_base64(self.commit_id[1])) else: self.txn_id = None #notify parent thread (if waiting) __condition_variable_for_completed_transactions__.notify() __condition_variable_for_completed_transactions__.release()
def __init__(self, ledger_config, commit_id, wait_parameter_for_ledger=30): self.ledger_config = ledger_config self.commit_id = commit_id self.wait = wait_parameter_for_ledger self.is_completed = False self.is_failed = False self.txn_id = None # add a pending status corresponding to the transaction in the dependency cache __lock_for_dependencies__.acquire() __dependencies__.SaveDependency( self.commit_id[0], crypto.byte_array_to_base64(self.commit_id[1]), 'pending') __lock_for_dependencies__.release()
def compute_hash(self, encoding = 'raw') : # the code hash is a combination of the hash of the actual code, # and the hash of the nonce. # this makes it possible to use the nonce to verify the identity # of the actual code (think MRENCLAVE). code_hash = crypto.compute_message_hash(crypto.string_to_byte_array(self.code + self.name)) nonce_hash = crypto.compute_message_hash(crypto.string_to_byte_array(self.nonce)) message = code_hash + nonce_hash code_hash = crypto.compute_message_hash(message) if encoding == 'raw' : return code_hash elif encoding == 'hex' : return crypto.byte_array_to_hex(code_hash) elif encoding == 'b64' : return crypto.byte_array_to_base64(code_hash) else : raise ValueError('unknown hash encoding; {}'.format(encoding))
def build_contract_registration_from_data( contract_creator_keys, contract_code_hash, provisioning_service_ids): payloadblob = dict() payloadblob['contract_code_hash'] = contract_code_hash payloadblob['provisioning_service_ids'] = provisioning_service_ids payloadblob['contract_creator_verifying_key_PEM'] = contract_creator_keys.verifying_key # sign the payload after adding a nonce nonce = time.time().hex() payloadblob['nonce'] = nonce payloadblob['signature'] = compute_pdo_signature_contract_registration(contract_creator_keys.signing_key, contract_creator_keys.verifying_key, contract_code_hash, provisioning_service_ids, nonce) payloadblob['contract_id'] = \ crypto.byte_array_to_base64(crypto.compute_message_hash(payloadblob['signature'])) return payloadblob
def register_contract(ledger_config, creator_keys, contract_code, provisioning_service_ids, **extra_params): txn_keys = keys.TransactionKeys() if 'wait' not in extra_params: extra_params['wait'] = 60 ss = Submitter(ledger_config['LedgerURL'], key_str=txn_keys.txn_private) txnsignature = ss.submit_contract_registration_from_data( creator_keys.signing_key, creator_keys.verifying_key, txn_keys.txn_public, crypto.byte_array_to_base64(contract_code.compute_hash()), provisioning_service_ids, **extra_params) contract_id = putils.from_transaction_signature_to_id(txnsignature) return contract_id
def wait_for_completion(self): """ wait until completion of transaction. If success, return txn_id, else raise Exception""" __condition_variable_for_completed_transactions__.acquire() while self.is_completed is False: __condition_variable_for_completed_transactions__.wait() __condition_variable_for_completed_transactions__.release() if self.is_failed: raise Exception( "Transaction submission failed for request number %d", self.commit_id[2]) txn_id = __dependencies__.FindDependency( self.ledger_config, self.commit_id[0], crypto.byte_array_to_base64(self.commit_id[1])) return txn_id
def verify_state_encryption_key_signature(encrypted_state_key, secret_list, contract_id, creator_id, signature, enclave_keys): """verify the signature on the contract state encryption key received from the enclave :param encrypted_state_key; base64 encoded string :param secret_list: array of dictionary defines values for pspk and encrypted_secret :param contract_id: 16 character, hex encoded, sha256 hashed, registration transaction signature :param creator_id: PEM encoded ECDSA verifying key :param signature: base64 encoded signature :param enclave_keys: object of type :EnclaveKeys: """ message = serialize_for_signing(encrypted_state_key, secret_list, contract_id, creator_id) logger.debug( "signed message has length %d and hash %s", len(message), crypto.byte_array_to_base64(crypto.compute_message_hash(message))) return enclave_keys.verify(message, signature, encoding='b64')
def pull_state_from_eservice(self, eservice, data_dir="./data"): """ push the blocks associated with the state to the eservice :param eservice EnclaveServiceClient object: """ if self.encrypted_state is '': return ContractState.__cache_block_from_eservice__( eservice, self.contract_id, self.get_state_hash(encoding='b64'), data_dir) for hex_block_id in self.component_block_ids: b64_block_id = crypto.byte_array_to_base64( crypto.hex_to_byte_array(hex_block_id)) ContractState.__cache_block_from_eservice__( eservice, self.contract_id, b64_block_id, data_dir)