Rand Protocol
문서
예제 프로그램

비공개 신용 심사 결정

공개된 스코어링 모델을 비공개 신청자 데이터에 적용하여 승인 또는 거절만을 게시하고, 데이터를 증명자(attester)에 묶는 SHA-256 커밋먼트를 함께 게시하는 기밀 프로그램입니다. 인터페이스, 고정소수점 연산, Rust 게스트, 호스트 스크립트, 그리고 chain 18에서 실행 중인 프로그램을 다룹니다.

심사 모델을 대출기관이 공개합니다. 신청인은 자신의 데이터로 그 모델을 실행하고, “승인” 또는 “거절”이라고 적힌 영수증을 대출기관에 건넵니다. 대출기관은 데이터를 보지 못하며, 신청인은 모델을 바꾸거나 지어낸 데이터를 입력할 수 없습니다.

이것은 공개 프로그램과 공개 매개변수, 비공개 입력으로 연산을 증명하는 가장 작은 실용 사례입니다. 모델은 정수 연산으로 구현한 2층 신경망입니다. 규제 기관이나 거래 상대방이 읽을 수 있기를 원하는 규칙이라면 어떤 것이든 같은 구조로 만들 수 있습니다. 가격 산정 공식, 적격성 검사, 위험 한도가 그 예입니다.

네 개의 예제 페이지가 모두 공유하는 설정은 Example programs에 있습니다.

1. 증명하는 내용

참여자는 세 주체입니다.

  • 대출기관은 모델을 공개 세그먼트로 담아 프로그램을 배포합니다. 프로그램 id가 그 모델을 가리킵니다. 누구든 체인에서 가중치를 읽어 낼 수 있습니다.
  • 증명자(attester)(신용평가사, 은행, 고용주)는 신청인의 데이터를 알고 있습니다. 신청인에게 특성값, 무작위 16바이트 솔트, 그리고 커밋먼트 SHA-256(salt ‖ features)에 대한 서명을 줍니다.
  • 신청인은 솔트와 특성값을 비공개 입력으로 하여 프로그램을 호출합니다.

호출이 증명하는 내용은 다음과 같습니다.

  1. 모든 특성값이 모델이 해당 특성에 대해 선언한 범위 안에 있습니다.
  2. 그 특성값에 대한 모델의 점수가 임계값 이상이거나(상태 1) 미만입니다(상태 2).
  3. 공개된 커밋먼트가 솔트와 바로 그 특성값의 SHA-256입니다.

대출기관은 영수증의 프로그램 id와 상태, 그리고 커밋먼트가 증명자가 서명한 것인지를 확인합니다. 대출기관이 신청인에 대해 알게 되는 것은 1비트뿐입니다.

비공개 입력만으로는 세상에 대해 아무것도 증명되지 않습니다. 3번이 없다면 신청인은 임의의 숫자를 입력할 수 있습니다. 증명 속의 숫자를 누군가 보증한 숫자와 연결하는 것이 커밋먼트입니다. 서명 자체는 대출기관이 오프체인에서 검증합니다. Dilithium이나 ECDSA 검증은 호출의 예산에 들어가지 않으며, 커밋먼트가 공개되어 있으므로 증명 안에 있을 필요도 없습니다.

2. 인터페이스

모든 공개 워드는 i32이며, 2의 보수 u32로 저장합니다.

공개 워드의미
0특성의 수 n, 1에서 16
1은닉 유닛의 수 h, 1에서 16
2임계값
3소수부 비트 수, 곧 시프트 s, 0에서 31
이어서 2n개 워드각 특성의 허용 min과 max
이어서 h × n개 워드첫 번째 층의 가중치, 행 단위
이어서 h개 워드첫 번째 층의 편향
이어서 h개 워드두 번째 층의 가중치
이어서 1개 워드두 번째 층의 편향
비공개 워드의미
0에서 3솔트, 16바이트를 빅엔디언 워드 4개로 표현
4부터n개의 특성값, 각각 i32
출력 슬롯의미
0상태: 1 승인, 2 거절, 3 특성값이 범위를 벗어남, 4 잘못된 모델(크기가 한계를 벗어나거나 연산이 오버플로됨)
1미사용
2에서 6커밋먼트의 처음 20바이트, 빅엔디언 워드 5개
7미사용. 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 함수가 기준 구현이 될 수 있습니다.

데모 모델은 특성 8개와 은닉 유닛 4개로 이루어져 있습니다. 가중치는 이 페이지를 위해 손으로 작성했습니다. 학습된 모델이 아니며 신용에 대해 아무것도 말해 주지 않습니다.

{
  "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으로 읽으므로, 배포된 이미지 하나가 특성 16개와 은닉 유닛 16개까지의 어떤 모델이든 처리합니다. 모델을 바꾸려면 같은 코드를 새 공개 세그먼트와 함께 새로 배포해야 하고, 그러면 새 프로그램 id가 생깁니다. 대출기관은 신청인 몰래 가중치를 바꿔치기할 수 없고, 신청인은 더 너그러운 모델을 고를 수 없습니다. 영수증이 프로그램 id를 명시하고, id가 가중치를 묶어 두기 때문입니다.

범위는 모델의 일부입니다. 범위가 없다면 신청인은 값을 속일 수 없지만(커밋먼트가 막습니다), 증명자가 터무니없는 값에 서명할 수 있습니다. 예를 들어 나이 −5는 음의 가중치를 가진 모델에서 보상을 받을 수 있습니다. 이 검사는 연산의 범위도 제한합니다.

솔트는 두 가지 역할을 합니다. 첫째, 커밋먼트를 추측할 수 없게 만듭니다. 작은 정수 여덟 개는 엔트로피가 낮아서, 솔트가 없으면 누구든 후보 신청인들을 해시해 비교할 수 있습니다. 둘째, 무작위 128비트를 연산에 통과시킵니다. 제약 집합 7이 증명의 버스 합계를 블라인딩하기 전에는 몇 개의 작은 비공개 워드에 대한 호출을 증명 자체로부터 무차별 대입할 수 있었고, fullnode의 노트는 그런 프로그램에 무작위 워드를 읽으라고 안내했습니다. Chain 18은 이 문제가 고쳐진 제약 집합 8을 실행하지만, 솔트는 입력 워드 네 개로 그 문제를 아예 없애 줍니다.

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입니다.

게스트는 무작위 신청인 300명에 대해 이 기준 구현과 비교하여 시험했습니다. 결정과 커밋먼트 워드 다섯 개를 모두 비교했고, 점수가 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)

나이 41세, 소득 85천, 부채 대비 소득 비율 28%, 정시 납부율 97%, 이용률 22%, 이력 12년, 조회 1건, 연체 없음인 신청인:

$ 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로, 커밋먼트의 처음 4바이트입니다. 점수 1075는 출력 어디에도 나타나지 않습니다.

두 번째 신청인(23, 31, 55, 82, 91, 2, 6, 2, 다른 솔트)은 점수가 417이며, 같은 1,810 사이클에서 out[0] = 2와 자신의 커밋먼트를 냅니다. 첫 번째 신청인의 나이를 17로 바꾸면 out[0] = 3이 나오고 커밋먼트는 없습니다.

7. 예산

프로그램462 워드
공개 세그먼트61 워드
비공개 워드12
사이클1,810
SHA-256 압축1 (메시지 48바이트가 한 블록에 들어감)
티어12

네 프로그램 중 가장 저렴합니다. 순전파는 곱셈 36회입니다. 프로그램의 한계인 특성 16개와 은닉 유닛 16개를 갖춘 모델은 곱셈 272회에 공개 세그먼트 325 워드입니다. 에뮬레이터에서 7,900 사이클로 측정되며, 이는 티어 14의 약 절반입니다.

실행은 데이터에 의존하지 않습니다. 승인과 거절을 가리지 않고 무작위 신청인 200명이 모두 정확히 1,810 사이클이 걸렸습니다. 호출이 선언하는 가스 한도는 공개되며 실행의 가스를 반올림한 값이므로, 사이클 수가 입력을 따라 달라지는 프로그램은 입력의 일부를 누설합니다. 이 프로그램은 그런 식으로 누설할 것이 없습니다.

Chain 18부터 지갑은 실행의 정확한 가스와 선언하는 한도를 출력합니다.

gas: 2011 (declaring 2048, tier 12)

1,948행이 행당 가스 1이고, 압축 1회에 대해 63이 추가됩니다.

8. 배포

모델의 61 워드는 코드 워드와 같은 방식으로 비용이 부과됩니다. 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이고 커밋먼트는 16진수로 9430c381 8f5966f8 dd33f78e 05a7d079 355191e6입니다. h_pub은 모델의 61 워드에 대한 다이제스트입니다. 특성값, 솔트, 점수는 트랜잭션에 들어 있지 않습니다.

호출에는 Apple Silicon 노트북에서 3분 남짓 걸렸습니다. 호출 증명에 31초, 지출 승인에 7초, 번들에 124초가 걸렸고 최대 메모리 사용량은 6.2 GB였습니다. 증명의 크기는 1,838,256바이트입니다.

10. 직접 확인하기

영수증과 증명자가 서명한 커밋먼트만 가진 대출기관의 입장에서:

  1. 모델. 프로그램 id에 대한 rand_getProgramPublic이 61 워드를 반환합니다. 공개된 model.json과 비교합니다(score.py setup이 이를 다시 만들어 냅니다).
  2. 코드. 같은 circuits 커밋에서 my-guests/score를 다시 빌드하고 hc를 프로그램의 code_hash와 비교합니다.
  3. 영수증. program이 모델의 id이고, 출력 0이 1이며, 슬롯 7이 3735928559가 아닙니다.
  4. 커밋먼트. 16진수로 출력한 출력 2에서 6은 증명자가 서명한 커밋먼트의 처음 20바이트입니다.
$ 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입니다. 가스, 선언된 한도, 티어는 승인된 호출과 같으므로, 두 호출은 온체인에서 상태 워드와 커밋먼트에서만 다르고 그 밖에는 다르지 않습니다. 신청자가 거절 결과를 공개할 이유는 없지만, 대출기관은 어느 쪽이든 영수증을 요구할 수 있으며, 거절된 신청자는 승인된 영수증을 만들어 낼 수 없습니다.

증명이 보장하지 않는 것

  • 데이터의 진실성. 증명은 모델이 커밋된 값에 대해 실행되었음을 보여 줍니다. 그 값이 참인지는 증명자(attester)의 말에 달려 있으며, 대출기관은 이를 위해 attester의 서명을 신뢰해야 합니다.
  • 연결 불가능한 신청자. 커밋먼트는 고정된 식별자입니다. attester는 이를 알아볼 수 있고, 커밋먼트가 같은 두 영수증은 같은 신청자와 같은 데이터를 가리킵니다. attester가 신청마다 새로운 솔트를 제공하면 둘째 문제는 해결되지만 첫째 문제는 해결되지 않습니다.
  • 비밀 모델. 가중치는 공개되어 있습니다. 사람들이 읽을 수 있어야 하는 규칙에는 이것이 핵심이지만, 모델을 영업 비밀로 다루는 대출기관에는 단점입니다. 신청자는 통과할 수 있는 입력을 탐색할 수도 있지만, 그 입력에 대한 attester의 서명이 없으면 그것을 사용할 수 없습니다.
  • 부정직한 attester의 탐색으로부터의 보호. 특성값을 아는 사람은 누구나 공개 모델을 직접 실행할 수 있습니다. 여기서의 프라이버시는 대출기관과 대중에 대한 신청자의 프라이버시이며, 데이터를 제공한 당사자에 대한 것이 아닙니다.
  • 대형 모델. 열여섯 개의 특성과 열여섯 개의 은닉 유닛은 스코어카드이지 심층 신경망이 아닙니다. 호출 한 번은 티어 14, 약 1만 6천 사이클로 제한됩니다.

fullnode 커밋 48dc04b(chain 18) 기준으로 2026-09-30에 마지막으로 검증되었습니다, 그리고 circuits `aeacf31` (`guest-sdk/src/lib.rs`, `rand-guest`), the commit fullnode v0.6.7 vendors; every `rand-guest`, `python3` and `rand` output on this page was produced on 2026-09-30, the deploy and the call on chain 18. 코드와 이 페이지의 내용이 다를 경우 코드가 옳습니다. 알려 주시기 바랍니다.