یک ارائهدهندهٔ وام مدل امتیازدهی خود را منتشر میکند. متقاضی آن را روی دادههای خودش اجرا میکند و رسیدی به وامدهنده میدهد که میگوید «تأیید شد» یا «رد شد». وامدهنده هرگز دادهها را نمیبیند، و متقاضی نمیتواند مدل را تغییر دهد یا دادههای ساختگی به آن بدهد.
این کوچکترین نمونهٔ کاربردی از اثبات یک محاسبه با برنامهٔ عمومی و پارامترهای عمومی روی ورودیهای خصوصی است. مدل یک شبکهٔ عصبی دولایه با حساب صحیح است. همین ساختار برای هر قاعدهای که یک نهاد ناظر یا طرف مقابل بخواهد خوانا باشد صدق میکند: یک فرمول قیمتگذاری، یک آزمون شایستگی، یک سقف ریسک.
Example programs تنظیمات مشترک هر چهار صفحهٔ نمونه را دارد.
1. آنچه اثبات میکند
سه طرف وجود دارند:
- وامدهنده برنامه را با مدل بهعنوان بخش عمومی آن مستقر میکند. شناسهٔ برنامه همان مدل را نام میبرد. هر کسی میتواند وزنها را از زنجیره بخواند.
- گواهیدهنده (یک دفتر اعتبارسنجی، یک بانک، یک کارفرما) دادههای متقاضی را میداند. او مقادیر ویژگیها، یک نمک تصادفی ۱۶ بایتی و امضای خود را روی تعهد
SHA-256(salt ‖ features)به متقاضی میدهد. - متقاضی برنامه را با نمک و ویژگیها بهعنوان ورودی خصوصی فراخوانی میکند.
این فراخوانی ثابت میکند که:
- هر ویژگی درون بازهای است که مدل برای آن اعلام کرده است؛
- امتیاز مدل روی آن ویژگیها برابر با آستانه یا بالاتر از آن است (وضعیت
1) یا پایینتر از آن (وضعیت2)؛ - تعهد منتشرشده همان SHA-256 نمک و همان ویژگیهاست.
وامدهنده شناسهٔ برنامه و وضعیت رسید را بررسی میکند، و اینکه تعهد همان است که گواهیدهنده امضا کرده است. او فقط یک بیت دربارهٔ متقاضی میآموزد.
ورودیهای خصوصی بهتنهایی چیزی دربارهٔ جهان واقعی اثبات نمیکنند. بدون مورد ۳ متقاضی میتوانست هر عددی را وارد کند. تعهد است که اعداد درون اثبات را به اعدادی که کسی صحت آنها را تأیید کرده پیوند میدهد. خود امضا خارج از زنجیره و توسط وامدهنده بررسی میشود: راستیآزمایی Dilithium یا ECDSA در بودجهٔ یک فراخوانی نمیگنجد، و لازم نیست درون اثبات باشد، چون تعهد عمومی است.
2. رابط
هر کلمهٔ عمومی یک i32 است که بهصورت u32 مکمل دو آن ذخیره میشود.
| کلمهٔ عمومی | معنا |
|---|---|
| 0 | تعداد ویژگیها n، از 1 تا 16 |
| 1 | تعداد واحدهای پنهان h، از 1 تا 16 |
| 2 | آستانه |
| 3 | تعداد بیتهای کسری، یعنی شیفت s، از 0 تا 31 |
سپس 2n کلمه | min و max مجاز هر ویژگی |
سپس h × n کلمه | وزنهای لایهٔ اول، سطر به سطر |
سپس h کلمه | بایاسهای لایهٔ اول |
سپس h کلمه | وزنهای لایهٔ دوم |
| سپس 1 کلمه | بایاس لایهٔ دوم |
| کلمهٔ خصوصی | معنا |
|---|---|
| 0 تا 3 | نمک، ۱۶ بایت بهصورت چهار کلمهٔ big-endian |
| 4 به بعد | n ویژگی، هرکدام یک i32 |
| شکاف خروجی | معنا |
|---|---|
| 0 | وضعیت: 1 تأیید شد، 2 رد شد، 3 یک ویژگی خارج از بازه است، 4 مدل نامعتبر (اندازهای خارج از حدود، یا حساب سرریز کرد) |
| 1 | استفادهنشده |
| 2 تا 6 | ۲۰ بایت اول تعهد، بهصورت پنج کلمهٔ big-endian |
| 7 | استفادهنشده؛ panic در Rust مقدار 0xdeadbeef را اینجا مینویسد |
تعهد فقط با وضعیت 1 یا 2 منتشر میشود. ورودی خارج از بازه پیش از آنکه چیزی هش شود متوقف میشود.
3. حساب
مدل فقط با اعداد صحیح کار میکند. ماشین ممیز شناور ندارد، و راستیآزما باید بتواند امتیاز را دقیقاً بازتولید کند، که ممیز شناور این را در میان ماشینهای مختلف تضمین نمیکند.
hidden[j] = max( (b1[j] + Σ w1[j][i] · x[i]) >> s , 0 ) for each hidden unit j
score = (b2 + Σ w2[j] · hidden[j]) >> s
approved = score >= threshold
وزنها ممیز ثابت با s بیت کسری هستند: با s = 8 وزن 256 یعنی 1.0. جمعها i64 هستند، و هر ضرب و جمع بررسیشده است، بنابراین مدلی با وزنهایی که بزرگتر از حد باعث سرریز شوند وضعیت 4 میدهد، نه امتیاز نادرست. >> روی i64 منفی در Rust به سمت منفی بینهایت گرد میکند، و >> در Python نیز همینطور است، و همین است که اجازه میدهد یک تابع دهخطی Python مرجع باشد.
مدل نمایشی هشت ویژگی و چهار واحد پنهان دارد. وزنهای آن برای این صفحه دستی نوشته شدهاند. این یک مدل آموزشدیده نیست و چیزی دربارهٔ اعتبار نمیگوید.
{
"shift": 8,
"threshold": 600,
"features": [
{"name": "age_years", "min": 18, "max": 120},
{"name": "income_thousands", "min": 0, "max": 100000},
{"name": "debt_to_income_pct", "min": 0, "max": 1000},
{"name": "on_time_payments_pct", "min": 0, "max": 100},
{"name": "utilisation_pct", "min": 0, "max": 100},
{"name": "history_years", "min": 0, "max": 100},
{"name": "inquiries_12m", "min": 0, "max": 100},
{"name": "delinquencies", "min": 0, "max": 100}
],
"w1": [
[ 64, 96, -512, 1536, -640, 1024, -2048, -8192],
[ 0, 256, -1024, 0, 0, 0, 0, 0],
[ 0, 0, 0, 2560, -1280, 0, -4096, -16384],
[ 128, 0, 0, 0, 0, 2048, 0, 0]
],
"b1": [25600, 12800, -128000, 0],
"w2": [160, 96, 224, 128],
"b2": 76800
}
4. مهمان
my-guests/score/src/main.rs، کامل:
#![no_std]
#![no_main]
use guest_sdk::{halt, read_input, read_public, sha256_compress, write_output};
const MAX_FEATURES: usize = 16;
const MAX_HIDDEN: usize = 16;
const APPROVED: u32 = 1;
const DECLINED: u32 = 2;
const OUT_OF_RANGE: u32 = 3;
const BAD_MODEL: u32 = 4;
/// SHA-256's initial state (FIPS 180-4 §5.3.3).
const IV: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
];
/// SHA-256 of `words`, each word taken as four big-endian bytes. The message is whole words,
/// so the padding is whole words too: `0x80000000`, zeros, then the bit length in the last
/// word of a block (the length's high word is always zero here).
fn sha256_words(words: &[u32]) -> [u32; 8] {
let mut buf = [0u32; 24];
buf[16..].copy_from_slice(&IV);
let mut fill = 0;
for &w in words {
buf[fill] = w;
fill += 1;
if fill == 16 {
sha256_compress(&mut buf);
fill = 0;
}
}
buf[fill] = 0x8000_0000;
fill += 1;
if fill > 14 {
buf[fill..16].fill(0);
sha256_compress(&mut buf);
fill = 0;
}
buf[fill..15].fill(0);
buf[15] = 32 * words.len() as u32;
sha256_compress(&mut buf);
let mut out = [0u32; 8];
out.copy_from_slice(&buf[16..]);
out
}
/// `weight · x`, added to `acc`; `None` when it does not fit an i64.
fn mul_add(acc: i64, weight: u32, x: i64) -> Option<i64> {
acc.checked_add((weight as i32 as i64).checked_mul(x)?)
}
/// Runs the model in the public segment over the private features.
fn score(features: &[i32], hidden: usize, shift: u32) -> Option<i64> {
let n = features.len();
// The public segment after the header and the feature ranges:
// hidden × n first-layer weights, hidden biases, hidden second-layer weights, one bias.
let w1 = 4 + 2 * n;
let b1 = w1 + hidden * n;
let w2 = b1 + hidden;
let b2 = w2 + hidden;
let mut out = read_public(b2 as u32) as i32 as i64;
for j in 0..hidden {
let mut acc = read_public((b1 + j) as u32) as i32 as i64;
for (i, &x) in features.iter().enumerate() {
acc = mul_add(acc, read_public((w1 + j * n + i) as u32), x as i64)?;
}
// Fixed point: drop the fractional bits, then ReLU.
let h = (acc >> shift).max(0);
out = mul_add(out, read_public((w2 + j) as u32), h)?;
}
Some(out >> shift)
}
/// Public segment, fixed at deploy (every value an i32):
/// [features n, hidden units, threshold, fractional bits,
/// n × (min, max), hidden × n weights, hidden biases, hidden weights, bias]
/// Private input: [salt (4 words), n features].
/// Outputs: [status, 0, commitment (first 5 words), 0], where
/// commitment = SHA-256(salt ‖ features), every word big-endian.
#[no_mangle]
pub extern "C" fn main() -> ! {
let n = read_public(0) as usize;
let hidden = read_public(1) as usize;
let threshold = read_public(2) as i32 as i64;
let shift = read_public(3);
if n == 0 || n > MAX_FEATURES || hidden == 0 || hidden > MAX_HIDDEN || shift > 31 {
write_output(0, BAD_MODEL);
halt();
}
// The committed message: the salt, then the features, in input order.
let mut message = [0u32; 4 + MAX_FEATURES];
for (i, w) in message.iter_mut().enumerate().take(4 + n) {
*w = read_input(i as u32);
}
let mut features = [0i32; MAX_FEATURES];
let mut in_range = true;
for i in 0..n {
let x = message[4 + i] as i32;
let min = read_public(4 + 2 * i as u32) as i32;
let max = read_public(5 + 2 * i as u32) as i32;
in_range &= min <= x && x <= max;
features[i] = x;
}
if !in_range {
write_output(0, OUT_OF_RANGE);
halt();
}
let status = match score(&features[..n], hidden, shift) {
Some(s) if s >= threshold => APPROVED,
Some(_) => DECLINED,
None => BAD_MODEL,
};
let commitment = sha256_words(&message[..4 + n]);
write_output(0, status);
for k in 0..5 {
write_output(2 + k as u32, commitment[k]);
}
halt();
}
مدل داده است، نه کد. وزنها با read_public خوانده میشوند، بنابراین یک تصویر مستقرشده به هر مدلی تا ۱۶ ویژگی و ۱۶ واحد پنهان خدمت میکند. تغییر مدل یعنی استقرار تازهٔ همان کد با بخش عمومی جدید، و شناسهٔ برنامهٔ جدید. وامدهنده نمیتواند وزنها را زیر دست متقاضی عوض کند، و متقاضی نمیتواند مدلی مهربانتر انتخاب کند: رسید شناسهٔ برنامه را نام میبرد، و شناسه وزنها را به خود میبندد.
بازهها بخشی از مدلاند. بدون آنها متقاضی نمیتوانست دربارهٔ یک مقدار دروغ بگوید (تعهد جلوی آن را میگیرد) اما گواهیدهنده میتوانست مقدار بیمعنایی را امضا کند، مثل سن −5، که مدلی با وزن منفی آن را پاداش میدهد. این بررسی حساب را نیز محدود میکند.
نمک دو کار انجام میدهد. تعهد را غیرقابلحدس میکند: هشت عدد صحیح کوچک آنتروپی کمی دارند، و بدون نمک هر کسی میتوانست متقاضیان نامزد را هش کند و مقایسه کند. و ۱۲۸ بیت تصادفی را از میان محاسبه عبور میدهد. پیش از آنکه مجموعهٔ قیود ۷ مجموعهای باس اثبات را کور کند، فراخوانی روی چند کلمهٔ خصوصی کوچک را میشد از خود اثبات بهروش جستجوی فراگیر شکست، و یادداشتهای fullnode به چنین برنامههایی میگفت کلمات تصادفی بخوانند. Chain 18 مجموعهٔ قیود ۸ را اجرا میکند، که در آن این مشکل رفع شده است، اما نمک چهار کلمهٔ ورودی هزینه دارد و این پرسش را از میان برمیدارد.
5. سمت میزبان
score.py فقط از کتابخانهٔ استاندارد Python استفاده میکند:
score.py setup <model.json> writes public.txt
score.py witness <model.json> <salt: 32 hex> <x0> <x1> … prints the private words;
stderr: the score, the decision, the commitment
score.py commit <salt: 32 hex> <x0> <x1> … prints the commitment alone
امتیازدهندهٔ مرجع و تعهد، بهطور کامل:
def score(m, x):
"""The guest's arithmetic exactly: integers, a floor shift after each layer, ReLU between."""
out = m["b2"]
for row, b, w2 in zip(m["w1"], m["b1"], m["w2"]):
h = max((b + sum(w * xi for w, xi in zip(row, x))) >> m["shift"], 0)
out += w2 * h
return out >> m["shift"]
def commitment(salt: bytes, x):
return hashlib.sha256(salt + b"".join(struct.pack(">i", xi) for xi in x)).digest()
$ cd my-guests/score
$ python3 score.py setup model.json
wrote public.txt (61 words)
$ cat public.txt
8 4 600 8 18 120 0 100000 0 1000 0 100 0 100 0 100 0 100 0 100 64 96 4294966784 1536 4294966656 1024 4294965248 4294959104 0 256 4294966272 0 0 0 0 0 0 0 0 2560 4294966016 0 4294963200 4294950912 128 0 0 0 0 2048 0 0 25600 12800 4294839296 0 160 96 224 128 76800
اعداد بزرگ همان وزنهای منفیاند: 4294966784 برابر −512 است.
مهمان با این مرجع روی ۳۰۰ متقاضی تصادفی آزمایش شد، با مقایسهٔ تصمیم و هر پنج کلمهٔ تعهد، و نیز روی متقاضیانی که امتیازشان 599 و 600 است. در همه یکسان بودند.
6. ساخت و اجرا
$ rand-guest build my-guests/score --max-words 65535
462 words against a cap of 65535 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/score/image.bin and its .sha256 (462 words, hc c63d7b15a165a11d007ce48bc37563b71f77fcc4991ffe0182c8cf4c77b6e7b5, program id ce8d234faf737c24bdd2826933ac843973c140c53e76fbb2cccaef3073da9818)
متقاضی ۴۱ ساله با درآمد ۸۵ هزار، ۲۸٪ نسبت بدهی به درآمد، ۹۷٪ پرداختهای بهموقع، ۲۲٪ بهرهبرداری، ۱۲ سال سابقه، یک استعلام و بدون معوقه:
$ python3 score.py witness model.json 00112233445566778899aabbccddeeff 41 85 28 97 22 12 1 0 > witness.txt
score 1075 approved
commitment 9430c3818f5966f8dd33f78e05a7d079355191e6
$ cat witness.txt
1122867 1146447479 2291772091 3437096703 41 85 28 97 22 12 1 0
$ rand-guest run image.bin --public $(cat public.txt) --input $(cat witness.txt) --tier 14
out[0] = 1
out[1] = 0
out[2] = 2486223745
out[3] = 2405000952
out[4] = 3711170446
out[5] = 94883961
out[6] = 894538214
out[7] = 0
cycles 1810
tier 12
tier 14: fits (cycles 1946 of 16383, Poseidon2 permutations 136 of 2048)
2486223745 برابر 0x9430c381 است، یعنی چهار بایت اول تعهد. امتیاز، 1075، در هیچیک از خروجیها نمیآید.
متقاضی دوم (23، 31، 55، 82، 91، 2، 6، 2، با نمکی دیگر) امتیاز 417 میگیرد و out[0] = 2 با تعهد مخصوص خودش میدهد، در همان 1,810 چرخه. متقاضی اول با سن تغییریافته به 17 مقدار out[0] = 3 و بدون تعهد میدهد.
7. بودجه
| برنامه | 462 کلمه |
| بخش عمومی | 61 کلمه |
| کلمات خصوصی | 12 |
| چرخهها | 1,810 |
| فشردهسازیهای SHA-256 | 1 (۴۸ بایت پیام در یک بلوک جا میشود) |
| ردیف | 12 |
این ارزانترین برنامه از میان چهار برنامه است. گذر رو به جلو ۳۶ ضرب است. مدلی در حدود برنامه، یعنی ۱۶ ویژگی و ۱۶ واحد پنهان، ۲۷۲ ضرب و بخش عمومی ۳۲۵ کلمهای است. در شبیهساز 7,900 چرخه اندازهگیری میشود، حدود نصف ردیف 14.
اجرا به دادهها بستگی ندارد: ۲۰۰ متقاضی تصادفی، چه تأییدشده و چه ردشده، هرکدام دقیقاً 1,810 چرخه طول کشیدند. سقف گازی که یک فراخوانی اعلام میکند عمومی است و از گاز اجرا گرد میشود، بنابراین برنامهای که تعداد چرخههایش از ورودیهایش پیروی کند اندکی از آنها را فاش میکند. این برنامه از این راه چیزی برای فاشکردن ندارد.
از Chain 18 به بعد کیف پول گاز دقیق اجرا و سقفی را که اعلام میکند چاپ میکند:
gas: 2011 (declaring 2048, tier 12)
1,948 ردیف با گاز ۱ برای هرکدام، بهعلاوهٔ ۶۳ دیگر برای یک فشردهسازی.
8. استقرار
۶۱ کلمهٔ مدل مانند کلمات کد پرداخت میشوند: 0.001 + 0.0001 × (462 + 61) = 0.0533 RAND.
$ rand fee deploy 462 --public-words 61
0.0533 RAND
$ rand program deploy image.bin --public public.txt
program id: 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c (462 words, hc 157b3dc61da165a18be47c00b76375c3c4fc771f01fe1f994ccfc882b5e7b677, public input 61 words, digest a08a7c691c45babeb39e953930d4d7174662b7b8adcc2d0f4e6c193d362d6668)
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 7.4s: tier 10, 1364940 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 123.5s: tier 14, 1497319 bytes
submitted deploy d47a9323ea53bfafc3faa36d004416d374bf3c2a48971d87e346be426b0caf14
0 RAND out, 59.7229 RAND change, fee 0.0533 RAND, anchored at height 98062, proved in 123.5s (bundle) + 7.4s (auth)
9. فراخوانی
$ rand call 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
proving the call locally (12 inputs stay private, over the program's 61-word public input)…
gas: 2011 (declaring 2048, tier 12)
fee 0.005673037 RAND (incl. two price steps of headroom)
proved in 30.6s: tier 12, 1838256 bytes, outputs [1, 0, 2486223745, 2405000952, 3711170446, 94883961, 894538214, 0]
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 6.8s: tier 10, 1369611 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 124.3s: tier 14, 1499112 bytes
submitted call 454c1b9eb1e159e682d4ff26b71f77d8a950b813533d91a89293dd432fdcf6f7
0 RAND out, 59.696058627 RAND change, fee 0.005673037 RAND, anchored at height 98832, proved in 124.3s (bundle) + 6.8s (auth)
{
"h_in": "ef80c744a8cdea62b56e334baaf7372e4a2e6c8282d37aa8ae96b4f039ec1a22",
"h_pub": "a08a7c691c45babeb39e953930d4d7174662b7b8adcc2d0f4e6c193d362d6668",
"height": 98993,
"index": 0,
"outputs": [
1,
0,
2486223745,
2405000952,
3711170446,
94883961,
894538214,
0
],
"program": "7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c",
"tier": 12,
"tx": "454c1b9eb1e159e682d4ff26b71f77d8a950b813533d91a89293dd432fdcf6f7"
}
input transcript published; open it with `rand open-call 454c1b9eb1e159e682d4ff26b71f77d8a950b813533d91a89293dd432fdcf6f7`
وضعیت 1 و تعهد 9430c381 8f5966f8 dd33f78e 05a7d079 355191e6 بهصورت هگزادسیمال. h_pub خلاصهٔ هش ۶۱ کلمهٔ مدل است. ویژگیها، نمک و امتیاز در تراکنش نیستند.
فراخوانی روی یک لپتاپ Apple Silicon کمی بیش از سه دقیقه طول کشید: ۳۱ ثانیه برای اثبات فراخوانی، ۷ ثانیه برای مجوز خرج و ۱۲۴ ثانیه برای بسته، با اوج ۶.۲ گیگابایت. اثبات 1,838,256 بایت است.
10. خودتان بررسی کنید
سمت وامدهنده، تنها با رسید و تعهد امضاشدهٔ گواهیدهنده:
- مدل.
rand_getProgramPublicروی شناسهٔ برنامه ۶۱ کلمه را برمیگرداند. آنها را باmodel.jsonمنتشرشده مقایسه کنید (score.py setupآنها را بازسازی میکند). - کد.
my-guests/scoreرا در همان commit مدارها بازسازی کنید وhcرا باcode_hashبرنامه مقایسه کنید. - رسید.
programشناسهٔ مدل است، خروجی 0 برابر1است، شکاف 7 برابر3735928559نیست. - تعهد. خروجیهای 2 تا 6، چاپشده بهصورت هگز، ۲۰ بایت اول تعهدی هستند که گواهیدهنده امضا کرد:
$ python3 score.py commit 00112233445566778899aabbccddeeff 41 85 28 97 22 12 1 0
9430c3818f5966f8dd33f78e05a7d079355191e6
یک رد هم رسید است. فراخوانی متقاضی دوم، یعنی 119e7834…1b3b روی chain 18 (که روی chain 20 هم تکرار شد، با c8a60183…9f73، و آن هم رد میکند)، به همان شیوه اثبات و ثبت شد:
$ python3 score.py witness model.json 0f1e2d3c4b5a69788796a5b4c3d2e1f0 23 31 55 82 91 2 6 2 > witness.txt
score 417 declined
commitment 0313ea462178574c49a10fe7ea1d84207652e1a3
$ rand call 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
…
gas: 2011 (declaring 2048, tier 12)
proved in 26.1s: tier 12, 1836624 bytes, outputs [2, 0, 51636806, 561534796, 1235292135, 3927802912, 1985143203, 0]
…
submitted call 119e7834033edd19bba8e10c1a764e8bdbc0b1c8415a42f041faa392c6411b3b
وضعیت 2 است و 51636806 برابر 0x0313ea46 است. گس، سقف اعلامشده و رده با فراخوانی تأییدشده یکسان است؛ پس این دو روی زنجیره فقط در کلمهٔ وضعیت و در تعهد با هم تفاوت دارند و در هیچ چیز دیگری فرق ندارند. متقاضی دلیلی ندارد که رد شدن را منتشر کند، اما وامدهنده در هر حال میتواند رسید را بخواهد، و متقاضی ردشده نمیتواند رسید تأییدشده ارائه کند.
آنچه اثبات به شما نمیدهد
- دادههای درست. اثبات نشان میدهد مدل روی مقادیر متعهدشده اجرا شده است. درست بودن این مقادیر به قول گواهدهنده بستگی دارد، و وامدهنده باید برای آن به امضای گواهدهنده اعتماد کند.
- متقاضی بدون قابلیت پیوند. تعهد یک شناسهٔ ثابت است: گواهدهنده میتواند آن را بشناسد، و دو رسید با تعهد یکسان به یک متقاضی و یک داده تعلق دارند. یک نمک تازه از سوی گواهدهنده برای هر درخواست، مورد دوم را برطرف میکند، نه مورد اول را.
- یک مدل محرمانه. وزنها عمومیاند. این برای قاعدهای که مردم باید بتوانند آن را بخوانند همان هدف است، و برای وامدهندهای که مدل خود را راز تجاری میداند یک ایراد است. متقاضی همچنین میتواند ورودیهایی را که قبول میشوند جستوجو کند، اما بدون امضای گواهدهنده بر آن ورودیها نمیتواند از آنها استفاده کند.
- حفاظت در برابر کاوش یک گواهدهندهٔ نادرستکار. هر کسی که ویژگیها را بداند میتواند خودش مدل عمومی را اجرا کند. حریم خصوصی در اینجا حریم متقاضی در برابر وامدهنده و عموم است، نه در برابر طرفی که داده را فراهم کرده است.
- یک مدل بزرگ. شانزده ویژگی و شانزده واحد نهان یک کارت امتیازدهی است، نه یک شبکهٔ عمیق. هر فراخوانی تا رده 14 محدود است، یعنی حدود شانزده هزار چرخه.