Погледајте видео лекцију: Осигуравање AI агената криптографским потврдама
(Видео лекције и сличица ће бити додати од стране Microsoft тима за садржај након спајања, у складу са обрасцем лекција 14 / 15.)
У овој лекцији ће бити обрађено:
Након завршене ове лекције, знаћете како да:
Замислите да сте поставили AI агента за Contoso Travel. Агент чита захтеве корисника, позива API за летове да нађе опције и резервише седишта у име корисника. Прошлог квартала, агент је обрадио 50.000 резервација.
Данас стиже ревидент. Поставља једноставно питање: “Покажи ми шта је твој агент радио.”
Предајете датотеке дневника. Ревидент их погледа и поставља тежје питање: “Како знам да ови записи нису били уређивани?”
Ово је проблем ревизијског трага. Већина данашњих агената се ослања на:
Ниједна од ових опција не може одговорити ревиденту без даље вере у неко лице (вас, вашег провајдера облака, добављача базе података). За интерну употребу то је често прихватљиво. За регулисане радне оптерећења (финансије, здравство, све подложно EU AI акту) то није.
Криптографске потврде сужавају овај проблем тако што праве потписану изјаву која је независно проверљива. Ревиденту је ипак потребан спољни разлог да верује потписном кључу и компоненту која је произвела изјаву. Уграђени кључ доказује само унутрашњу конзистентност, не идентитет издаваоца или место примене.
Потврда је JSON објекат који бележи шта је агент урадио, потписан дигиталним потписом.
flowchart LR
A[Agent invokes a tool] --> B[Build receipt payload]
B --> C[Canonicalize JSON RFC 8785]
C --> E[Ed25519 sign canonical bytes]
E --> F[Receipt with signature]
F --> G[Auditor verifies offline]
G --> H{Signature valid?}
H -- yes --> I[Signed statement is intact]
H -- no --> J[Receipt rejected]
Минимална потврда изгледа овако:
{
"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 канонизационе шеме (JCS, RFC 8785). Ово осигурава да две имплементације које производе исти логички садржај дају потпуно идентичан бајт излаз. Без канонизације, различити JSON серијализатори дали би различите потписе за исти садржај.
Повезивање хешем. Поље previous_receipt_hash повезује сваку потврду са претходном. Уклањање или преуређивање потврде разбија представљени низ. Детекција прескакања или преписивања које контролише потписивач такође захтева закључану тренутну главу ланца, независно сведочени контролни скуп или аутентификовану обавезу за затворену популацију.
Заједно ова својства пружају три гаранције:
Не треба вам посебна библиотека да направите потврду. Криптографски примитиви су широко доступни, а логика је само неколико десетина линија Python кода.
Практични задаци у code_samples/18-signed-receipts.ipynb воде вас кроз цео процес. Изворно контролисани code_samples/18-signed-receipts.execution-receipt.json бележи тачан хеш бележнице, Python окружење, број ћелија и провере из последњег чистог извршења без промене сачуваних резултата лекције. Сажетак:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 canonical JSON
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()}"
# Generate or load a signing key (in production, store in a key vault)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
TRUSTED_PUBLIC_KEYS = {b64url_nopad(bytes(verify_key))} # pinned out of band by the verifier
# Build the receipt payload (no signature yet)
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,
}
# Canonicalize and sign the JCS bytes directly. PureEdDSA hashes internally.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Attach a structured signature object.
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 CryptoError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.b64decode(s + padding, altchars=b"-_", validate=True)
def verify_receipt(receipt: dict, trusted_public_keys: set[str]) -> bool:
# The signature is a structured object: {"alg", "sig", "public_key"}.
if not isinstance(receipt, dict):
return False
sig_obj = receipt.get("signature")
if not isinstance(sig_obj, dict) or sig_obj.get("alg") != "EdDSA":
return False
try:
# An inline key proves only self-consistency. Trust is configured out of band.
public_key = sig_obj.get("public_key")
signature = sig_obj.get("sig")
if not isinstance(public_key, str) or not isinstance(signature, str):
return False
if public_key not in trusted_public_keys:
return False
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
verify_key = signing.VerifyKey(b64url_decode(public_key))
verify_key.verify(canonical_bytes, b64url_decode(signature))
return True
except (CryptoError, TypeError, ValueError, KeyError, OverflowError):
return False
Овај метод прихвата потврду и скуп закључаних кључева корисника који се ослања и враћа
True само ако је потпис важећи под прихваћеним кључем. Проверa не захтева
позив мрежи, али поверење у кључ је и даље спољна конфигурациона одлука.
Да бисте видели детекцију измене у акцији, бележница вас води кроз:
tool_args_hash.Ово је практична демонстрација да су потврде доказиво непромењиве: свака измена, ма колико мала била, разбија потпис.
Једна потписана потврда штити једну радњу. Ланац потврда штити низ радњи.
flowchart LR
R0[Receipt 0<br/>genesis] --> R1[Receipt 1]
R1 --> R2[Receipt 2]
R2 --> R3[Receipt 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Свaka потврда бележи хеш претходне. Да би нападач тихо уклонио потврду 2, морао би да:
previous_receipt_hash потврде 3 (што разбија потпис потврде 3), ИЛИАко је приватни кључ заштићен и верификатор закључава независно сведочену главу ланца, ови напади су откривљиви. Објављивање кључа унутар сваке потврде није довољно: потписивач или нападач може представити други кључ, преписати наставак или прескочити ланац ако корисник није обезбедио спољни ослонац поверења и потпуности.
Бележница води корисника кроз:
previous_receipt_hash сваке потврде одговара стварном хешу претходне.Овако правите ревизијски траг који спољни ревидент може проверити својим закључаним издавачким кључевима и контролним тачкама ланца, без поверења у вашу изменљиву продавницу дневника.
Ово је најважнији део ове лекције. Потврде су моћне али њихова моћ је ограничена.
Потврде доказују три ствари:
Потврде НЕ доказују:
policy_id заиста процењена, или да би дозволила ову радњу ако би била проверена. Потврда бележи шта је тврђено, а не шта је спроводљено.Ова граница је важна из два разлога:
Честа грешка је претпоставка да “имамо потврде” значи “имамо управљање.” Не значи. Потврде су основа. Управљање је систем који се на њој гради.
Према ставци 3 горе, потврда радње каже “овaj кључ је потписао овај садржај,” а никада “човек је одобрио ово.” За радње високог ризика (повраћаји новца, брисања, електронски трансфери) посебан потпис одобраваоца може повезати тачну радњу. Повезивање тог кључа са човеком и провера људског присуства остају посебне одговорности.
Наставна бележница 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) су добро одржаване и ревидиране библиотеке.
Користите продукциону библиотеку за потврде. Неколико open-source пројеката имплементира исти образац са додатним функцијама (ротација кључева, групна верификација, дистрибуција JWK сета, интеграција са политикама):
draft-farley-acta-signed-receipts, ревизија 02). Образовна равна потврда ове лекције разликује се од коверте из {payload, signature} нацрта и није приказана као пример усклађености. Нацрт објављује заједнички скуп тестова (agent-governance-testvectors) за имплементације које циљају тај формат.protect-mcp (npm) и @veritasacta/verify (npm) пакети обезбеђују Node-ову имплементацију потписивања потврда и офлајн провере, намењену за обавијање било ког MCP сервера са доказиво непромењивим ревизијским трагом, укључујући процес задржавања за заједничко потписивање у којем паузирана радња емитује потврду одобрења везану за дигест радње (WebAuthn подржано у desktop процесу), исти образац одобрења као у горе поменутој бележници за људску ауторизацију.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: проширите шему признања додатним пољем по свом избору (на пример, ID захтева за праћење), ажурирајте логику канонског потписивања да га укључи и потврдите да признаница и даље успешно пролази верификацију. Затим промените то поље након потписивања и потврдите да верификација пада. Ово вас приморава да разумете како сваки бајт канонског кодирања доприноси потпису.
Изазов 2: SHA-256 рашчланите две своје признанице заједно (конкатенацијом њихових канонских бајтова у детерминистичком редоследу) и уградите добијени сажећ на трећој признаници пре потписивања. Проверите да ли све три признанице и даље пролазе круг верификације. Управо сте направили доказ укључивања у једном кораку: било ко има трећу признаницу може доказати да су прве две постојале у тренутку потписа, без потребе да открива њихов садржај. Ово је образац који користе признанице селективног откривања на скали (Merkle обавезе, RFC 6962).
Криптографске признанице дају AI агентима преглед за ревизију који је:
Нису замена за валидацију уноса, спровођење политика, пријем извршиоца, доказе исхода, контролу комплетности или инфраструктуру идентитета. Они су темељ за те слојеве. У регулисаним или мулти-организационим радним токовима, признанице чине одређене изјаве издавача независно проверљивим у оквиру поверења ревизора.
Најважнија поука: признанице доказују да је одређени кључ потписао одређени садржај. Пренета временска ознака је изјава потписника, осим ако није посебно оверена временски. Признаница не доказује да је њен садржај био истинит, да је акција извршена или да је скуп записа комплетан. Чувајте те разлике јасно.
Када будете спремни да завршите ову лекцију и имплементирате агенте који потписују признанице у правом окружењу:
Придружите се Microsoft Foundry Discord да се сретнете са другим ученицима, посетите радне сате и добијете одговоре на питања о AI агентима.
Ова лекција покрива потписивање једне признанице и низове ланаца са хешевима. Исти примитиви се састављају у неколико напреднијих образаца на које можете наићи како ваш управљачки оквир сазрева:
authorization_*) и после-извршне (result_*) делове са независним потписима, корисно када одлуку о овлашћењу и посматрани резултат доносе различити актери или у различитим временима. Ово се надовезује на формат признанице из ове лекције.result_hash. Стварни садржаји често су богатији од резултата једног позива алата: претходно образложење одлуке (прогнозa модела, разматране опције, доказ и његова комплетност, ризични статус, ланац одговорности, исход капије) све може бити унутар садржаја, запечаћено једном признаницом. Ово држи формат признанице минималним док омогућава еволуцију шема по домену.alg ознака није миграција: профил мора дефинисати ML-DSA кодирање кључа, канонске потписане бајтове, верификацију, политику стране која се ослања, отпорност на смањење и семантику транзиције. Планирајте и тестирате двоструки потпис у транзицији према заједничким векторима пре тврдњи о компатибилности.Изјава о одрицању одговорности: Овај документ је преведен коришћењем услуге за аутоматски превод Co-op Translator. Иако тежимо тачности, имајте у виду да аутоматски преводи могу садржати грешке или нетачности. Оригинални документ на његовом изворном језику треба сматрати ауторитативним извором. За критичне информације препоручује се професионални људски превод. Нисмо одговорни за било каква неспоразума или погрешна тумачења која произилазе из коришћења овог превода.