Перегляньте відеоурок: Захист агентів ШІ криптографічними квитанціями
(Відео уроку та мініатюра будуть додані контент-командою Microsoft після мерджу, відповідно до шаблону уроків 14 / 15.)
У цьому уроці розглянемо:
Після завершення уроку ви знатимете, як:
Уявіть, що ви розгорнули агента ШІ для Contoso Travel. Агент читає запити клієнтів, виконує запити до API авіарейсів, щоб знайти варіанти, і бронює місця від імені клієнта. Торік агент опрацював 50 000 бронювань.
Сьогодні приходить аудитор. Він задає просте питання: «Покажіть, що робив ваш агент».
Ви передаєте файли журналів. Аудитор дивиться на них і ставить складніше питання: «Як я можу бути впевненим, що ці логи не редагувалися?»
Це і є проблема аудиторського сліду. Більшість розгортань агентів сьогодні спираються на:
Жоден із цих способів не може відповісти на запитання аудитора, не вимагаючи довіри до когось (вас, вашого хмарного провайдера, вашого постачальника бази даних). Для внутрішнього використання така довіра часто прийнятна. Для регульованих робочих навантажень (фінанси, охорона здоров’я, будь-що під дією ЄС AI Act) — ні.
Криптографічні квитанції розв’язують це, роблячи кожну дію агента незалежно перевірною. Аудитору не потрібно довіряти вам. Потрібні лише ваш публічний ключ та сама квитанція.
Квитанція — це JSON-обʼєкт, який фіксує, що зробив агент, підписаний цифровим підписом.
flowchart LR
A[Агент виконує виклик інструменту] --> B[Побудова навантаження квитанції]
B --> C[Канонізація JSON RFC 8785]
C --> E[Підпис Ed25519 канонічних байтів]
E --> F[Квитанція з підписом]
F --> G[Аудитор перевіряє офлайн]
G --> H{Підпис дійсний?}
H -- yes --> I[Доказ захищений від підробки]
H -- no --> J[Квитанцію відхилено]
Мінімальна квитанція виглядає так:
{
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": "sha256:a3f9c1...",
"result_hash": "sha256:7b2e1d...",
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": {
"alg": "EdDSA",
"sig": "c5af83...",
"public_key": "8f3b2c..."
}
}
Роботу виконують три властивості:
Підпис. Квитанція підписується шлюзом агента за допомогою приватного ключа Ed25519. Будь-хто з відповідним публічним ключем може перевірити підпис офлайн. Зміна будь-якого поля робить підпис недійсним.
Канонічне кодування. Перед підписанням квитанція серіалізується за схемою JSON Canonicalization Scheme (JCS, RFC 8785). Це гарантує, що дві реалізації, які створюють однакову логічну квитанцію, мають ідентичний байтовий вихід. Без канонізації різні JSON-серіалізатори створювали б різні підписи для однакового вмісту.
Хеш-ланцюг. Поле previous_receipt_hash пов’язує кожну квитанцію з попередньою. Видалення або перестановка квитанції порушує всі наступні. Підміна стає помітною на рівні ланцюга, навіть якщо окремі підписи обходять.
Разом ці властивості дають три гарантії:
Вам не потрібна спеціальна бібліотека для створення квитанції. Криптографічні примітиви широко доступні, а логіка займає кілька десятків рядків Python.
Практичні вправи в code_samples/18-signed-receipts.ipynb проходять весь процес. Коротко:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # Канонічний JSON згідно з RFC 8785
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
"""SHA-256 of a Python object's JCS-canonical JSON form."""
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# Згенеруйте або завантажте ключ підпису (у продакшені зберігайте у сейфі ключів)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Побудуйте корисне навантаження квитанції (поки без підпису)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": sha256_canonical(tool_args),
"result_hash": sha256_canonical(tool_result),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 0,
"previous_receipt_hash": None,
}
# Канонізуйте і підпишіть байти JCS безпосередньо. PureEdDSA хешує внутрішньо.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Додайте структурований об’єкт підпису.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Це вся конвеєр підписання. Вправи в зошиті пояснюють кожен крок.
Перевірка — це зворотна операція:
import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.urlsafe_b64decode(s + padding)
def verify_receipt(receipt: dict) -> bool:
# Підпис є структурованим об'єктом: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Відновити повідомлення, яке фактично було підписане (усе, крім підпису).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(canonical_bytes, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Ця функція приймає квитанцію і повертає True, якщо підпис дійсний, інакше False. Немає викликів у мережу, залежностей від сервісів, не потрібно довіряти третім сторонам.
Щоб побачити в дії виявлення підміни, у зошиті демонструють:
tool_args_hash.Це практична демонстрація того, що квитанції помітно підміняють: будь-яка зміна, навіть найменша, робить підпис недійсним.
Одна підписана квитанція захищає одну дію. Ланцюжок квитанцій захищає послідовність.
flowchart LR
R0[Квитанція 0<br/>генезис] --> R1[Квитанція 1]
R1 --> R2[Квитанція 2]
R2 --> R3[Квитанція 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Кожна квитанція містить хеш попередньої. Щоб тихо видалити квитанцію 2, нападнику потрібно:
previous_receipt_hash квитанції 3 (порушить підпис квитанції 3), АБОЯкщо приватний ключ зберігається у апаратному сховищі ключів, а ви публікуєте публічний ключ із кожною квитанцією, жоден із цих нападів неможливий без виявлення.
У зошиті показано:
previous_receipt_hash кожної квитанції збігається з реальним хешем попередньої.Так ви створюєте аудиторський слід, який зовнішній аудитор може перевірити без довіри до вас.
Це найважливіша частина уроку. Квитанції потужні, але їх сила обмежена.
Квитанції доводять три речі:
Квитанції НЕ доводять:
policy_id, була фактично оцінена або що вона дозволила б цю дію, якби перевірялася. Квитанція фіксує, що було заявлено, а не що було застосовано.Ця межа важлива з двох причин:
Поширена помилка — вважати, що «у нас є квитанції» означає «ми керуємося». Ні. Квитанції — це фундамент. Управління — це система, яку ви на ньому будуєте.
Пункт 3 вище вартий окремого розгляду: квитанція про дію каже “цей ключ підписав цей вміст”, але ніколи не “людина авторизувала це”. Для ризикових дій (повернення грошей, видалення, перекази) рамки управління все частіше вимагають саме такої відсутньої заяви, і вона можлива з використанням тих самих примітивів, створених у цьому уроці.
Наступний зошит code_samples/human-authorization-receipts.ipynb додає другий тип квитанції, human.approval.v1, у тому самому форматі, що й урокові квитанції (типізований пейлоад, підписаний Ed25519 над його канонічними JCS байтами, з обʼєктом signature поза підписаними байтами). Іменований схвалювач підписує повну канонічну дію і її дайджест до виконання; квитанція дії агента несе той самий дайджест дії і parent_approval_ref, receipt_hash схвалення, так само, як previous_receipt_hash у побудованому ланцюгу вище. Одна функція verify_chain перевіряє обидва артефакти під окремими закріпленими реєстрами ключів (ключі схвалювачів проти ключів агентів), таким чином код спільний, але авторитети ніколи не перетинаються.
Отримана властивість, сформульована точно: людина схвалила цю точну дію, і агент виконав саме цю схвалену дію. Механізми відмов у зошиті роблять цю властивість реальною, а не просто ствердженою:
Кожен збій повертає окрему причину, тому аудитор, читаючи відмову, може зрозуміти, чи авторитет застарів, чи дія змінилася. Правило зошита: підписане схвалення не є авторитетом саме по собі. Авторитет існує лише, якщо обидві квитанції зв’язані з тією самою канонічною дією в момент виконання. Квитанція про схвалення людиною — це освітня композиція, визначена цим уроком, а не тип квитанції з draft-farley-acta-signed-receipts.
Код на Python у цьому уроці навмисно мінімальний, щоб ви могли прочитати кожний рядок і чітко зрозуміти, що відбувається. У виробництві у вас є два варіанти:
Працювати безпосередньо з криптографічними примітивами. Наведені вище 50 рядків достатньо для багатьох випадків. PyNaCl (Ed25519) і пакет jcs (канонічний JSON) — це добре підтримувані та аудиторські бібліотеки.
Використовувати виробничу бібліотеку квитанцій. Декілька відкритих проектів реалізують ту саму схему з додатковими функціями (ротація ключів, пакетна перевірка, розповсюдження наборів JWK, інтеграція з рушіями політик):
draft-farley-acta-signed-receipts, ревізія 02). Освітня плоска квитанція уроку відрізняється від {payload, signature} у драфті і не подається як відповідна імплементація. Драфт публікує спільний набір для тестування відповідності (agent-governance-testvectors) для реалізацій, орієнтованих на його формат.protect-mcp (npm) та @veritasacta/verify (npm) надають реалізацію підписання квитанцій та офлайн-перевірки на Node, призначені для обгортання серверів MCP із захищеним аудиторським слідом, включно з потоком утримання на співавторство, де призупинена дія випускає квитанцію схвалення, прив’язану до дайджесту дії (підтримка WebAuthn у десктопному потокові), та сама схема схвалення, що і в зошиті про авторизацію людини вище.pip install nobulex) забезпечує ту саму схему підписання Ed25519 + JCS у Python із інтеграціями LangChain та CrewAI, включаючи опубліковані вектори тестування та аудиторської відповідності, внесені через OWASP PR #2210.Вибір між написанням свого коду і використанням бібліотеки нагадує вибір між власною JWT-бібліотекою та перевіреною: обидва варіанти допустимі; бібліотека заощаджує час і зменшує площу аудиту; підхід з нуля змушує зрозуміти кожний примітив. Цей урок навчає шляху з нуля, щоб у вас була база для будь-якого вибору.
Перевірте своє розуміння перед практичним завданням.
1. Квитанція підписана приватним ключем Ed25519 агента. Аудитор має лише публічний ключ. Чи може аудитор перевірити квитанцію офлайн?
2. Зловмисник змінює поле policy_id у квитанції, щоб стверджувати, що дія регулювалась більш ліберальною політикою. Підпис був над оригінальним пейлоадом. Що станеться під час перевірки?
3. Чому у квитанції є tool_args_hash та result_hash, а не сирі аргументи та результат?
4. Поле previous_receipt_hash пов’язує кожну квитанцію з попередньою. Якщо зловмисник тихо видалить одну квитанцію посеред ланцюга, що стане недійсним?
5. Квитанція проходить перевірку. Чи доводить це, що дія агента була правильною, обґрунтованою або відповідала політиці?
Відкрийте code_samples/18-signed-receipts.ipynb і виконайте всі чотири розділи:
Розширене завдання 1: розширте схему квитанції додатковим полем на свій вибір (наприклад, ідентифікатор запиту для трасування), оновіть логіку канонічного підпису для включення цього поля і підтвердіть, що квитанція все ще проходить перевірку. Потім змініть поле після підпису та підтвердіть, що перевірка не проходить. Це змушує вас зрозуміти, як кожен байт канонічного кодування впливає на підпис.
Розширене завдання 2: об’єднайте два своїх квитанції через SHA-256-хеш (конкатенуйте їх канонічні байти у детермінованому порядку) і вкладіть отриманий дайджест як нове поле в третю квитанцію перед її підписанням. Перевірте, що всі три квитанції все ще проходять перевірку. Ви щойно створили доказ включення в один крок: будь-хто, хто має третю квитанцію, може довести, що перші дві існували на момент її підписання, не розкриваючи їх вмісту. Це шаблон, який використовується в квитанціях з вибірковим розкриттям у великому масштабі (Merkle commitments, RFC 6962).
Криптографічні квитанції надають агентам ШІ аудиторський слід, який є:
Вони не заміняють валідацію вводу, застосування політики чи інфраструктуру ідентифікації. Вони є основою для цих шарів. Коли ви розгортаєте агентів у регульованих робочих навантаженнях, багатозадачних робочих процесах кількох організацій або в будь-якому середовищі, де майбутній аудитор не може автоматично вам довіряти, квитанції — це спосіб зробити аудиторський слід чесним.
Найголовніше: квитанції доводять, хто що сказав і коли. Вони не доводять, що сказане було правдою чи правильним. Тримайте це розмежування чітко. Це різниця між чесною системою походження та такою, що вводить в оману.
Коли будете готові перейти від цього уроку до розгортання агентів з підписаними квитанціями у реальному середовищі:
https://your-org.example.com/.well-known/agent-keys.json.Приєднуйтесь до Microsoft Foundry Discord, щоб зустрітися з іншими учнями, відвідати години консультацій та отримати відповіді на питання щодо агентів ШІ.
Цей урок охоплює підписання однієї квитанції та послідовність з хеш-ланцюгом. Ті ж примітиви складають кілька більш складних шаблонів, з якими ви можете зіткнутися у міру розвитку вашої політики управління:
authorization_*) і післявиконавчу (result_*) з незалежними підписами, корисне, коли рішення щодо авторизації та спостережуваний результат створюються різними акторами чи в різний час. Це додається до формату квитанції, викладеного в цьому уроці.result_hash. Реальні навантаження часто багатші за один результат виклику інструменту: передрішення (прогноз моделі, розглянуті опції, докази та їх повнота, ризиковий стан, ланцюжок відповідальності, результат пропуску) можуть жити всередині навантаження, запечатаного однією квитанцією. Це зберігає формат квитанції мінімальним і дозволяє схемам навантаження розвиватися по доменах.signature.alg може містити ML-DSA-65 (стандарт постквантового підпису від NIST), коли потрібно мігрувати. Плануйте перехідний період з подвійним підписуванням квитанцій.Створення локальних агентів ШІ
Відмова від відповідальності: Цей документ було перекладено за допомогою сервісу штучного інтелекту для перекладу Co-op Translator. Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ рідною мовою слід вважати авторитетним джерелом. Для критично важливої інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникли внаслідок використання цього перекладу.