Microsoft Azure Confidential Ledger Python客户端库
项目描述
Azure Confidential Ledger Python客户端库
Azure Confidential Ledger提供了一个将日志记录到不可变、防篡改账本的服务的服务。作为Azure Confidential Computing产品组合的一部分,Azure Confidential Ledger在基于硬件的安全可信执行环境中运行,也称为enclaves。它是基于微软研究Confidential Consortium Framework构建的。
源代码 | 包(PyPI) | 包(Conda) | API参考文档 | 产品文档
入门指南
安装软件包
使用pip安装azure-confidentialledger和azure-identity
pip install azure-identity azure-confidentialledger
azure-identity 用于 Azure Active Directory 身份验证,如下所示。
先决条件
- Azure 订阅
- Python 3.6 或更高版本
- Azure 保密账本运行实例。
- 保密账本中的注册用户,通常在 ARM 资源创建期间分配,具有
管理员
权限。
客户端认证
使用 Azure Active Directory
本文档演示了使用 DefaultAzureCredential 通过 Azure Active Directory 认证到保密账本。然而,ConfidentialLedgerClient
接受任何 azure-identity 凭证。有关其他凭证的更多信息,请参阅 azure-identity 文档。
使用客户端证书
作为 Azure Active Directory 的替代方案,客户端可以选择使用客户端证书通过相互 TLS 进行认证。可以使用 azure.confidentialledger.ConfidentialLedgerCertificateCredential
来实现此目的。
创建客户端
DefaultAzureCredential
将自动处理大多数 Azure SDK 客户端场景。要开始,请设置与您的保密账本注册的 AAD 身份的环境变量。
export AZURE_CLIENT_ID="generated app id"
export AZURE_CLIENT_SECRET="random password"
export AZURE_TENANT_ID="tenant id"
然后,DefaultAzureCredential
将能够认证 ConfidentialLedgerClient
。
构建客户端还需要您的保密账本的 URL 和 id,您可以从 Azure CLI 或 Azure 门户获取这些值。当您获取这些值后,请将以下示例中的 "my-ledger-id"
和 "https://my-ledger-id.confidential-ledger.azure.com"
实例替换为这些值。您可能还需要将 "https://identity.confidential-ledger.core.azure.com"
替换为您的账本的 ARM 描述中的 identityServiceUri
的主机名。
由于保密账本使用安全生成并存储在加密区域中的自签名证书,因此必须首先从保密账本身份服务中检索每个保密账本的签名证书。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
方便的是,如果提供不存在的文件,ConfidentialLedgerClient
构造函数将检索账本 TLS 证书(并将其写入指定的文件)。用户负责根据需要删除创建的文件。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path="ledger_certificate.pem"
)
# The ledger TLS certificate is written to `ledger_certificate.pem`.
为了清楚地表明正在使用文件作为账本 TLS 证书,后续示例将明确将账本 TLS 证书写入文件。
关键概念
账本条目和事务
对 Azure 保密账本的每次写入都会在服务中生成一个不可变的账本条目。写入(也称为事务)由随每次写入递增的事务 id 唯一标识。一旦写入,账本条目可以随时检索。
集合
虽然大多数用例只涉及每个保密账本一个集合,但我们提供了集合 id 功能,以便在语义上或逻辑上不同的数据组需要存储在同一个保密账本中。
通过 collectionId
获取账本条目。保密账本将始终假设没有指定 collectionId
的条目使用由服务确定的常量 collectionId
。
用户
用户直接通过保密账本进行管理,而不是通过 Azure。用户可以是基于 AAD 的,通过他们的 AAD 对象 id 识别,也可以是基于证书的,通过他们的 PEM 证书指纹识别。
收据
为了确保事务完整性,Azure保密账本使用[Merkle树][merkle_tree_wiki]数据结构来记录附加到不可变账本的所有事务块的哈希值。在写事务提交后,Azure保密账本用户可以获得加密的Merkle证明或收据,以验证写入操作是否正确保存。写事务收据是系统已提交相应事务的证明,可用于验证条目是否已有效附加到账本。
有关Azure保密账本写事务收据的更多信息,请参阅以下文章。
收据验证
在获得写事务收据后,Azure保密账本用户可以使用验证算法验证获取的收据内容。验证成功是证明与收据关联的写操作已被正确附加到不可变账本的证据。
有关Azure保密账本写事务收据验证过程的更多信息,请参阅以下文章。
应用程序声明
Azure保密账本应用程序可以将任意数据(称为应用程序声明)附加到写事务中。这些声明代表写操作期间执行的操作。当附加到事务时,声明对象的SHA-256摘要包含在账本中,并作为写事务的一部分提交。这保证了摘要已原地签名且不可篡改。
稍后,应用程序声明可以在对应于添加它们的同一事务的收据有效负载中以未摘要的形式披露。这允许用户利用收据中的信息重新计算Azure保密账本实例在事务期间附加和签名的同一声明摘要。声明摘要可以用作写事务收据验证过程的一部分,为用户提供离线方式完全验证记录声明的真实性。
有关应用程序声明格式和摘要计算算法的更多详细信息,请参阅以下链接
有关CCF应用程序声明的更多信息,请参阅以下CCF文档页面
保密计算
Azure保密计算允许您在云中处理数据时隔离和保护您的数据。Azure保密账本在Azure保密计算虚拟机上运行,因此通过加密使用中的数据提供了更强的数据保护。
保密联盟框架
Azure保密账本建立在微软研究院开源的保密联盟框架(CCF)之上。在CCF下,应用程序由一个成员联盟管理,这些成员有权提交提案以修改和治理应用程序操作。在Azure保密账本中,微软Azure拥有一个操作成员身份,允许它执行治理和维护操作,如替换保密账本中的不健康节点和升级enclave代码。
示例
本节包含覆盖常见任务的代码片段,包括
附加条目
需要以防篡改的方式不可变存储的数据可以通过将条目附加到账本保存到Azure保密账本。
由于保密账本是分布式系统,罕见的短暂故障可能会导致写入丢失。对于必须保留的条目,建议验证写入是否已持久化。对于不太重要的写入,如果更偏好更高的客户端吞吐量,可以跳过等待步骤。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
post_entry_result = ledger_client.create_ledger_entry(
{"contents": "Hello world!"}
)
transaction_id = post_entry_result["transactionId"]
wait_poller = ledger_client.begin_wait_for_commit(transaction_id)
wait_poller.wait()
print(f'Ledger entry at transaction id {transaction_id} has been committed successfully')
或者,客户端可以在写入账本条目时等待提交。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
post_poller = ledger_client.begin_create_ledger_entry(
{"contents": "Hello world again!"}
)
new_post_result = post_poller.result()
print(
'The new ledger entry has been committed successfully at transaction id '
f'{new_post_result["transactionId"]}'
)
检索账本条目
由于服务正在加载历史条目,获取比最新条目更旧的条目可能需要一些时间,因此提供了轮询器。
通过集合检索账本条目。返回值是在事务ID标识的时间点指定集合中包含的值。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
post_poller = ledger_client.begin_create_ledger_entry(
{"contents": "Original hello"}
)
post_result = post_poller.result()
post_transaction_id = post_result["transactionId"]
latest_entry = ledger_client.get_current_ledger_entry()
print(
f'Current entry (transaction id = {latest_entry["transactionId"]}) '
f'in collection {latest_entry["collectionId"]}: {latest_entry["contents"]}'
)
post_poller = ledger_client.begin_create_ledger_entry(
{"contents": "Hello!"}
)
post_result = post_poller.result()
get_entry_poller = ledger_client.begin_get_ledger_entry(post_transaction_id)
older_entry = get_entry_poller.result()
print(
f'Contents of {older_entry["entry"]["collectionId"]} at {post_transaction_id}: {older_entry["entry"]["contents"]}'
)
执行范围查询
可以检索一系列事务ID的账本条目。只有从默认或指定的集合中返回条目。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
post_poller = ledger_client.begin_create_ledger_entry(
{"contents": "First message"}
)
first_transaction_id = post_poller.result()["transactionId"]
for i in range(10):
ledger_client.create_ledger_entry(
{"contents": f"Message {i}"}
)
post_poller = ledger_client.begin_create_ledger_entry(
{"contents": "Last message"}
)
last_transaction_id = post_poller.result()["transactionId"]
ranged_result = ledger_client.list_ledger_entries(
from_transaction_id=first_transaction_id,
to_transaction_id=last_transaction_id,
)
for entry in ranged_result:
print(f'Contents at {entry["transactionId"]}: {entry["contents"]}')
管理用户
具有管理员
权限的用户可以直接使用保密账本来管理保密账本的用户。可用的角色有读取者
(只读)、贡献者
(读写)和管理员
(读写、添加或删除用户)。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
user_id = "some AAD object id"
user = ledger_client.create_or_update_user(
user_id, {"assignedRole": "Contributor"}
)
# A client may now be created and used with AAD credentials (i.e. AAD-issued JWT tokens) for the user identified by `user_id`.
user = ledger_client.get_user(user_id)
assert user["userId"] == user_id
assert user["assignedRole"] == "Contributor"
ledger_client.delete_user(user_id)
# For a certificate-based user, their user ID is the fingerprint for their PEM certificate.
user_id = "PEM certificate fingerprint"
user = ledger_client.create_or_update_user(
user_id, {"assignedRole": "Reader"}
)
user = ledger_client.get_user(user_id)
assert user["userId"] == user_id
assert user["assignedRole"] == "Reader"
ledger_client.delete_user(user_id)
使用证书身份验证
客户端可以通过相互TLS中的客户端证书而不是通过Azure Active Directory令牌进行身份验证。ConfidentialLedgerCertificateCredential
为这类客户端提供。
from azure.confidentialledger import (
ConfidentialLedgerCertificateCredential,
ConfidentialLedgerClient,
)
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = ConfidentialLedgerCertificateCredential(
certificate_path="Path to user certificate PEM file"
)
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
验证写事务收据
客户端可以利用SDK中的收据验证库来验证由Azure保密账本实例发出的写入事务收据。此实用程序可以用于离线完全验证收据,因为验证算法不需要连接到保密账本或任何其他Azure服务。
一旦将新条目附加到账本中(请参阅此示例),就可以获取提交的写入事务的收据。
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
# Replace this with the Confidential Ledger ID
ledger_id = "my-ledger-id"
# Setup authentication
credential = DefaultAzureCredential()
# Create a Ledger Certificate client and use it to
# retrieve the service identity for our ledger
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id=ledger_id
)
# Save ledger service certificate into a file for later use
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
# Create Confidential Ledger client
ledger_client = ConfidentialLedgerClient(
endpoint=f"https://{ledger_id}.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
# The method begin_get_receipt returns a poller that
# we can use to wait for the receipt to be available for retrieval
get_receipt_poller = ledger_client.begin_get_receipt(transaction_id)
get_receipt_result = get_receipt_poller.result()
print(f"Write receipt for transaction id {transaction_id} was successfully retrieved: {get_receipt_result}")
在获取写入事务的收据后,可以调用verify_receipt
函数来验证收据是否有效。该函数可以接受一个可选的应用声明列表,以验证收据声明摘要。
from azure.confidentialledger.receipt import (
verify_receipt,
)
# Read contents of service certificate file saved in previous step.
with open(ledger_tls_cert_file_name, "r") as service_cert_file:
service_cert_content = service_cert_file.read()
# Optionally read application claims, if any
application_claims = get_receipt_result.get("applicationClaims", None)
try:
# Verify the contents of the receipt.
verify_receipt(get_receipt_result["receipt"], service_cert_content, application_claims=application_claims)
print(f"Receipt for transaction id {transaction_id} successfully verified")
except ValueError:
print(f"Receipt verification for transaction id {transaction_id} failed")
一个完整的Python示例程序,展示了如何将新条目追加到运行的保密账本实例中,获取提交事务的收据以及验证收据内容,可以在示例文件夹下找到:get_and_verify_receipt.py。
异步API
此库包括一个完整的异步API,支持Python 3.5+。要使用它,您必须首先安装一个异步传输,例如aiohttp。有关更多信息,请参阅azure-core文档。
从azure.confidentialledger.aio
获取异步客户端。方法具有与同步客户端相同的名称和签名。样本可在此处找到:这里。
故障排除
通用
保密账本客户端引发在azure-core中定义的异常。例如,如果您尝试获取不存在的交易,则ConfidentialLedgerClient
引发ResourceNotFoundError
from azure.core.exceptions import ResourceNotFoundError
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name
)
try:
ledger_client.begin_get_ledger_entry(
transaction_id="10000.100000" # Using a very high id that probably doesn't exist in the ledger if it's relatively new.
)
except ResourceNotFoundError as e:
print(e.message)
日志记录
此库使用标准的logging库进行日志记录。HTTP会话的基本信息(URL、标题等)以INFO级别进行记录。
可以通过客户端的logging_enable
参数启用详细的DEBUG级别日志记录,包括请求/响应体和未脱敏的标题。
import logging
import sys
from azure.confidentialledger import ConfidentialLedgerClient
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient
from azure.identity import DefaultAzureCredential
# Create a logger for the 'azure' SDK
logger = logging.getLogger('azure')
logger.setLevel(logging.DEBUG)
# Configure a console output
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)
identity_client = ConfidentialLedgerCertificateClient()
network_identity = identity_client.get_ledger_identity(
ledger_id="my-ledger-id"
)
ledger_tls_cert_file_name = "ledger_certificate.pem"
with open(ledger_tls_cert_file_name, "w") as cert_file:
cert_file.write(network_identity["ledgerTlsCertificate"])
credential = DefaultAzureCredential()
# This client will log detailed information about its HTTP sessions, at DEBUG level.
ledger_client = ConfidentialLedgerClient(
endpoint="https://my-ledger-id.confidential-ledger.azure.com",
credential=credential,
ledger_certificate_path=ledger_tls_cert_file_name,
logging_enable=True,
)
同样,即使没有为客户端启用,logging_enable
也可以为单个操作启用详细日志。
ledger_client.get_current_ledger_entry(logging_enable=True)
下一步
更多示例代码
以下代码示例展示了与 Azure 密集型账本客户端库的常见场景操作。
常见场景
-
写入账本
-
写入多个账本条目并在之后检索它们
-
使用服务实现的基于角色的访问控制管理用户
高级场景
-
使用集合
-
获取账本写入的收据
-
验证服务详情
其他文档
有关 Azure 密集型账本的更多详细文档,请参阅API 参考文档。您还可以了解更多关于微软研究开源的Confidential Consortium Framework。
贡献
本项目欢迎贡献和建议。大多数贡献需要您同意贡献者许可协议(CLA),声明您有权并将实际授予我们使用您贡献的权利。有关详细信息,请访问https://cla.microsoft.com。
当您提交拉取请求时,CLA-bot 将自动确定您是否需要提供 CLA 并相应地装饰 PR(例如,标签、注释)。只需遵循 bot 提供的说明即可。您只需要在整个使用我们 CLA 的所有存储库中做一次。
本项目采用了微软开源行为准则。有关更多信息,请参阅行为准则常见问题解答或联系opencode@microsoft.com以获取任何其他问题或评论。
发布历史
1.1.1 (2023-08-01)
修复的 Bug
- 在
begin_wait_for_commit
中允许一些ResourceNotFoundError
发生,以考虑会话粘性意外丢失的情况。当连接的节点更改且事务尚未完全复制时,可能会发生这些错误。
1.1.0 (2023-05-09)
新增功能
- 添加
azure.confidentialledger.receipt
模块以用于 Azure 密集型账本写入事务收据验证。 - 添加
verify_receipt
函数以验证来自收据 JSON 对象的写入事务收据。该函数接受一个可选的、关键字参数的列表,即应用声明参数,可以用于从给定的声明计算声明摘要:如果计算的摘要值与收据中存在的claimsDigest
值不匹配,则验证会失败。 - 添加
compute_claims_digest
函数以从应用声明 JSON 对象列表计算声明摘要。 - 添加示例代码以从正在运行的密集型账本实例获取和验证写入收据。
- 更新 README 以包含收据验证和应用声明的示例和文档。
其他更改
- 添加对 Python
cryptography
库的依赖(>= 2.1.4
) - 添加对收据验证模型和收据验证公共方法的测试。
- 为应用程序声明模型和摘要计算公共方法添加测试。
1.0.0 (2022-07-19)
Confidential Ledger 的 GA 数据平面 Python SDK。
修复的 Bug
- 用户 ID 为证书指纹的不再在请求 URI 中进行 URL 编码。
重大更改
- 已移除所有模型。现在方法直接返回 JSON。
sub_ledger_id
字段现在命名为collection_id
。azure.confidentialledger.identity_service
已重命名为azure.confidentialledger.certificate
。ConfidentialLedgerIdentityServiceClient
现在是ConfidentialLedgerCertificateClient
。post_ledger_entry
已重命名为create_ledger_entry
。
其他更改
- 不再支持 Python 2.7。请使用 Python 3.7 或更高版本。
- 为某些长时间运行的操作添加了方便的轮询方法。
- 添加了新的支持 API 版本:
2022-05-13
。
1.0.0b1 (2021-05-12)
- 这是 Azure Confidential Ledger 库的初始版本。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。
源分发
构建分发
azure-confidentialledger-1.1.1.zip 的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 8a8ee6103e6f6de8a3c5bb952542d4e4002066c7e3973a9278011c1515850a70 |
|
MD5 | 748cb35fc1a22d8fae06a8be0e16b63f |
|
BLAKE2b-256 | c7e55e07345475de8edc74c9620145b12ce8d9f4502697947fa909f531ba419f |
azure_confidentialledger-1.1.1-py3-none-any.whl 的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | f6cc82c425f3313cc02560780b066d63c56b99199bd1e35c7cb34dfbdfb033a0 |
|
MD5 | 29fe882faaa3be1ff8e9a4c17cfb6c8f |
|
BLAKE2b-256 | e7d9197b4c2328fde0e6b5979fcfcdd010d32be9897e64010fb136599e4b2fb4 |