Partilhar via


O Razão Confidencial do Azure grava recibos de transação

Para impor garantias de integridade de transação, um Razão Confidencial do Azure usa uma estrutura de dados de árvore Merkle para registrar o hash de todos os blocos de transações que são anexados ao razão imutável. Depois que uma transação de gravação é confirmada, os usuários do Razão Confidencial do Azure podem obter uma prova ou recibo criptográfico do Merkle sobre a entrada produzida em um Razão Confidencial para verificar se a operação de gravação foi salva corretamente. Um recibo de transação de gravação é a prova de que o sistema confirmou a transação correspondente e pode ser usado para verificar se a entrada foi efetivamente anexada ao livro-razão.

Mais detalhes sobre como uma árvore Merkle é usada em um livro razão confidencial podem ser encontrados na documentação do CCF.

Obter recibos de transação de gravação

Configuração e pré-requisitos

Os usuários do Razão Confidencial do Azure podem obter um recibo de uma transação específica usando a biblioteca de cliente do Razão Confidencial do Azure. O exemplo a seguir mostra como obter um recibo de gravação usando a biblioteca de cliente para Python, mas as etapas são as mesmas com qualquer outro SDK suportado para o Azure Confidential Ledger.

Supomos que um recurso de Razão Confidencial já tenha sido criado usando a biblioteca de Gerenciamento de Razão Confidencial do Azure. Se você ainda não tiver um recurso de contabilidade existente, crie um usando as instruções a seguir.

Instruções de código

Começamos por configurar as importações para o nosso programa Python.

import json 

# Import the Azure authentication library 
from azure.identity import DefaultAzureCredential 

# Import the Confidential Ledger Data Plane SDK 
from azure.confidentialledger import ConfidentialLedgerClient 
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient 

A seguir estão os valores constantes usados para configurar o cliente do Razão Confidencial do Azure. Certifique-se de atualizar a ledger_name constante com o nome exclusivo do seu recurso Razão Confidencial.

# Constants for our program 
ledger_name = "<your-unique-ledger-name>" 
identity_url = "https://identity.confidential-ledger.core.azure.com" 
ledger_url = "https://" + ledger_name + ".confidential-ledger.azure.com" 

Nós autenticamos usando a classe DefaultAzureCredential.

# Setup authentication 
credential = DefaultAzureCredential() 

Em seguida, obtemos e salvamos o certificado de serviço do Livro-razão Confidencial usando o cliente de Certificado da URL de Identidade do Livro-razão Confidencial. O certificado de serviço é um certificado de chave pública de identidade de rede usado como raiz de confiança para autenticação do servidor TLS . Em outras palavras, ele é usado como a Autoridade de Certificação (CA) para estabelecer uma conexão TLS com qualquer um dos nós na rede CCF.

# Create a Certificate client and use it to 
# get the service identity for our ledger 
identity_client = ConfidentialLedgerCertificateClient(identity_url) 
network_identity = identity_client.get_ledger_identity( 
     ledger_id=ledger_name 
)

# Save network certificate into a file for later use 
ledger_tls_cert_file_name = "network_certificate.pem" 

with open(ledger_tls_cert_file_name, "w") as cert_file: 
    cert_file.write(network_identity["ledgerTlsCertificate"]) 

Em seguida, podemos usar nossas credenciais, o certificado de rede buscado e nossa URL de razão exclusiva para criar um cliente de razão confidencial.

# Create Confidential Ledger client 
ledger_client = ConfidentialLedgerClient( 
     endpoint=ledger_url,  
     credential=credential, 
     ledger_certificate_path=ledger_tls_cert_file_name 
) 

Usando o cliente Razão Confidencial, podemos executar quaisquer operações com suporte em uma instância do Razão Confidencial do Azure. Por exemplo, podemos acrescentar uma nova entrada ao livro razão e aguardar que a transação de gravação correspondente seja confirmada.

# The method begin_create_ledger_entry returns a poller that  
# we can use to wait for the transaction to be committed 
create_entry_poller = ledger_client.begin_create_ledger_entry( 
    {"contents": "Hello World!"} 
)

create_entry_result = create_entry_poller.result() 

Depois que a transação é confirmada, podemos usar o cliente para obter um recibo sobre a entrada anexada ao livro razão na etapa anterior usando o respetivo ID da transação.

# The method begin_get_receipt returns a poller that  
# we can use to wait for the receipt to be available by the system 
get_receipt_poller = ledger_client.begin_get_receipt( 
    create_entry_result["transactionId"] 
)

get_receipt_result = get_receipt_poller.result() 

Código de exemplo

O código de exemplo completo usado no passo a passo do código é fornecido.

import json 

# Import the Azure authentication library 
from azure.identity import DefaultAzureCredential 

# Import the Confidential Ledger Data Plane SDK 
from azure.confidentialledger import ConfidentialLedgerClient 
from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient 

from receipt_verification import verify_receipt 

# Constants 
ledger_name = "<your-unique-ledger-name>" 
identity_url = "https://identity.confidential-ledger.core.azure.com" 
ledger_url = "https://" + ledger_name + ".confidential-ledger.azure.com" 

# Setup authentication 
credential = DefaultAzureCredential() 

# Create Ledger Certificate client and use it to 
# retrieve the service identity for our ledger 
identity_client = ConfidentialLedgerCertificateClient(identity_url) 
network_identity = identity_client.get_ledger_identity(ledger_id=ledger_name) 

# Save network certificate into a file for later use 
ledger_tls_cert_file_name = "network_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=ledger_url, 
    credential=credential, 
    ledger_certificate_path=ledger_tls_cert_file_name, 
) 

# The method begin_create_ledger_entry returns a poller that 
# we can use to wait for the transaction to be committed 
create_entry_poller = ledger_client.begin_create_ledger_entry( 
    {"contents": "Hello World!"} 
) 
create_entry_result = create_entry_poller.result() 

# The method begin_get_receipt returns a poller that 
# we can use to wait for the receipt to be available by the system 
get_receipt_poller = ledger_client.begin_get_receipt( 
    create_entry_result["transactionId"] 
) 
get_receipt_result = get_receipt_poller.result() 

# Save fetched receipt into a file
with open("receipt.json", "w") as receipt_file: 
    receipt_file.write(json.dumps(get_receipt_result, sort_keys=True, indent=2)) 

Escrever conteúdo de recibo de transação

Aqui está um exemplo de uma carga útil de resposta JSON retornada por uma instância do Razão Confidencial do Azure ao chamar o GET_RECEIPT ponto de extremidade.

{
    "receipt": {
        "cert": "-----BEGIN CERTIFICATE-----\nMIIB0jCCAXmgAwIBAgIQPxdrEtGY+SggPHETin1XNzAKBggqhkjOPQQDAjAWMRQw\nEgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yMjA3MjAxMzUzMDFaFw0yMjEwMTgxMzUz\nMDBaMBMxETAPBgNVBAMMCENDRiBOb2RlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\nQgAEWy81dFeEZ79gVJnfHiPKjZ54fZvDcFlntFwJN8Wf6RZa3PaV5EzwAKHNfojj\noXT4xNkJjURBN7q+1iE/vvc+rqOBqzCBqDAJBgNVHRMEAjAAMB0GA1UdDgQWBBQS\nwl7Hx2VkkznJNkVZUbZy+TOR/jAfBgNVHSMEGDAWgBTrz538MGI/SdV8k8EiJl5z\nfl3mBTBbBgNVHREEVDBShwQK8EBegjNhcGljY2lvbmUtdGVzdC1sZWRnZXIuY29u\nZmlkZW50aWFsLWxlZGdlci5henVyZS5jb22CFWFwaWNjaW9uZS10ZXN0LWxlZGdl\ncjAKBggqhkjOPQQDAgNHADBEAiAsGawDcYcH/KzF2iK9Ldx/yABUoYSNti2Cyxum\n9RRNKAIgPB/XGh/FQS3nmZLExgBVXkDYdghQu/NCY/hHjQ9AvWg=\n-----END CERTIFICATE-----\n",
        "leafComponents": {
            "claimsDigest": "0000000000000000000000000000000000000000000000000000000000000000",
            "commitEvidence": "ce:2.40:f36ffe2930ec95d50ebaaec26e2bec56835abd051019eb270f538ab0744712a4",
            "writeSetDigest": "8452624d10bdd79c408c0f062a1917aa96711ea062c508c745469636ae1460be"
        },
        "nodeId": "70e995887e3e6b73c80bc44f9fbb6e66b9f644acaddbc9c0483cfc17d77af24f",
        "proof": [
            {
                "left": "b78230f9abb27b9b803a9cae4e4cec647a3be1000fc2241038867792d59d4bc1"
            },
            {
                "left": "a2835d4505b8b6b25a0c06a9c8e96a5204533ceac1edf2b3e0e4dece78fbaf35"
            }
        ],
        "signature": "MEUCIQCjtMqk7wOtUTgqlHlCfWRqAco+38roVdUcRv7a1G6pBwIgWKpCSdBmhzgEdwguUW/Cj/Z5bAOA8YHSoLe8KzrlqK8="
    },
    "state": "Ready",
    "transactionId": "2.40"
}

A resposta JSON contém os seguintes campos no nível raiz.

  • recibo: Contém os valores que podem ser usados para verificar a validade do recibo para a transação de gravação correspondente.

  • state: O status da resposta JSON retornada. Os valores possíveis permitidos são os seguintes:

    • Ready: O recibo devolvido na resposta está disponível
    • Loading: O recibo ainda não está disponível para ser recuperado e o pedido tem de ser repetido
  • transactionId: O ID da transação associado ao recibo da transação de gravação.

O receipt campo contém os seguintes campos.

  • cert: String com o certificado de chave pública PEM do nó CCF que assinou a transação de gravação. O certificado de identidade de serviço deve sempre endossar o certificado do nó de assinatura. Veja também mais detalhes sobre como as transações são assinadas regularmente e como as transações de assinatura são anexadas ao livro razão no CCF no link a seguir.

  • nodeId: Cadeia de caracteres hexadecimal que representa o resumo de hash SHA-256 da chave pública do nó CCF de assinatura.

  • leafComponents: Os componentes do hash do nó folha na árvore Merkle que estão associados à transação especificada. Uma árvore Merkle é uma estrutura de dados em árvore que registra o hash de cada transação e garante a integridade do livro-razão. Para obter mais informações sobre como uma árvore Merkle é usada no CCF, consulte a documentação relacionada do CCF.

  • proof: Lista de pares chave-valor que representam os hashes dos nós da árvore Merkle que, quando combinados com o hash do nó folha correspondente à transação dada, permitem o recálculo do hash raiz da árvore. Graças às propriedades de uma árvore Merkle, é possível recalcular o hash raiz da árvore apenas um subconjunto de nós. Os elementos nesta lista estão na forma de pares chave-valor: as chaves indicam a posição relativa em relação ao nó pai na árvore em um determinado nível; valores são os resumos de hash SHA-256 do nó fornecido, como cadeias hexadecimais.

  • serviceEndorsements: Lista de cadeias de caracteres de certificados codificados em PEM que representam certificados de identidades de serviço anteriores. É possível que a identidade de serviço que endossou o nó de assinatura não seja a mesma que emitiu o recibo. Por exemplo, o certificado de serviço é renovado após uma recuperação de desastre de um Razão Confidencial. A lista de certificados de serviço anteriores permite que os auditores criem a cadeia de confiança do nó de assinatura CCF para o certificado de serviço atual.

  • assinatura: Cadeia de caracteres Base64 que representa a assinatura da raiz da árvore Merkle na transação dada, pelo nó CCF de assinatura.

O leafComponents campo contém os seguintes campos.

  • claimsDigest: cadeia de caracteres hexadecimal que representa o resumo de hash SHA-256 da declaração de aplicativo anexada pelo aplicativo Confidential Ledger no momento em que a transação foi executada. As declarações de aplicativo não são suportadas no momento, pois o aplicativo Razão Confidencial não anexa nenhuma reivindicação ao executar uma transação de gravação.

  • commitEvidence: uma cadeia de caracteres exclusiva produzida por transação, derivada do ID da transação e dos segredos do livro-razão. Para obter mais informações sobre as evidências de confirmação, consulte a documentação do CCF relacionada.

  • writeSetDigest: Cadeia de caracteres hexadecimal que representa o resumo de hash SHA-256 do armazenamento Key-Value, que contém todas as chaves e valores gravados no momento em que a transação foi concluída. Para obter mais informações sobre o conjunto de gravação, consulte a documentação relacionada do CCF.

Afirmações da aplicação

Os aplicativos do Razão Confidencial do Azure podem anexar dados arbitrários, chamados declarações de aplicativo, para gravar transações. Essas declarações representam as ações executadas durante uma operação de gravação. Quando anexado a uma transação, o resumo SHA-256 do objeto de declarações é incluído no livro razão e confirmado como parte da transação de gravação. A inclusão da declaração na transação de gravação garante que o resumo da declaração seja assinado no local e não possa ser adulterado.

Mais tarde, as declarações de aplicação podem ser reveladas no seu formato simples na carga útil do recibo correspondente à mesma transação em que foram adicionadas. As declarações expostas permitem que os usuários recalculem o mesmo resumo de declarações que foi anexado e assinado no lugar pelo razão durante a transação. O resumo de declarações pode ser usado como parte do processo de verificação de recibo de transação de gravação, fornecendo uma maneira offline para os usuários verificarem totalmente a autenticidade das declarações registradas.

As declarações de aplicativo são suportadas atualmente na versão 2023-01-18-previewde visualização da API.

Gravar conteúdo de recibo de transação com declarações de aplicativo

Aqui está um exemplo de uma carga útil de resposta JSON retornada por uma instância do Razão Confidencial do Azure que registrou declarações de aplicativo ao chamar o GET_RECEIPT ponto de extremidade.

{
  "applicationClaims": [
    {
      "kind": "LedgerEntry",
      "ledgerEntry": {
        "collectionId": "subledger:0",
        "contents": "Hello world",
        "protocol": "LedgerEntryV1",
        "secretKey": "Jde/VvaIfyrjQ/B19P+UJCBwmcrgN7sERStoyHnYO0M="
      }
    }
  ],
  "receipt": {
    "cert": "-----BEGIN CERTIFICATE-----\nMIIBxTCCAUygAwIBAgIRAMR89lUNeIghDUfpyHi3QzIwCgYIKoZIzj0EAwMwFjEU\nMBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjMwNDI1MTgxNDE5WhcNMjMwNzI0MTgx\nNDE4WjATMREwDwYDVQQDDAhDQ0YgTm9kZTB2MBAGByqGSM49AgEGBSuBBAAiA2IA\nBB1DiBUBr9/qapmvAIPm1o3o3LRViSOkfFVI4oPrw3SodLlousHrLz+HIe+BqHoj\n4nBjt0KAS2C0Av6Q+Xg5Po6GCu99GQSoSfajGqmjy3j3bwjsGJi5wHh1pNbPmMm/\nTqNhMF8wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUCPaDohOGjVgQ2Lb8Pmubg7Y5\nDJAwHwYDVR0jBBgwFoAU25KejcEmXDNnKvSLUwW/CQZIVq4wDwYDVR0RBAgwBocE\nfwAAATAKBggqhkjOPQQDAwNnADBkAjA8Ci9myzieoLoIy+7mUswVEjUG3wrEXtxA\nDRmt2PK9bTDo2m3aJ4nCQJtCWQRUlN0CMCMOsXL4NnfsSxaG5CwAVkDwLBUPv7Zy\nLfSh2oZ3Wn4FTxL0UfnJeFOz/CkDUtJI1A==\n-----END CERTIFICATE-----\n",
    "leafComponents": {
      "claimsDigest": "d08d8764437d09b2d4d07d52293cddaf40f44a3ea2176a0528819a80002df9f6",
      "commitEvidence": "ce:2.13:850a25da46643fa41392750b6ca03c7c7d117c27ae14e3322873de6322aa7cd3",
      "writeSetDigest": "6637eddb8741ab54cc8a44725be67fd9be390e605f0537e5a278703860ace035"
    },
    "nodeId": "0db9a22e9301d1167a2a81596fa234642ad24bc742451a415b8d653af056795c",
    "proof": [
      {
        "left": "bcce25aa51854bd15257cfb0c81edc568a5a5fa3b81e7106c125649db93ff599"
      },
      {
        "left": "cc82daa27e76b7525a1f37ed7379bb80f6aab99f2b36e2e06c750dd9393cd51b"
      },
      {
        "left": "c53a15cbcc97e30ce748c0f44516ac3440e3e9cc19db0852f3aa3a3d5554dfae"
      }
    ],
    "signature": "MGYCMQClZXVAFn+vflIIikwMz64YZGoH71DKnfMr3LXkQ0lhljSsvDrmtmi/oWwOsqy28PsCMQCMe4n9aXXK4R+vY0SIfRWSCCfaADD6teclFCkVNK4317ep+5ENM/5T/vDJf3V4IvI="
  },
  "state": "Ready",
  "transactionId": "2.13"
}

Em comparação com o exemplo de recibo mostrado na seção anterior, a resposta JSON contém outro applicationClaims campo que representa a lista de declarações de aplicativo registradas pelo razão durante a transação de gravação. Cada objeto dentro da applicationClaims lista contém os seguintes campos.

  • tipo: Representa o tipo de reivindicação do pedido. O valor indica como analisar o objeto de declaração do aplicativo para o tipo fornecido.

  • ledgerEntry: Representa uma declaração de aplicativo derivada de dados de entrada do livro-razão. A declaração conteria os dados registrados pelo aplicativo durante uma transação de gravação (por exemplo, o ID de coleta e o conteúdo fornecido pelo usuário) e as informações necessárias para calcular o resumo correspondente ao objeto de declaração única.

  • digest: Representa uma reivindicação de pedido na forma digerida. Este objeto de declaração conteria o resumo pré-calculado pelo aplicativo e o protocolo usado para o cálculo.

O ledgerEntry campo contém os seguintes campos.

  • protocolo: Representa o protocolo a ser usado para calcular o resumo de uma declaração a partir dos dados de declaração fornecidos.

  • collectionId: O identificador da coleção gravada durante a transação de gravação correspondente.

  • conteúdo: O conteúdo do livro razão escrito durante a transação de gravação correspondente.

  • secretKey: Uma chave secreta codificada em base64. Essa chave deve ser usada no algoritmo HMAC com os valores fornecidos na declaração de aplicativo para obter o resumo da declaração.

O digest campo contém os seguintes campos.

  • protocolo: Representa o protocolo usado para calcular o resumo da reivindicação dada.

  • value: O resumo da reivindicação do aplicativo, na forma hexadecimal. Esse valor teria que ser colocado em hash com o protocol valor para calcular o resumo completo da reivindicação do aplicativo.

Mais recursos

Para obter mais informações sobre como gravar recibos de transação e como o CCF garante a integridade de cada transação, consulte os seguintes links:

Próximos passos