Ein Kreditgeber veröffentlicht sein Scoring-Modell. Ein Antragsteller lässt es über seinen eigenen Daten laufen und übergibt dem Kreditgeber einen Beleg, auf dem „genehmigt“ oder „abgelehnt“ steht. Der Kreditgeber sieht die Daten nie, und der Antragsteller kann weder das Modell ändern noch erfundene Daten einspeisen.
Das ist der kleinste nützliche Fall, eine Berechnung mit einem öffentlichen Programm und öffentlichen Parametern über privaten Eingaben zu beweisen. Das Modell ist ein zweischichtiges neuronales Netz in Ganzzahlarithmetik. Dieselbe Form passt auf jede Regel, die eine Aufsichtsbehörde oder eine Gegenpartei einsehen können soll: eine Preisformel, eine Zulassungsprüfung, ein Risikolimit.
Beispielprogramme enthält die Einrichtung, die alle vier Beispielseiten gemeinsam haben.
1. Was bewiesen wird
Drei Parteien:
- Der Kreditgeber deployt das Programm mit dem Modell als öffentlichem Segment. Die Programm-ID benennt dieses Modell. Jeder kann die Gewichte aus der Chain zurücklesen.
- Ein Attestierer (eine Auskunftei, eine Bank, ein Arbeitgeber) kennt die Daten des Antragstellers. Er gibt dem Antragsteller die Merkmalswerte, ein zufälliges 16-Byte-Salt und seine Signatur über die Commitment
SHA-256(salt ‖ features). - Der Antragsteller ruft das Programm mit dem Salt und den Merkmalen als privater Eingabe auf.
Der Aufruf beweist:
- Jedes Merkmal liegt innerhalb des Bereichs, den das Modell dafür festlegt;
- der Score des Modells über diesen Merkmalen liegt auf oder über dem Schwellenwert (Status
1) oder darunter (Status2); - die veröffentlichte Commitment ist der SHA-256 aus dem Salt und genau diesen Merkmalen.
Der Kreditgeber prüft die Programm-ID des Belegs, seinen Status und ob die Commitment eine ist, die der Attestierer signiert hat. Er erfährt ein einziges Bit über den Antragsteller.
Private Eingaben beweisen für sich genommen nichts über die Welt. Ohne Punkt 3 könnte ein Antragsteller beliebige Zahlen eintippen. Die Commitment verbindet die Zahlen im Beweis mit Zahlen, für die jemand gebürgt hat. Die Signatur selbst prüft der Kreditgeber off-chain: Eine Dilithium- oder ECDSA-Verifikation passt nicht in das Budget eines Aufrufs, und sie muss auch nicht im Beweis stecken, denn die Commitment ist öffentlich.
2. Die Schnittstelle
Jedes öffentliche Wort ist ein i32, gespeichert als sein Zweierkomplement u32.
| öffentliches Wort | Bedeutung |
|---|---|
| 0 | die Anzahl der Merkmale n, 1 bis 16 |
| 1 | die Anzahl der verborgenen Einheiten h, 1 bis 16 |
| 2 | der Schwellenwert |
| 3 | die Anzahl der Nachkommabits, die Verschiebung s, 0 bis 31 |
dann 2n Wörter | das erlaubte min und max jedes Merkmals |
dann h × n Wörter | die Gewichte der ersten Schicht, zeilenweise |
dann h Wörter | die Biases der ersten Schicht |
dann h Wörter | die Gewichte der zweiten Schicht |
| dann 1 Wort | der Bias der zweiten Schicht |
| privates Wort | Bedeutung |
|---|---|
| 0 bis 3 | das Salt, 16 Bytes als vier Big-Endian-Wörter |
| ab 4 | die n Merkmale, jeweils ein i32 |
| Ausgabeslot | Bedeutung |
|---|---|
| 0 | Status: 1 genehmigt, 2 abgelehnt, 3 ein Merkmal liegt außerhalb des Bereichs, 4 fehlerhaftes Modell (eine Größe außerhalb der Grenzen, oder die Arithmetik ist übergelaufen) |
| 1 | ungenutzt |
| 2 bis 6 | die ersten 20 Bytes der Commitment, als fünf Big-Endian-Wörter |
| 7 | ungenutzt; ein Rust-Panic schreibt hier 0xdeadbeef |
Die Commitment wird nur bei Status 1 oder 2 veröffentlicht. Eine Eingabe außerhalb des Bereichs bricht ab, bevor irgendetwas gehasht wird.
3. Die Arithmetik
Das Modell besteht nur aus Ganzzahlen. Die Maschine hat keine Gleitkommazahlen, und ein Verifier muss den Score exakt reproduzieren können, was Gleitkommazahlen über Maschinen hinweg nicht garantieren.
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
Die Gewichte sind Festkommazahlen mit s Nachkommabits: Bei s = 8 bedeutet ein Gewicht von 256 den Wert 1,0. Summen sind i64, und jede Multiplikation und Addition ist geprüft, sodass ein Modell mit überlaufträchtig großen Gewichten Status 4 statt eines falschen Scores liefert. >> rundet auf einem negativen i64 in Rust gegen minus unendlich, und Pythons >> tut dasselbe; deshalb kann eine zehnzeilige Python-Funktion als Referenz dienen.
Das Demomodell hat acht Merkmale und vier verborgene Einheiten. Seine Gewichte wurden für diese Seite von Hand geschrieben. Es ist kein trainiertes Modell und sagt nichts über Kreditwürdigkeit aus.
{
"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. Der Guest
my-guests/score/src/main.rs, vollständig:
#![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();
}
Das Modell sind Daten, kein Code. Die Gewichte werden mit read_public gelesen, sodass ein deploytes Image jedes Modell mit bis zu 16 Merkmalen und 16 verborgenen Einheiten bedient. Ein anderes Modell bedeutet ein neues Deploy desselben Codes mit einem neuen öffentlichen Segment und damit eine neue Programm-ID. Ein Kreditgeber kann einem Antragsteller die Gewichte nicht unterschieben, und ein Antragsteller kann sich kein milderes Modell aussuchen: Der Beleg nennt die Programm-ID, und die ID bindet die Gewichte.
Die Bereiche sind Teil des Modells. Ohne sie könnte ein Antragsteller zwar nicht bei einem Wert lügen (das verhindert die Commitment), aber ein Attestierer könnte Unsinn signieren, etwa ein Alter von −5, das ein Modell mit negativem Gewicht belohnen würde. Die Prüfung begrenzt außerdem die Arithmetik.
Das Salt erfüllt zwei Aufgaben. Es macht die Commitment unerratbar: Acht kleine Ganzzahlen haben wenig Entropie, und ohne Salt könnte jeder Kandidaten hashen und vergleichen. Und es führt 128 Zufallsbits durch die Berechnung. Bevor Constraint-Set 7 die Bus-Summen des Beweises verblindete, ließ sich ein Aufruf über wenige kleine private Wörter aus dem Beweis selbst per Brute Force knacken, und die Hinweise des Fullnodes forderten solche Programme auf, Zufallswörter zu lesen. Chain 18 läuft mit Constraint-Set 8, wo das behoben ist, aber ein Salt kostet vier Eingabewörter und beseitigt die Frage.
5. Die Host-Seite
score.py verwendet nur die Standardbibliothek von 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
Der Referenz-Scorer und die Commitment, vollständig:
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
Die großen Zahlen sind die negativen Gewichte: 4294966784 ist −512.
Der Guest wurde an 300 zufälligen Antragstellern gegen diese Referenz getestet, wobei die Entscheidung und alle fünf Commitment-Wörter verglichen wurden, sowie an Antragstellern mit Scores von 599 und 600. Sie stimmen in jedem Fall überein.
6. Build und Ausführung
$ 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)
Ein 41-jähriger Antragsteller mit einem Einkommen von 85 Tausend, 28 % Schuldenquote, 97 % pünktlichen Zahlungen, 22 % Auslastung, 12 Jahren Historie, einer Anfrage und keinen Zahlungsausfällen:
$ 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 ist 0x9430c381, die ersten vier Bytes der Commitment. Der Score, 1075, taucht in den Ausgaben nirgends auf.
Ein zweiter Antragsteller (23, 31, 55, 82, 91, 2, 6, 2, mit einem anderen Salt) erreicht einen Score von 417 und liefert out[0] = 2 mit seiner eigenen Commitment, in denselben 1.810 Zyklen. Der erste Antragsteller mit auf 17 geändertem Alter liefert out[0] = 3 und keine Commitment.
7. Das Budget
| Programm | 462 Wörter |
| öffentliches Segment | 61 Wörter |
| private Wörter | 12 |
| Zyklen | 1.810 |
| SHA-256-Kompressionen | 1 (48 Bytes Nachricht passen in einen Block) |
| Tier | 12 |
Das ist das günstigste der vier Programme. Der Forward-Pass besteht aus 36 Multiplikationen. Ein Modell an den Grenzen des Programms, 16 Merkmale und 16 verborgene Einheiten, sind 272 Multiplikationen und ein öffentliches Segment von 325 Wörtern. Es misst im Emulator 7.900 Zyklen, etwa die Hälfte von Tier 14.
Der Lauf hängt nicht von den Daten ab: 200 zufällige Antragsteller, genehmigte wie abgelehnte, brauchten jeweils genau 1.810 Zyklen. Das Gas-Limit, das ein Aufruf angibt, ist öffentlich und wird aus dem Gas des Laufs aufgerundet; ein Programm, dessen Zyklenzahl seinen Eingaben folgte, würde daher ein wenig davon preisgeben. Dieses hier hat auf diesem Weg nichts preiszugeben.
Seit Chain 18 gibt die Wallet das exakte Gas des Laufs und das angegebene Limit aus:
gas: 2011 (declaring 2048, tier 12)
1.948 Zeilen zu je 1 Gas, plus 63 weitere für die eine Kompression.
8. Deploy
Die 61 Wörter des Modells werden wie Codewörter bezahlt: 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. Aufruf
$ 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`
Status 1 und die Commitment 9430c381 8f5966f8 dd33f78e 05a7d079 355191e6 in Hex. h_pub ist der Digest der 61 Wörter des Modells. Die Merkmale, das Salt und der Score stehen nicht in der Transaktion.
Der Aufruf dauerte auf einem Apple-Silicon-Laptop knapp über drei Minuten: 31 s für den Aufrufbeweis, 7 s für die Spend-Autorisierung und 124 s für das Bundle, bei einem Spitzenwert von 6,2 GB. Der Beweis ist 1.838.256 Bytes groß.
10. Selbst prüfen
Die Seite des Kreditgebers, mit nichts als dem Beleg und der vom Attestierer signierten Commitment:
- Das Modell.
rand_getProgramPublicauf der Programm-ID liefert die 61 Wörter. Vergleichen Sie sie mit der veröffentlichtenmodel.json(score.py setupbaut sie neu auf). - Der Code. Bauen Sie
my-guests/scorebeim selben Circuits-Commit neu und vergleichen Siehcmit demcode_hashdes Programms. - Der Beleg.
programist die ID des Modells, Ausgabe 0 ist1, Slot 7 ist nicht3735928559. - Die Commitment. Die Ausgaben 2 bis 6, als Hex ausgegeben, sind die ersten 20 Bytes der Commitment, die der Attestierer signiert hat:
$ python3 score.py commit 00112233445566778899aabbccddeeff 41 85 28 97 22 12 1 0
9430c3818f5966f8dd33f78e05a7d079355191e6
Auch eine Ablehnung ist ein Beleg. Der Aufruf des zweiten Antragstellers, 119e7834…1b3b auf Chain 18 (auf Chain 20 wiederholt als c8a60183…9f73, der ebenfalls ablehnt), wurde auf dieselbe Weise bewiesen und festgeschrieben:
$ 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
Status 2, und 51636806 ist 0x0313ea46. Gas, das angegebene Limit und die Tier sind dieselben wie beim genehmigten Aufruf; die beiden unterscheiden sich on-chain also im Statuswort und in der Verpflichtung (Commitment) und sonst nirgends. Ein Antragsteller hat keinen Grund, eine Ablehnung zu veröffentlichen, aber der Kreditgeber kann den Beleg in jedem Fall verlangen, und ein abgelehnter Antragsteller kann keinen genehmigten vorlegen.
Was der Beweis nicht leistet
- Wahre Daten. Der Beweis zeigt, dass das Modell über die committeten Werte gelaufen ist. Ob die Werte stimmen, ist das Wort des Attestierers, und der Kreditgeber muss dafür der Signatur des Attestierers vertrauen.
- Ein nicht verknüpfbarer Antragsteller. Das Commitment ist ein stabiler Bezeichner: Der Attestierer kann es wiedererkennen, und zwei Belege mit demselben Commitment betreffen denselben Antragsteller und dieselben Daten. Ein frisches Salt vom Attestierer für jeden Antrag behebt Letzteres, nicht Ersteres.
- Ein geheimes Modell. Die Gewichte sind öffentlich. Das ist der Zweck bei einer Regel, die Menschen lesen können müssen, und ein Nachteil für einen Kreditgeber, der sein Modell als Geschäftsgeheimnis behandelt. Ein Antragsteller kann auch nach den Eingaben suchen, die bestehen würden, kann sie aber ohne die Signatur eines Attestierers darüber nicht verwenden.
- Schutz vor Ausforschung durch einen unehrlichen Attestierer. Wer die Merkmale kennt, kann das öffentliche Modell selbst ausführen. Die Privatsphäre gilt hier für den Antragsteller gegenüber dem Kreditgeber und der Öffentlichkeit, nicht gegenüber der Partei, die die Daten geliefert hat.
- Ein großes Modell. Sechzehn Merkmale und sechzehn verborgene Einheiten sind eine Scorecard, kein tiefes Netz. Ein Aufruf ist auf Tier 14 begrenzt, etwa sechzehntausend Zyklen.