Memverifikasi tanda terima transaksi tulis Azure Confidential Ledger

Tanda terima transaksi tulis Azure Confidential Ledger mewakili bukti Merkle kriptografi bahwa transaksi tulis yang sesuai telah dilakukan secara global oleh jaringan CCF. Pengguna Azure Confidential Ledger bisa mendapatkan tanda terima atas transaksi tulis yang diterapkan kapan saja untuk memverifikasi bahwa operasi tulis yang sesuai berhasil direkam ke dalam ledger yang tidak dapat diubah.

Untuk informasi selengkapnya tentang tanda terima transaksi tulis Azure Confidential Ledger, lihat artikel khusus.

Langkah-langkah verifikasi tanda terima

Tanda terima transaksi tulis dapat diverifikasi mengikuti serangkaian langkah tertentu yang diuraikan dalam subbagian berikut. Langkah yang sama diuraikan dalam Dokumentasi CCF.

Komputasi simpul daun

Langkah pertama adalah menghitung hash SHA-256 dari simpul daun di Pohon Merkle yang sesuai dengan transaksi yang dilakukan. Simpul daun terdiri dari perangkaian yang diurutkan dari bidang berikut yang dapat ditemukan dalam tanda terima Azure Confidential Ledger, di bawah leafComponents:

  1. writeSetDigest
  2. Hash SHA-256 dari commitEvidence
  3. claimsDigest Bidang

Nilai-nilai ini perlu digabungkan sebagai array byte: keduanya writeSetDigest dan claimsDigest perlu dikonversi dari string digit heksadesimal ke array byte; di sisi lain, hash commitEvidence (sebagai array byte) dapat diperoleh dengan menerapkan fungsi hash SHA-256 melalui string yang dikodekan commitEvidence UTF-8.

Demikian pula, hash hash simpul daun dapat dihitung dengan menerapkan fungsi hash SHA-256 atas penggalian hasil byte yang dihasilkan.

Komputasi simpul akar

Langkah kedua adalah menghitung hash SHA-256 dari akar Pohon Merkle pada saat transaksi dilakukan. Komputasi dilakukan dengan menggabungkan dan hash hasil iterasi sebelumnya secara berulang (mulai dari hash simpul daun yang dihitung pada langkah sebelumnya) dengan hash simpul yang diurutkan yang disediakan di proof bidang tanda terima. Daftar proof disediakan sebagai daftar yang diurutkan dan elemennya perlu diulang dalam urutan yang diberikan.

Perangkaian perlu dilakukan pada representasi byte sehubungan dengan urutan relatif yang ditunjukkan dalam objek yang disediakan di proof bidang (baik left atau right).

  • Jika kunci elemen saat ini adalah proofleft, maka hasil iterasi sebelumnya harus ditambahkan ke nilai elemen saat ini.
  • Jika kunci elemen saat ini adalah proofright, maka hasil iterasi sebelumnya harus didahului ke nilai elemen saat ini.

Setelah setiap perangkaian, fungsi SHA-256 perlu diterapkan untuk mendapatkan input untuk iterasi berikutnya. Proses ini mengikuti langkah-langkah standar untuk menghitung simpul akar struktur data Merkle Tree mengingat simpul yang diperlukan untuk komputasi.

Memverifikasi tanda tangan melalui simpul akar

Langkah ketiga adalah memverifikasi bahwa tanda tangan kriptografi yang dihasilkan melalui hash simpul akar valid menggunakan sertifikat simpul penandatanganan dalam tanda terima. Proses verifikasi mengikuti langkah-langkah standar untuk verifikasi tanda tangan digital untuk pesan yang ditandatangani menggunakan Elliptic Curve Digital Signature Algorithm (ECDSA). Lebih khusus lagi, langkah-langkahnya adalah:

  1. Dekode string signature base64 ke dalam array byte.
  2. Ekstrak kunci publik ECDSA dari sertifikat certsimpul penandatanganan .
  3. Verifikasi bahwa tanda tangan di atas akar Pohon Merkle (dihitung menggunakan instruksi di subbagian sebelumnya) autentik menggunakan kunci publik yang diekstrak dari langkah sebelumnya. Langkah ini secara efektif sesuai dengan proses verifikasi tanda tangan digital standar menggunakan ECDSA. Ada banyak pustaka dalam bahasa pemrograman paling populer yang memungkinkan memverifikasi tanda tangan ECDSA menggunakan sertifikat kunci publik melalui beberapa data (misalnya, pustaka kriptografi untuk Python).

Memverifikasi penandatanganan dukungan sertifikat simpul

Selain langkah sebelumnya, juga diperlukan untuk memverifikasi bahwa sertifikat simpul penandatanganan didukung (yaitu, ditandatangani) oleh sertifikat ledger saat ini. Langkah ini tidak bergantung pada tiga langkah sebelumnya lainnya dan dapat dilakukan secara independen dari yang lain.

Ada kemungkinan bahwa identitas layanan saat ini yang mengeluarkan tanda terima berbeda dari yang didukung node penandatanganan (misalnya, karena perpanjangan sertifikat). Dalam hal ini, diperlukan untuk memverifikasi rantai kepercayaan sertifikat dari sertifikat simpul penandatanganan (yaitu, cert bidang dalam tanda terima) hingga Otoritas Sertifikat akar (CA) tepercaya (yaitu, sertifikat identitas layanan saat ini) melalui identitas layanan sebelumnya lainnya (yaitu, serviceEndorsements bidang daftar dalam tanda terima). Daftar serviceEndorsements ini disediakan sebagai daftar yang diurutkan dari identitas layanan terlama ke terbaru.

Dukungan sertifikat perlu diverifikasi untuk seluruh rantai dan mengikuti proses verifikasi tanda tangan digital yang sama persis yang diuraikan dalam subbagian sebelumnya. Ada pustaka kriptografi sumber terbuka populer (misalnya, OpenSSL) yang biasanya dapat digunakan untuk melakukan langkah dukungan sertifikat.

Memverifikasi hash klaim aplikasi

Sebagai langkah opsional, jika klaim aplikasi dilampirkan ke tanda terima, dimungkinkan untuk menghitung hash klaim dari klaim yang diekspos (mengikuti algoritma tertentu) dan memverifikasi bahwa hash cocok claimsDigest dengan yang terkandung dalam payload tanda terima. Untuk menghitung hash dari objek klaim yang diekspos, diperlukan untuk melakukan iterasi melalui setiap objek klaim aplikasi dalam daftar dan memeriksa bidangnya kind .

Jika objek klaim berjenis LedgerEntry, ID koleksi ledger (collectionId) dan konten (contents) klaim harus diekstrak dan digunakan untuk menghitung hash HMAC mereka menggunakan kunci rahasia (secretKey) yang ditentukan dalam objek klaim. Kedua hash ini kemudian digabungkan dan hash SHA-256 dari perangkaian dihitung. Protokol (protocol) dan hash data klaim yang dihasilkan kemudian digabungkan dan hash SHA-256 lainnya dari perangkaian dihitung untuk mendapatkan hash akhir.

Jika objek klaim jenisnya ClaimDigest, hash klaim (value) harus diekstrak, digabungkan dengan protokol (protocol), dan hash SHA-256 dari perangkaian dihitung untuk mendapatkan hash akhir.

Setelah menghitung setiap hash klaim tunggal, perlu untuk menggabungkan semua hash komputasi dari setiap objek klaim aplikasi (dalam urutan yang sama mereka disajikan dalam tanda terima). Perangkaian kemudian harus didahului dengan jumlah klaim yang diproses. Hash SHA-256 dari perangkaian sebelumnya menghasilkan hash klaim akhir, yang harus cocok dengan claimsDigest yang ada di objek tanda terima.

Sumber daya lainnya

Untuk informasi selengkapnya tentang konten tanda terima transaksi tulis Azure Confidential Ledger dan penjelasan setiap bidang, lihat artikel khusus. Dokumentasi CCF juga berisi informasi selengkapnya tentang verifikasi tanda terima dan sumber daya terkait lainnya di tautan berikut:

Memverifikasi tanda terima transaksi tulis

Utilitas verifikasi tanda terima

Pustaka klien Azure Confidential Ledger untuk Python menyediakan fungsi utilitas untuk memverifikasi tanda terima transaksi tulis dan menghitung hash klaim dari daftar klaim aplikasi. Untuk informasi selengkapnya tentang cara menggunakan Data Plane SDK dan utilitas khusus tanda terima, lihat bagian ini dan kode sampel ini.

Penyiapan dan prasyarat

Untuk tujuan referensi, kami menyediakan kode sampel di Python untuk memverifikasi sepenuhnya tanda terima transaksi tulis Azure Confidential Ledger dengan mengikuti langkah-langkah yang diuraikan di bagian sebelumnya.

Untuk menjalankan algoritma verifikasi lengkap, sertifikat jaringan layanan saat ini dan tanda terima transaksi tulis dari sumber daya Confidential Ledger yang sedang berjalan diperlukan. Lihat artikel ini untuk detail tentang cara mengambil tanda terima transaksi tulis dan sertifikat layanan dari instans Confidential Ledger.

Panduan kode

Kode berikut dapat digunakan untuk menginisialisasi objek yang diperlukan dan menjalankan algoritma verifikasi tanda terima. Utilitas terpisah (verify_receipt) digunakan untuk menjalankan algoritma verifikasi lengkap, dan menerima konten receipt bidang dalam GET_RECEIPT respons sebagai kamus dan sertifikat layanan sebagai string sederhana. Fungsi ini melemparkan pengecualian jika tanda terima tidak valid atau jika ada kesalahan yang ditemui selama pemrosesan.

Diasumsikan bahwa tanda terima dan sertifikat layanan dapat dimuat dari file. Pastikan untuk memperbarui konstanta service_certificate_file_name dan receipt_file_name dengan nama file masing-masing sertifikat layanan dan tanda terima yang ingin Anda verifikasi.

import json 

# Constants
service_certificate_file_name = "<your-service-certificate-file>"
receipt_file_name = "<your-receipt-file>"

# Use the receipt and the service identity to verify the receipt content 
with open(service_certificate_file_name, "r") as service_certificate_file, open( 
    receipt_file_name, "r" 
) as receipt_file: 

    # Load relevant files content 
    receipt = json.loads(receipt_file.read())["receipt"] 
    service_certificate_cert = service_certificate_file.read() 

    try: 
        verify_receipt(receipt, service_certificate_cert) 
        print("Receipt verification succeeded") 

    except Exception as e: 
        print("Receipt verification failed") 

        # Raise caught exception to look at the error stack
        raise e 

Karena proses verifikasi memerlukan beberapa primitif kriptografi dan hashing, pustaka berikut digunakan untuk memfasilitasi komputasi.

  • Pustaka CCF Python: modul menyediakan serangkaian alat untuk verifikasi tanda terima.
  • Pustaka kriptografi Python: pustaka yang banyak digunakan yang mencakup berbagai algoritma kriptografi dan primitif.
  • Modul hashlib, bagian dari pustaka standar Python: modul yang menyediakan antarmuka umum untuk algoritma hash populer.
from ccf.receipt import verify, check_endorsements, root 
from cryptography.x509 import load_pem_x509_certificate, Certificate 
from hashlib import sha256 
from typing import Dict, List, Any 

verify_receipt Di dalam fungsi, kami memeriksa bahwa tanda terima yang diberikan valid dan berisi semua bidang yang diperlukan.

# Check that all the fields are present in the receipt 
assert "cert" in receipt 
assert "leafComponents" in receipt 
assert "claimsDigest" in receipt["leafComponents"] 
assert "commitEvidence" in receipt["leafComponents"] 
assert "writeSetDigest" in receipt["leafComponents"] 
assert "proof" in receipt 
assert "signature" in receipt 

Kami menginisialisasi variabel yang akan digunakan di sisa program.

# Set the variables 
node_cert_pem = receipt["cert"] 
claims_digest_hex = receipt["leafComponents"]["claimsDigest"] 
commit_evidence_str = receipt["leafComponents"]["commitEvidence"] 
write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"] 
proof_list = receipt["proof"] 
service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
root_node_signature = receipt["signature"] 

Kita dapat memuat sertifikat PEM untuk identitas layanan, simpul penandatanganan, dan sertifikat dukungan dari identitas layanan sebelumnya menggunakan pustaka kriptografi.

# Load service and node PEM certificates 
service_cert = load_pem_x509_certificate(service_cert_pem.encode()) 
node_cert = load_pem_x509_certificate(node_cert_pem.encode()) 

# Load service endorsements PEM certificates 
service_endorsements_certs = [ 
    load_pem_x509_certificate(pem.encode()) 
    for pem in service_endorsements_certs_pem 
] 

Langkah pertama dari proses verifikasi adalah menghitung hash simpul daun.

# Compute leaf of the Merkle Tree corresponding to our transaction 
leaf_node_hex = compute_leaf_node( 
    claims_digest_hex, commit_evidence_str, write_set_digest_hex 
)

Fungsi ini compute_leaf_node menerima sebagai parameter komponen daun dari tanda terima ( claimsDigest, , commitEvidencedan writeSetDigest) dan mengembalikan hash simpul daun dalam bentuk heksadesimal.

Seperti yang dijelaskan sebelumnya, kami menghitung hash commitEvidence (menggunakan fungsi SHA-256 hashlib ). Kemudian, kami mengonversi keduanya writeSetDigest dan claimsDigest menjadi array byte. Terakhir, kami menggabungkan tiga array, dan kami mencerna hasilnya menggunakan fungsi SHA256.

def compute_leaf_node( 
    claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str 
) -> str: 
    """Function to compute the leaf node associated to a transaction 
    given its claims digest, commit evidence, and write set digest.""" 

    # Digest commit evidence string 
    commit_evidence_digest = sha256(commit_evidence_str.encode()).digest() 

    # Convert write set digest to bytes 
    write_set_digest = bytes.fromhex(write_set_digest_hex) 

    # Convert claims digest to bytes 
    claims_digest = bytes.fromhex(claims_digest_hex) 

    # Create leaf node by hashing the concatenation of its three components 
    # as bytes objects in the following order: 
    # 1. write_set_digest 
    # 2. commit_evidence_digest 
    # 3. claims_digest 
    leaf_node_digest = sha256( 
        write_set_digest + commit_evidence_digest + claims_digest 
    ).digest() 

    # Convert the result into a string of hexadecimal digits 
    return leaf_node_digest.hex() 

Setelah menghitung daun, kita dapat menghitung akar pohon Merkle.

# Compute root of the Merkle Tree 
root_node = root(leaf_node_hex, proof_list) 

Kami menggunakan fungsi root yang disediakan sebagai bagian dari pustaka CCF Python. Fungsi ini secara berturut-turut menggabungkan hasil iterasi sebelumnya dengan elemen baru dari proof, mencerna perangkaian, dan kemudian mengulangi langkah untuk setiap elemen dengan proof hash yang dihitung sebelumnya. Penggabungan perlu menghormati urutan simpul di Pohon Merkle untuk memastikan akar dikomputasi ulang dengan benar.

def root(leaf: str, proof: List[dict]): 
    """ 
    Recompute root of Merkle tree from a leaf and a proof of the form: 
    [{"left": digest}, {"right": digest}, ...] 
    """ 

    current = bytes.fromhex(leaf) 

    for n in proof: 
        if "left" in n: 
            current = sha256(bytes.fromhex(n["left"]) + current).digest() 
        else: 
            current = sha256(current + bytes.fromhex(n["right"])).digest() 
    return current.hex() 

Setelah menghitung hash simpul akar, kita dapat memverifikasi tanda tangan yang terkandung dalam tanda terima melalui akar untuk memvalidasi bahwa tanda tangan sudah benar.

# Verify signature of the signing node over the root of the tree 
verify(root_node, root_node_signature, node_cert) 

Demikian pula, pustaka CCF menyediakan fungsi verify untuk melakukan verifikasi ini. Kami menggunakan kunci umum ECDSA dari sertifikat simpul penandatanganan untuk memverifikasi tanda tangan di atas akar pohon.

def verify(root: str, signature: str, cert: Certificate):
    """ 
    Verify signature over root of Merkle Tree 
    """ 

    sig = base64.b64decode(signature) 
    pk = cert.public_key() 
    assert isinstance(pk, ec.EllipticCurvePublicKey) 
    pk.verify( 
        sig, 
        bytes.fromhex(root), 
        ec.ECDSA(utils.Prehashed(hashes.SHA256())), 
    )

Langkah terakhir verifikasi tanda terima adalah memvalidasi sertifikat yang digunakan untuk menandatangani akar pohon Merkle.

# Verify node certificate is endorsed by the service certificates through endorsements 
check_endorsements(node_cert, service_cert, service_endorsements_certs) 

Demikian juga, kita dapat menggunakan utilitas check_endorsements CCF untuk memvalidasi bahwa identitas layanan didukung node penandatanganan. Rantai sertifikat dapat terdiri dari sertifikat layanan sebelumnya, jadi kita harus memvalidasi bahwa dukungan diterapkan secara transitif jika serviceEndorsements bukan daftar kosong.

def check_endorsement(endorsee: Certificate, endorser: Certificate): 
    """ 
    Check endorser has endorsed endorsee 
    """ 

    digest_algo = endorsee.signature_hash_algorithm 
    assert digest_algo 
    digester = hashes.Hash(digest_algo) 
    digester.update(endorsee.tbs_certificate_bytes) 
    digest = digester.finalize() 
    endorser_pk = endorser.public_key() 
    assert isinstance(endorser_pk, ec.EllipticCurvePublicKey) 
    endorser_pk.verify( 
        endorsee.signature, digest, ec.ECDSA(utils.Prehashed(digest_algo)) 
    ) 

def check_endorsements( 
    node_cert: Certificate, service_cert: Certificate, endorsements: List[Certificate] 
): 
    """ 
    Check a node certificate is endorsed by a service certificate, transitively through a list of endorsements. 
    """ 

    cert_i = node_cert 
    for endorsement in endorsements: 
        check_endorsement(cert_i, endorsement) 
        cert_i = endorsement 
    check_endorsement(cert_i, service_cert) 

Sebagai alternatif, kita juga dapat memvalidasi sertifikat dengan menggunakan pustaka OpenSSL menggunakan metode serupa.

from OpenSSL.crypto import ( 
    X509, 
    X509Store, 
    X509StoreContext, 
)

def verify_openssl_certificate( 
    node_cert: Certificate, 
    service_cert: Certificate, 
    service_endorsements_certs: List[Certificate], 
) -> None: 
    """Verify that the given node certificate is a valid OpenSSL certificate through 
    the service certificate and a list of endorsements certificates.""" 

    store = X509Store() 

    # pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired 
    # services and historical receipts, we want to ignore the validity time. 0x200000 
    # is the bitmask for this option in more recent versions of OpenSSL. 
    X509_V_FLAG_NO_CHECK_TIME = 0x200000 
    store.set_flags(X509_V_FLAG_NO_CHECK_TIME) 

    # Add service certificate to the X.509 store 
    store.add_cert(X509.from_cryptography(service_cert)) 

    # Prepare X.509 endorsement certificates 
    certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs] 

    # Prepare X.509 node certificate 
    node_cert_pem = X509.from_cryptography(node_cert) 

    # Create X.509 store context and verify its certificate 
    ctx = X509StoreContext(store, node_cert_pem, certs_chain) 
    ctx.verify_certificate() 

Kode Sampel

Kode sampel lengkap yang digunakan dalam panduan kode disediakan.

Program utama

import json 

# Use the receipt and the service identity to verify the receipt content 
with open("network_certificate.pem", "r") as service_certificate_file, open( 
    "receipt.json", "r" 
) as receipt_file: 

    # Load relevant files content 
    receipt = json.loads(receipt_file.read())["receipt"]
    service_certificate_cert = service_certificate_file.read()

    try: 
        verify_receipt(receipt, service_certificate_cert) 
        print("Receipt verification succeeded") 

    except Exception as e: 
        print("Receipt verification failed") 

        # Raise caught exception to look at the error stack 
        raise e 

Verifikasi tanda terima

from cryptography.x509 import load_pem_x509_certificate, Certificate 
from hashlib import sha256 
from typing import Dict, List, Any 

from OpenSSL.crypto import ( 
    X509, 
    X509Store, 
    X509StoreContext, 
) 

from ccf.receipt import root, verify, check_endorsements 

def verify_receipt(receipt: Dict[str, Any], service_cert_pem: str) -> None: 
    """Function to verify that a given write transaction receipt is valid based 
    on its content and the service certificate. 
    Throws an exception if the verification fails.""" 

    # Check that all the fields are present in the receipt 
    assert "cert" in receipt 
    assert "leafComponents" in receipt 
    assert "claimsDigest" in receipt["leafComponents"] 
    assert "commitEvidence" in receipt["leafComponents"] 
    assert "writeSetDigest" in receipt["leafComponents"] 
    assert "proof" in receipt 
    assert "signature" in receipt 

    # Set the variables 
    node_cert_pem = receipt["cert"] 
    claims_digest_hex = receipt["leafComponents"]["claimsDigest"] 
    commit_evidence_str = receipt["leafComponents"]["commitEvidence"] 

    write_set_digest_hex = receipt["leafComponents"]["writeSetDigest"] 
    proof_list = receipt["proof"] 
    service_endorsements_certs_pem = receipt.get("serviceEndorsements", [])
    root_node_signature = receipt["signature"] 

    # Load service and node PEM certificates
    service_cert = load_pem_x509_certificate(service_cert_pem.encode()) 
    node_cert = load_pem_x509_certificate(node_cert_pem.encode()) 

    # Load service endorsements PEM certificates
    service_endorsements_certs = [ 
        load_pem_x509_certificate(pem.encode()) 
        for pem in service_endorsements_certs_pem 
    ] 

    # Compute leaf of the Merkle Tree 
    leaf_node_hex = compute_leaf_node( 
        claims_digest_hex, commit_evidence_str, write_set_digest_hex 
    ) 

    # Compute root of the Merkle Tree
    root_node = root(leaf_node_hex, proof_list) 

    # Verify signature of the signing node over the root of the tree
    verify(root_node, root_node_signature, node_cert) 

    # Verify node certificate is endorsed by the service certificates through endorsements
    check_endorsements(node_cert, service_cert, service_endorsements_certs) 

    # Alternative: Verify node certificate is endorsed by the service certificates through endorsements 
    verify_openssl_certificate(node_cert, service_cert, service_endorsements_certs) 

def compute_leaf_node( 
    claims_digest_hex: str, commit_evidence_str: str, write_set_digest_hex: str 
) -> str: 
    """Function to compute the leaf node associated to a transaction 
    given its claims digest, commit evidence, and write set digest.""" 

    # Digest commit evidence string
    commit_evidence_digest = sha256(commit_evidence_str.encode()).digest() 

    # Convert write set digest to bytes
    write_set_digest = bytes.fromhex(write_set_digest_hex) 

    # Convert claims digest to bytes
    claims_digest = bytes.fromhex(claims_digest_hex) 

    # Create leaf node by hashing the concatenation of its three components 
    # as bytes objects in the following order: 
    # 1. write_set_digest 
    # 2. commit_evidence_digest 
    # 3. claims_digest 
    leaf_node_digest = sha256( 
        write_set_digest + commit_evidence_digest + claims_digest 
    ).digest() 

    # Convert the result into a string of hexadecimal digits 
    return leaf_node_digest.hex() 

def verify_openssl_certificate( 
    node_cert: Certificate, 
    service_cert: Certificate, 
    service_endorsements_certs: List[Certificate], 
) -> None: 
    """Verify that the given node certificate is a valid OpenSSL certificate through 
    the service certificate and a list of endorsements certificates.""" 

    store = X509Store() 

    # pyopenssl does not support X509_V_FLAG_NO_CHECK_TIME. For recovery of expired 
    # services and historical receipts, we want to ignore the validity time. 0x200000 
    # is the bitmask for this option in more recent versions of OpenSSL. 
    X509_V_FLAG_NO_CHECK_TIME = 0x200000 
    store.set_flags(X509_V_FLAG_NO_CHECK_TIME) 

    # Add service certificate to the X.509 store
    store.add_cert(X509.from_cryptography(service_cert)) 

    # Prepare X.509 endorsement certificates
    certs_chain = [X509.from_cryptography(cert) for cert in service_endorsements_certs] 

    # Prepare X.509 node certificate
    node_cert_pem = X509.from_cryptography(node_cert) 

    # Create X.509 store context and verify its certificate
    ctx = X509StoreContext(store, node_cert_pem, certs_chain) 
    ctx.verify_certificate() 

Langkah berikutnya