ZKML 以太坊深度技術實作完整指南:零知識機器學習應用程式碼範例與工程實踐

ZKML(Zero-Knowledge Machine Learning)代表著區塊鏈與人工智慧交叉領域最具突破性的技術融合。本指南從工程師視角出發,提供截至2026年第一季度的ZKML技術全景圖,重點涵蓋Circom、Noir、Halo2等主流框架的程式碼範例、ZKML智慧合約部署實務、以及在DeFi、借貸、保險等場景的具體應用實現。我們展示可直接用於生產環境的程式碼架構,幫助開發團隊快速掌握這項前沿技術的工程實作細節。

ZKML 以太坊深度技術實作完整指南:零知識機器學習應用程式碼範例與工程實踐

執行摘要

ZKML(Zero-Knowledge Machine Learning)代表著區塊鏈與人工智慧交叉領域最具突破性的技術融合。本指南從工程師視角出發,提供截至2026年第一季度的ZKML技術全景圖,重點涵蓋Circom、Noir等主流框架的程式碼範例、ZKML智慧合約部署實務、以及在DeFi、借貸、保險等場景的具體應用實現。我們將展示可直接用於生產環境的程式碼架構,幫助開發團隊快速掌握這項前沿技術的工程實作細節。

第一章:ZKML 技術基礎與框架比較

1.1 ZKML 技術原理

ZKML的核心價值在於允許證明者向驗證者證明「某個機器學習模型的推理結果是正確的」,同時保護模型參數和輸入數據的隱私。這種能力為區塊鏈應用帶來了革命性的變化:

傳統ML vs ZKML的差異

特性傳統機器學習ZKML
輸入隱私輸入數據暴露輸入數據加密保護
模型保護模型參數公開模型參數可選擇隱藏
驗證方式中心化服務信任去中心化密碼學驗證
適用場景內部數據處理需要公開驗證的場景

ZKML的典型應用場景

信用評估場景:用戶可以在本地設備上運行信用評分模型,生成零知識證明來證明自己的信用評級超過某個閾值,而無需向借貸協議揭露具體的財務數據。

預測市場場景:預測者可以證明自己的預測模型達到了某種準確率水平,而不揭露模型的具体参数,保护策略知识产权。

保險理賠場景:理賠申请人可以证明自己的损失符合理赔条件,而无需公开敏感的医疗或财务记录。

1.2 主流 ZKML 框架比較

Circom 框架

Circom是以太坊生態中使用最廣泛的ZK電路編譯器,特別適合開發ZKML應用。其特點包括:

// Circom 電路範例:簡單的神經網路推理驗證
// 文件名稱: simple_nn.circom

pragma circom 2.0.0;

// 輸入向量與權重矩陣的點積
template DotProduct(n) {
    signal input in[n];
    signal input weight[n];
    signal output out;
    
    signal intermediate[n-1];
    
    // 初始化
    intermediate[0] <== in[0] * weight[0];
    
    // 迴圈計算點積
    for (var i = 1; i < n; i++) {
        intermediate[i] <== intermediate[i-1] + in[i] * weight[i];
    }
    
    out <== intermediate[n-1];
}

// ReLU 激活函數
template Relu(n) {
    signal input in[n];
    signal output out[n];
    
    for (var i = 0; i < n; i++) {
        // ReLU: max(0, x)
        out[i] <== in[i] * (1 - IsZero()([in[i]])[0]);
    }
}

// 簡單的全連接層
template DenseLayer(nIn, nOut) {
    signal input in[nIn];
    signal input weights[nIn][nOut];
    signal input bias[nOut];
    signal output out[nOut];
    
    component dotProducts[nOut];
    
    for (var i = 0; i < nOut; i++) {
        dotProducts[i] = DotProduct(nIn);
        for (var j = 0; j < nIn; j++) {
            dotProducts[i].in[j] <== in[j];
            dotProducts[i].weight[j] <== weights[j][i];
        }
        // 加上偏置
        out[i] <== dotProducts[i].out + bias[i];
    }
}

// 主電路:驗證神經網路輸出超過閾值
template VerifyThreshold(nIn, nHidden, nOut) {
    signal input inputs[nIn];
    signal input weights1[nIn][nHidden];
    signal input bias1[nHidden];
    signal input weights2[nHidden][nOut];
    signal input bias2[nOut];
    signal input threshold;
    
    signal hidden[nHidden];
    signal output out[nOut];
    signal isValid;
    
    // 第一層:Dense(nIn -> nHidden)
    component layer1 = DenseLayer(nIn, nHidden);
    layer1.in <== inputs;
    layer1.weights <== weights1;
    layer1.bias <== bias1;
    
    // ReLU 激活(簡化版本)
    for (var i = 0; i < nHidden; i++) {
        hidden[i] <== layer1.out[i] * (layer1.out[i] > 0);
    }
    
    // 第二層:Dense(nHidden -> nOut)
    component layer2 = DenseLayer(nHidden, nOut);
    layer2.in <== hidden;
    layer2.weights <== weights2;
    layer2.bias <== bias2;
    out <== layer2.out;
    
    // 驗證至少一個輸出超過閾值
    component comparators[nOut];
    component anyValid = GreaterThan(252);
    
    for (var i = 0; i < nOut; i++) {
        comparators[i] = GreaterThan(252);
        comparators[i].in[0] <== threshold;
        comparators[i].in[1] <== out[i];
    }
    
    // 計算 OR 邏輯
    signal validCount;
    validCount <== comparators[0].out;
    for (var i = 1; i < nOut; i++) {
        validCount <== validCount + comparators[i].out;
    }
    
    isValid <== (validCount > 0) * 1;
}

component main {public [threshold]} = VerifyThreshold(10, 16, 1);

Noir 框架

Noir是Aztec Network開發的ZK語言,特點是:

// Noir 語言語法範例:信用評分驗證
// 文件名稱: credit_score.nr

// 定義結構體
struct CreditScore {
    income: Field,        // 收入
    debt: Field,          // 負債
    payment_history: Field,  // 付款歷史分數
    credit_age: Field     // 信用年齡
}

struct ProofInputs {
    score: CreditScore,
    threshold: Field,
    secret: Field,       // 用於防止重放的秘密值
    nullifier: Field     // 廢除值
}

// 信用評分計算函數
fn calculate_credit_score(score: CreditScore) -> Field {
    // 簡化的信用評分模型
    // 實際應用中應該使用更複雜的權重
    
    let income_weight = 30000;
    let debt_weight = 20000;
    let history_weight = 40000;
    let age_weight = 10000;
    
    let income_score = score.income * income_weight / 100000;
    let debt_score = (100000 - score.debt) * debt_weight / 100000;
    let history_score = score.payment_history * history_weight / 100;
    let age_score = score.credit_age * age_weight / 10;
    
    income_score + debt_score + history_score + age_score
}

// 驗證信用評分是否超過閾值
fn verify_credit_score(
    inputs: ProofInputs,
    // 公開輸入
    min_threshold: Field
) -> Field {
    // 計算信用評分
    let computed_score = calculate_credit_score(inputs.score);
    
    // 驗證評分超過閾值
    // 使用條件執行,這會在電路中產生相應的約束
    if computed_score > min_threshold {
        // 返回 1 表示驗證成功
        1
    } else {
        0
    }
}

// 主電路
fn main(
    // 私有輸入
    income: Field,
    debt: Field,
    payment_history: Field,
    credit_age: Field,
    secret: Field,
    nullifier: Field,
    // 公開輸入
    min_threshold: Field
) -> pub Field {
    // 構建信用評分結構
    let score = CreditScore {
        income,
        debt,
        payment_history,
        credit_age
    };
    
    let inputs = ProofInputs {
        score,
        threshold: min_threshold,
        secret,
        nullifier
    };
    
    // 執行驗證
    let result = verify_credit_score(inputs, min_threshold);
    
    // 輸出結果(公開)
    result
}

// 測試用例
#[test]
fn test_credit_score_pass() {
    let result = main(
        80000,    // income
        20000,    // debt  
        95,       // payment_history
        5,        // credit_age
        12345,    // secret
        67890,    // nullifier
        700       // min_threshold
    );
    
    assert(result == 1);
}

Halo2 框架

Halo2是ECC(Electric Coin Company)開發的ZK證明系統,特點是:

// Halo2 電路範例:年齡驗證
// 使用 Rust 語言

use halo2_proofs::{
    circuit::*,    // 曲線和證明系統
    plonk::*,
    poly::group::CurveAffine,
    SerdeFormat,
};
use std::marker::PhantomData;
use halo2curves::bn256::Bn256;

// 配置晶胞
#[derive(Clone)]
struct AgeVerificationConfig {
    primary: Column<Advice>,
    selector: Selector,
    age: Column<Instance>,  // 公開輸入:年齡
    is_ adult: Column<Instance>,  // 公開輸出:是否成年
}

// 簡單的年齡驗證電路
struct AgeVerificationCircuit<F: Field> {
    age: Number<F>,  // 私有輸入
    _phantom: PhantomData<F>,
}

impl<F: Field> Circuit<F> for AgeVerificationCircuit<F> {
    type Config = AgeVerificationConfig;
    type FloorPlanner = SimpleFloorPlanner;
    
    fn without_witnesses(&self) -> Self {
        Self {
            age: Number::default(),
            _phantom: PhantomData,
        }
    }
    
    fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
        let primary = meta.advice_column();
        let selector = meta.selector();
        let age = meta.instance_column();
        let is_adult = meta.instance_column();
        
        // 啟用相等性約束
        meta.enable_equality(age);
        meta.enable_equality(is_adult);
        
        // 創建門制約
        meta.create_gate("age_verification", |cells| {
            let s = cells.query_selector(selector);
            let age_value = cells.query_advice(primary, Rotation::cur());
            
            // 約束:age >= 18
            // 使用簡單的多項式約束
            vec![s * (age_value - Constant(F::from(18)))]
        });
        
        AgeVerificationConfig {
            primary,
            selector,
            age,
            is_adult,
        }
    }
    
    fn synthesize(
        &self,
        config: Self::Config,
        mut layouter: impl Layouter<F>
    ) -> Result<(), Error> {
        // 分配年齡值
        let age_cell = layouter.assign_region(
            || "age input",
            |mut region| {
                region.assign_advice(
                    || "age",
                    config.primary,
                    0,
                    || self.age.value().copied()
                )?;
                
                // 啟用選擇器
                config.selector.enable(&mut region, 0)?;
                
                Ok(())
            }
        )?;
        
        // 計算是否成年(>= 18)
        let is_adult_value = self.age.value().map(|age| {
            if age >= &F::from(18) { F::one() } 
            else { F::zero() }
        });
        
        // 暴露公開輸出
        layouter.constrain_instance(
            age_cell.cell(),
            config.age,
            0
        )?;
        
        layouter.assign_instance(
            || "is_adult",
            config.is_adult,
            0,
            || is_adult_value.unwrap_or(F::zero())
        )?;
        
        Ok(())
    }
}

1.3 證明生成與驗證流程

證明生成流程

# Python 客戶端:證明生成流程
# 完整的 ZKML 證明生成流程

import json
import numpy as np
from pathlib import Path
from snarkjs import groth16
from web3 import Web3

class ZKMLProofGenerator:
    """ZKML 證明生成器"""
    
    def __init__(self, circuit_path, zkey_path):
        self.circuit_path = circuit_path
        self.zkey_path = zkey_path
        self.witness_generator = WitnessGenerator(circuit_path)
    
    def prepare_inputs(self, model_input, model_weights, threshold):
        """準備電路輸入"""
        # 將模型輸入序列化為電路格式
        # 注意:需要根據具體電路設計調整
        
        # 將numpy數組轉換為定點表示
        def to_fixed_point(arr, precision=16):
            return [int(x * (2**precision)) for x in arr.flatten()]
        
        inputs = {
            'inputs': to_fixed_point(model_input),
            'weights1': to_fixed_point(model_weights['layer1']),
            'bias1': to_fixed_point(model_weights['bias1']),
            'weights2': to_fixed_point(model_weights['layer2']),
            'bias2': to_fixed_point(model_weights['bias2']),
            'threshold': to_fixed_point(np.array([threshold]))[0]
        }
        
        return inputs
    
    def generate_witness(self, inputs, input_file='input.json'):
        """生成見證"""
        # 將輸入寫入文件
        with open(input_file, 'w') as f:
            json.dump(inputs, f)
        
        # 使用 snarkjs 生成見證
        # 這需要 Node.js 環境
        import subprocess
        
        cmd = [
            'node', 'generate_witness.js',
            self.circuit_path,
            input_file,
            'witness.wtns'
        ]
        subprocess.run(cmd, check=True)
        
        return 'witness.wtns'
    
    def generate_proof(self, witness_file, proving_key='circuit_final.zkey'):
        """生成零知識證明"""
        # 使用 groth16 證明生成
        proof = groth16.full_proof(
            witness_file,
            self.zkey_path,
            proving_key
        )
        
        return proof
    
    def export_calldata(self, proof, public_signals):
        """導出可提交到智慧合約的調用數據"""
        # 將證明和公開信號轉換為solidity驗證器需要的格式
        
        calldata = {
            'a': [hex(proof['a'][0]), hex(proof['a'][1])],
            'b': [
                [hex(proof['b'][0][0]), hex(proof['b'][0][1])],
                [hex(proof['b'][1][0]), hex(proof['b'][1][1])]
            ],
            'c': [hex(proof['c'][0]), hex(proof['c'][1])],
            'publicSignals': [hex(s) for s in public_signals]
        }
        
        return calldata
    
    def generate_and_submit(
        self, 
        model_input, 
        model_weights, 
        threshold,
        private_key,
        contract_address,
        rpc_url
    ):
        """完整的證明生成和提交流程"""
        
        # 步驟1:準備輸入
        inputs = self.prepare_inputs(model_input, model_weights, threshold)
        
        # 步驟2:生成見證
        witness_file = self.generate_witness(inputs)
        
        # 步驟3:生成證明
        proof = self.generate_proof(witness_file)
        
        # 步驟4:導出調用數據
        calldata = self.export_calldata(
            proof['proof'], 
            proof['publicSignals']
        )
        
        # 步驟5:提交到區塊鏈
        web3 = Web3(Web3.HTTPProvider(rpc_url))
        account = web3.eth.account.from_key(private_key)
        
        contract = web3.eth.contract(
            address=contract_address,
            abi=self.get_contract_abi()
        )
        
        tx = contract.functions.submitProof(
            calldata['a'],
            calldata['b'],
            calldata['c'],
            calldata['publicSignals']
        ).buildTransaction({
            'from': account.address,
            'nonce': web3.eth.get_transaction_count(account.address),
            'gas': 500000,
            'gasPrice': web3.eth.gas_price
        })
        
        signed_tx = account.sign_transaction(tx)
        tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
        
        return tx_hash

# 使用示例
def main():
    generator = ZKMLProofGenerator(
        circuit_path='./build/circuit.wasm',
        zkey_path='./build/circuit_0000.zkey'
    )
    
    # 模型輸入(示例)
    model_input = np.array([[50000, 10000, 80, 3]])  # 收入、負債、分數、年齡
    model_weights = {
        'layer1': np.random.randn(4, 8),
        'bias1': np.zeros(8),
        'layer2': np.random.randn(8, 1),
        'bias2': np.zeros(1)
    }
    threshold = 650
    
    tx_hash = generator.generate_and_submit(
        model_input=model_input,
        model_weights=model_weights,
        threshold=threshold,
        private_key='0x...',  # 錢包私鑰
        contract_address='0x...',  // 驗證合約地址
        rpc_url='https://eth.llamarpc.com'
    )
    
    print(f"交易已提交: {tx_hash.hex()}")

if __name__ == '__main__':
    main()

智慧合約驗證

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/**
 * @title ZKMLVerifier
 * @dev ZKML 證明驗證智慧合約
 * @notice 使用 Groth16 驗證 ZKML 證明
 */
contract ZKMLVerifier {
    
    // ============ 驗證器接口 ============
    
    // Groth16 驗證器接口
    // 實際部署時需要根據具體電路生成
    
    // 驗證密鑰結構
    struct VerifyingKey {
        uint256[2] alpha;
        uint256[2][2] beta;
        uint256[2][2] gamma;
        uint256[2][2] delta;
        uint256[2][] gamma_abc;
    }
    
    // 驗證密鑰(從trusted setup生成)
    VerifyingKey public verifyingKey;
    
    // 驗證標記
    bool public isVerified;
    
    // 事件
    event ProofVerified(
        address indexed submitter,
        uint256 indexed result,
        bool success
    );
    
    constructor() {
        // 初始化驗證密鑰(示例,實際需要從trusted setup獲取)
        // 這裡使用佔位符,實際部署時替換為真實密鑰
        _initializeVerifyingKey();
    }
    
    function _initializeVerifyingKey() internal {
        // 驗證密鑰初始化
        // 注意:以下為示例值,實際部署需要使用真實的trusted setup輸出
        
        verifyingKey.alpha = [
            uint256(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef),
            uint256(0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321)
        ];
        
        verifyingKey.beta = [
            [
                uint256(0x1111111111111111111111111111111111111111111111111111111111111111),
                uint256(0x2222222222222222222222222222222222222222222222222222222222222222)
            ],
            [
                uint256(0x3333333333333333333333333333333333333333333333333333333333333333),
                uint256(0x4444444444444444444444444444444444444444444444444444444444444444)
            ]
        ];
        
        // ... 其他驗證密鑰初始化
    }
    
    /**
     * @dev 驗證 Groth16 證明
     * @param a 第一個證明元素 (G1點)
     * @param b 第二個證明元素 (G2點)
     * @param c 第三個證明元素 (G1點)
     * @param publicInputs 公開輸入
     * @return 驗證結果
     */
    function verifyProof(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[] memory publicInputs
    ) public returns (bool) {
        // 驗證 AAA 配對
        // 這是 Groth16 驗證的核心邏輯
        
        // 計算配對
        uint256[24] memory input = [
            // publicInputs
            publicInputs[0], publicInputs[1], publicInputs[2], publicInputs[3],
            publicInputs[4], publicInputs[5], publicInputs[6], publicInputs[7],
            // 填充
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0
        ];
        
        // 調用預編譯合約進行配對檢查
        // AltBN128 配對預編譯合約地址: 0x08
        // 注意:這裡需要使用真實的驗證邏輯
        
        bool success = _pairingCheck(a, b, c, input);
        
        isVerified = success;
        
        emit ProofVerified(msg.sender, publicInputs[0], success);
        
        return success;
    }
    
    /**
     * @dev 配對檢查內部函數
     */
    function _pairingCheck(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[24] memory input
    ) internal view returns (bool) {
        // 使用 AltBN128 配對預編譯合約
        // 地址: 0x08
        
        bytes32[1] memory result;
        
        assembly {
            // 準備配對檢查輸入
            let ptr := mload(0x40)
            
            // 複製 a 點
            mstore(ptr, mload(a))
            mstore(add(ptr, 0x20), mload(add(a, 0x20)))
            
            // 複製 b 點
            mstore(add(ptr, 0x40), mload(b))
            mstore(add(ptr, 0x60), mload(add(b, 0x20)))
            mstore(add(ptr, 0x80), mload(add(b, 0x40)))
            mstore(add(ptr, 0xa0), mstore(add(b, 0x60)))
            
            // 調用配對預編譯合約
            // 這是簡化版本,實際需要完整的配對計算
        }
        
        // 實際驗證邏輯
        // 這裡需要實現完整的 Groth16 驗證算法
        
        return true;  // 佔位符
    }
    
    /**
     * @dev 批量驗證多個證明
     */
    function batchVerify(
        uint256[2][] memory a,
        uint256[2][2][] memory b,
        uint256[2][] memory c,
        uint256[][] memory publicInputs
    ) public returns (bool[] memory) {
        uint256 length = a.length;
        bool[] memory results = new bool[](length);
        
        for (uint256 i = 0; i < length; i++) {
            results[i] = verifyProof(a[i], b[i], c[i], publicInputs[i]);
        }
        
        return results;
    }
}

/**
 * @title ZKMLCreditScoreVerifier
 * @dev 特定於信用評分驗證的合約
 */
contract ZKMLCreditScoreVerifier is ZKMLVerifier {
    
    // 信用評分相關常量
    uint256 public constant MIN_CREDIT_SCORE = 650;
    uint256 public constant MAX_CREDIT_SCORE = 850;
    
    // 用戶信用記錄
    mapping(address => bool) public verifiedUsers;
    mapping(address => uint256) public userCreditScores;
    
    // 事件
    event CreditScoreVerified(
        address indexed user,
        uint256 score,
        bool meetsMinimum
    );
    
    /**
     * @dev 驗證用戶信用評分
     * @param a 證明元素 A
     * @param b 證明元素 B  
     * @param c 證明元素 C
     * @param score 信用評分(公開輸入)
     */
    function verifyCreditScore(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256 score
    ) public returns (bool) {
        uint256[] memory publicInputs = new uint256[](1);
        publicInputs[0] = score;
        
        bool valid = verifyProof(a, b, c, publicInputs);
        
        if (valid && score >= MIN_CREDIT_SCORE) {
            verifiedUsers[msg.sender] = true;
            userCreditScores[msg.sender] = score;
            
            emit CreditScoreVerified(msg.sender, score, true);
        } else {
            emit CreditScoreVerified(msg.sender, score, false);
        }
        
        return valid;
    }
    
    /**
     * @dev 檢查用戶是否通過驗證
     */
    function isUserVerified(address user) public view returns (bool) {
        return verifiedUsers[user];
    }
    
    /**
     * @dev 獲取用戶信用評分
     */
    function getUserScore(address user) public view returns (uint256) {
        require(verifiedUsers[user], "User not verified");
        return userCreditScores[user];
    }
}

第二章:ZKML 在 DeFi 中的應用實作

2.1 信用評估應用

完整信用評估系統架構

// TypeScript: ZKML 信用評估客戶端

import { ethers } from 'ethers';
import { ZKMLProofGenerator } from './zkml_proof_generator';
import { CreditScoreModel } from './credit_model';

interface UserFinancialData {
    income: number;           // 年收入(美元)
    debt: number;            // 總負債(美元)
    creditAge: number;       // 信用年齡(年)
    paymentHistory: number;  // 付款歷史分數(0-100)
    employmentYears: number; // 工作年數
    loanCount: number;       // 現有贷款數量
}

interface CreditScoreResult {
    score: number;
    tier: 'excellent' | 'good' | 'fair' | 'poor';
    meetsThreshold: boolean;
}

class ZKMLCreditEvaluator {
    private proofGenerator: ZKMLProofGenerator;
    private model: CreditScoreModel;
    private verifierContract: ethers.Contract;
    private minThreshold: number;
    
    constructor(
        proverUrl: string,
        verifierAddress: string,
        minThreshold: number = 650
    ) {
        this.proofGenerator = new ZKMLProofGenerator(proverUrl);
        this.model = new CreditScoreModel();
        this.minThreshold = minThreshold;
        
        // 初始化合約
        const provider = new ethers.JsonRpcProvider(
            process.env.ETH_RPC_URL
        );
        const wallet = new ethers.Wallet(
            process.env.PRIVATE_KEY,
            provider
        );
        
        this.verifierContract = new ethers.Contract(
            verifierAddress,
            VERIFIER_ABI,
            wallet
        );
    }
    
    /**
     * @dev 計算信用評分(本地執行)
     */
    calculateScore(data: UserFinancialData): CreditScoreResult {
        // 標準化輸入
        const normalized = this.normalizeInputs(data);
        
        // 模型推理
        const rawScore = this.model.predict(normalized);
        
        // 映射到信用評分範圍
        const score = Math.min(
            Math.max(Math.round(rawScore), 300),
            850
        );
        
        // 確定評級
        let tier: CreditScoreResult['tier'];
        if (score >= 800) tier = 'excellent';
        else if (score >= 700) tier = 'good';
        else if (score >= 650) tier = 'fair';
        else tier = 'poor';
        
        return {
            score,
            tier,
            meetsThreshold: score >= this.minThreshold
        };
    }
    
    /**
     * @dev 生成零知識證明並提交
     */
    async proveAndSubmit(
        data: UserFinancialData,
        userAddress: string
    ): Promise<{ txHash: string; score: number }> {
        // 步驟1: 計算評分
        const result = this.calculateScore(data);
        
        // 步驟2: 準備證明輸入
        const inputs = this.prepareProofInputs(data, result.score);
        
        // 步驟3: 生成零知識證明
        // 注意:這一步通常在用戶端或專門的證明服務上執行
        const proof = await this.proofGenerator.generateProof(inputs);
        
        // 步驟4: 提交證明到區塊鏈
        const tx = await this.verifierContract.verifyCreditScore(
            proof.a,
            proof.b,
            proof.c,
            result.score,
            { gasLimit: 500000 }
        );
        
        await tx.wait();
        
        return {
            txHash: tx.hash,
            score: result.score
        };
    }
    
    /**
         * @dev 準備證明輸入(轉換為定點數)
     */
    private prepareProofInputs(
        data: UserFinancialData,
        score: number
    ): any {
        const FIXED_POINT_PRECISION = 2 ** 16;
        
        return {
            inputs: [
                Math.round(data.income / 1000) * FIXED_POINT_PRECISION / 1000,
                Math.round(data.debt / 1000) * FIXED_POINT_PRECISION / 1000,
                data.creditAge * FIXED_POINT_PRECISION,
                data.paymentHistory * FIXED_POINT_PRECISION / 100,
                data.employmentYears * FIXED_POINT_PRECISION,
                data.loanCount * FIXED_POINT_PRECISION
            ],
            threshold: this.minThreshold * FIXED_POINT_PRECISION,
            // 額外的安全性參數
            secret: ethers.randomBytes(32),
            nullifier: ethers.keccak256(ethers.randomBytes(32))
        };
    }
    
    /**
     * @dev 規範化輸入數據
     */
    private normalizeInputs(data: UserFinancialData): number[] {
        // 假設使用標準化參數
        const mean = [60000, 15000, 5, 75, 3, 2];
        const std = [30000, 10000, 3, 20, 2, 1];
        
        return [
            (data.income - mean[0]) / std[0],
            (data.debt - mean[1]) / std[1],
            (data.creditAge - mean[2]) / std[2],
            (data.paymentHistory - mean[3]) / std[3],
            (data.employmentYears - mean[4]) / std[4],
            (data.loanCount - mean[5]) / std[5]
        ];
    }
}

// 使用示例
async function main() {
    const evaluator = new ZKMLCreditEvaluator(
        'https://prover.example.com',
        '0x1234567890123456789012345678901234567890',
        700
    );
    
    const userData: UserFinancialData = {
        income: 85000,
        debt: 12000,
        creditAge: 7,
        paymentHistory: 92,
        employmentYears: 5,
        loanCount: 1
    };
    
    // 計算本地評分
    const result = evaluator.calculateScore(userData);
    console.log('信用評分:', result);
    
    // 生成證明並提交(需要錢包連接)
    // const { txHash, score } = await evaluator.proveAndSubmit(
    //     userData,
    //     '0x...'
    // );
}

export { ZKMLCreditEvaluator, UserFinancialData, CreditScoreResult };

2.2 借貸協議應用

ZKML 借貸合約整合

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./ZKMLCreditScoreVerifier.sol";

/**
 * @title ZKML Lending Pool
 * @dev 整合 ZKML 信用驗證的去中心化借貸池
 */
contract ZKMLLendingPool is ReentrancyGuard {
    
    // ============ 狀態變數 ============
    
    // 代幣
    IERC20 public immutable collateralToken;
    IERC20 public immutable borrowToken;
    
    // ZKML 驗證器
    ZKMLCreditScoreVerifier public creditVerifier;
    
    // 借貸參數
    uint256 public constant COLLATERAL_RATIO = 150;  // 150% 抵押率
    uint256 public constant LIQUIDATION_THRESHOLD = 130;  // 130% 清算門檻
    uint256 public constant MIN_CREDIT_SCORE = 650;   // 最低信用評分
    
    // 利率模型
    uint256 public constant BASE_RATE = 5e16;  // 5% 年利率
    uint256 public constant UTIL_OPTIMAL = 80e16;  // 80% 最佳利用率
    
    // 用戶帳戶
    struct Account {
        uint256 collateral;      // 抵押品數量
        uint256 borrowed;        // 借款數量
        uint256 lastUpdate;      // 上次更新時間
        bool verified;           // 是否通過ZKML驗證
        uint256 creditScore;     // 信用評分
    }
    
    mapping(address => Account) public accounts;
    
    // 事件
    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event Borrow(address indexed user, uint256 amount);
    event Repay(address indexed user, uint256 amount);
    event Liquidate(
        address indexed liquidator,
        address indexed user,
        uint256 amount
    );
    event CreditVerified(address indexed user, uint256 score);
    
    // ============ 構造函數 ============
    
    constructor(
        address _collateralToken,
        address _borrowToken,
        address _creditVerifier
    ) {
        collateralToken = IERC20(_collateralToken);
        borrowToken = IERC20(_borrowToken);
        creditVerifier = ZKMLCreditScoreVerifier(_creditVerifier);
    }
    
    // ============ 存款功能 ============
    
    /**
     * @dev 存入抵押品
     */
    function deposit(uint256 amount) external nonReentrant {
        require(amount > 0, "Amount must be greater than 0");
        
        Account storage account = accounts[msg.sender];
        
        // 轉入代幣
        require(
            collateralToken.transferFrom(msg.sender, address(this), amount),
            "Transfer failed"
        );
        
        account.collateral += amount;
        account.lastUpdate = block.timestamp;
        
        emit Deposit(msg.sender, amount);
    }
    
    /**
     * @dev 提取抵押品
     */
    function withdraw(uint256 amount) external nonReentrant {
        Account storage account = accounts[msg.sender];
        require(amount > 0 && amount <= account.collateral, "Invalid amount");
        
        // 檢查提取後是否會觸發清算
        if (account.borrowed > 0) {
            uint256 healthFactor = _calculateHealthFactor(
                account.collateral - amount,
                account.borrowed,
                account.creditScore
            );
            require(
                healthFactor >= 100,
                "Health factor would be too low"
            );
        }
        
        account.collateral -= amount;
        
        require(
            collateralToken.transfer(msg.sender, amount),
            "Transfer failed"
        );
        
        emit Withdraw(msg.sender, amount);
    }
    
    // ============ 借款功能 ============
    
    /**
     * @dev 借款
     */
    function borrow(uint256 amount) external nonReentrant {
        Account storage account = accounts[msg.sender];
        
        // 檢查是否通過信用驗證
        require(account.verified, "Credit not verified");
        
        // 檢查信用評分
        require(
            account.creditScore >= MIN_CREDIT_SCORE,
            "Credit score too low"
        );
        
        // 計算健康因子
        uint256 healthFactor = _calculateHealthFactor(
            account.collateral,
            account.borrowed + amount,
            account.creditScore
        );
        require(healthFactor >= 100, "Health factor too low");
        
        // 計算借款限額
        uint256 maxBorrow = _calculateMaxBorrow(
            account.collateral,
            account.creditScore
        );
        require(
            account.borrowed + amount <= maxBorrow,
            "Exceeds borrowing limit"
        );
        
        account.borrowed += amount;
        account.lastUpdate = block.timestamp;
        
        require(
            borrowToken.transfer(msg.sender, amount),
            "Transfer failed"
        );
        
        emit Borrow(msg.sender, amount);
    }
    
    /**
     * @dev 還款
     */
    function repay(uint256 amount) external nonReentrant {
        Account storage account = accounts[msg.sender];
        require(amount > 0 && amount <= account.borrowed, "Invalid amount");
        
        // 計算利息
        uint256 interest = _calculateInterest(
            account.borrowed,
            account.lastUpdate
        );
        
        // 轉入代幣
        require(
            borrowToken.transferFrom(msg.sender, address(this), amount),
            "Transfer failed"
        );
        
        account.borrowed = account.borrowed + interest - amount;
        account.lastUpdate = block.timestamp;
        
        emit Repay(msg.sender, amount);
    }
    
    // ============ ZKML 驗證 ============
    
    /**
     * @dev 提交 ZKML 信用評分證明
     */
    function submitCreditProof(
        uint256[2] calldata a,
        uint256[2][2] calldata b,
        uint256[2] calldata c,
        uint256 score
    ) external {
        // 驗證 ZKML 證明
        bool valid = creditVerifier.verifyCreditScore(a, b, c, score);
        
        require(valid, "Invalid proof");
        require(score >= MIN_CREDIT_SCORE, "Score below minimum");
        
        Account storage account = accounts[msg.sender];
        account.verified = true;
        account.creditScore = score;
        
        emit CreditVerified(msg.sender, score);
    }
    
    // ============ 清算功能 ============
    
    /**
     * @dev 清算健康因子不足的帳戶
     */
    function liquidate(address user) external nonReentrant {
        Account storage account = accounts[user];
        
        uint256 healthFactor = _calculateHealthFactor(
            account.collateral,
            account.borrowed,
            account.creditScore
        );
        
        require(
            healthFactor < LIQUIDATION_THRESHOLD * 100,
            "Account is healthy"
        );
        
        // 計算清算金額(最大為債務的50%)
        uint256 maxLiquidate = (account.borrowed * 50) / 100;
        
        // 獎勵清算人
        uint256 reward = (maxLiquidate * 5) / 100;  // 5% 獎勵
        
        account.borrowed -= maxLiquidate;
        account.collateral -= (maxLiquidate + reward);
        
        require(
            borrowToken.transfer(msg.sender, maxLiquidate),
            "Transfer to liquidator failed"
        );
        
        require(
            collateralToken.transfer(msg.sender, reward),
            "Transfer reward failed"
        );
        
        emit Liquidate(msg.sender, user, maxLiquidate);
    }
    
    // ============ 內部函數 ============
    
    /**
     * @dev 計算健康因子
     */
    function _calculateHealthFactor(
        uint256 collateral,
        uint256 debt,
        uint256 creditScore
    ) internal pure returns (uint256) {
        if (debt == 0) return 10000;
        
        // 信用評分加成
        uint256 creditMultiplier = 100 + (creditScore - 650) / 10;
        
        return (collateral * COLLATERAL_RATIO * creditMultiplier) / (debt * 100);
    }
    
    /**
     * @dev 計算最大借款額度
     */
    function _calculateMaxBorrow(
        uint256 collateral,
        uint256 creditScore
    ) internal pure returns (uint256) {
        // 基礎借款限額為抵押品的 60%
        uint256 baseLimit = (collateral * 60) / 100;
        
        // 信用評分加成
        uint256 creditBonus = creditScore >= 750 ? 120 :
                              creditScore >= 700 ? 110 :
                              creditScore >= 650 ? 100 : 0;
        
        return (baseLimit * creditBonus) / 100;
    }
    
    /**
     * @dev 計算利息
     */
    function _calculateInterest(
        uint256 debt,
        uint256 lastUpdate
    ) internal view returns (uint256) {
        uint256 timeElapsed = block.timestamp - lastUpdate;
        uint256 rate = (BASE_RATE * timeElapsed) / (365 days);
        return (debt * rate) / 1e18;
    }
}

2.3 預測市場應用

ZKML 預測市場合約

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title ZKMLPredictionMarket
 * @dev 使用 ZKML 驗證預測結果的預測市場
 */
contract ZKMLPredictionMarket is ReentrancyGuard {
    
    // ============ 類型定義 ============
    
    enum MarketStatus { 
        Open,      // 開放預測
        Closed,    // 預測結束
        Resolved   // 結果確定
    }
    
    enum Position { 
        Yes,       // 押注「是」
        No         // 押注「否」
    }
    
    struct Market {
        string question;           // 預測問題
        uint256 endTime;           // 結束時間
        uint256 resolveTime;      // 結果揭曉時間
        MarketStatus status;       // 市場狀態
        uint256 totalYes;          // 總押注「是」的金額
        uint256 totalNo;           // 總押注「否」的金額
        bool result;               // 預測結果
        address creator;           // 創建者
    }
    
    struct PositionInfo {
        uint256 amount;            // 押注金額
        uint256 timestamp;        // 押注時間
        bool claimed;             // 是否已領取獎勵
    }
    
    // ============ 狀態變數 ============
    
    IERC20 public immutable token;
    mapping(uint256 => Market) public markets;
    mapping(uint256 => mapping(address => PositionInfo)) public positions;
    mapping(uint256 => mapping(address => bool)) public hasVerifiedPrediction;
    
    uint256 public marketCount;
    
    // ZKML 驗證器地址(用於驗證預測準確率證明)
    address public zkmlVerifier;
    
    // 事件
    event MarketCreated(
        uint256 indexed marketId,
        string question,
        uint256 endTime
    );
    event PositionTaken(
        uint256 indexed marketId,
        address indexed user,
        Position position,
        uint256 amount
    );
    event MarketResolved(
        uint256 indexed marketId,
        bool result
    );
    event RewardClaimed(
        uint256 indexed marketId,
        address indexed user,
        uint256 reward
    );
    
    // ============ 構造函數 ============
    
    constructor(address _token, address _zkmlVerifier) {
        token = IERC20(_token);
        zkmlVerifier = _zkmlVerifier;
    }
    
    // ============ 市場管理 ============
    
    /**
     * @dev 創建預測市場
     */
    function createMarket(
        string memory question,
        uint256 duration,
        uint256 resolveDelay
    ) external returns (uint256) {
        uint256 marketId = marketCount++;
        
        markets[marketId] = Market({
            question: question,
            endTime: block.timestamp + duration,
            resolveTime: block.timestamp + duration + resolveDelay,
            status: MarketStatus.Open,
            totalYes: 0,
            totalNo: 0,
            result: false,
            creator: msg.sender
        });
        
        emit MarketCreated(marketId, question, markets[marketId].endTime);
        
        return marketId;
    }
    
    /**
     * @dev 押注
     */
    function takePosition(
        uint256 marketId,
        Position position,
        uint256 amount
    ) external nonReentrant {
        Market storage market = markets[marketId];
        
        require(market.status == MarketStatus.Open, "Market not open");
        require(block.timestamp < market.endTime, "Market closed");
        require(amount > 0, "Amount must be greater than 0");
        
        // 轉入代幣
        require(
            token.transferFrom(msg.sender, address(this), amount),
            "Transfer failed"
        );
        
        // 更新市場總額
        if (position == Position.Yes) {
            market.totalYes += amount;
        } else {
            market.totalNo += amount;
        }
        
        // 記錄用戶持倉
        PositionInfo storage pos = positions[marketId][msg.sender];
        pos.amount += amount;
        pos.timestamp = block.timestamp;
        
        emit PositionTaken(marketId, msg.sender, position, amount);
    }
    
    /**
     * @dev 提交 ZKML 預測準確率證明
     * 
     * 用戶可以提交證明來驗證其預測模型的準確率
     * 這可以作為「專業預測者」的認證
     */
    function submitPredictionProof(
        uint256[2] calldata a,
        uint256[2][2] calldata b,
        uint256[2] calldata c,
        uint256 accuracy
    ) external {
        // 驗證 ZKML 證明
        // 這裡調用外部 ZKML 驗證合約
        
        // 標記用戶已驗證
        require(accuracy >= 70, "Accuracy too low");  // 至少70%準確率
        
        hasVerifiedPrediction[msg.sender] = true;
    }
    
    /**
     * @dev 解決市場(預測結果揭曉)
     */
    function resolveMarket(uint256 marketId, bool result) external {
        Market storage market = markets[marketId];
        
        require(market.creator == msg.sender, "Not authorized");
        require(
            block.timestamp >= market.resolveTime,
            "Resolve time not reached"
        );
        require(market.status == MarketStatus.Open, "Already resolved");
        
        market.status = MarketStatus.Resolved;
        market.result = result;
        
        emit MarketResolved(marketId, result);
    }
    
    /**
     * @dev 領取獎勵
     */
    function claimReward(uint256 marketId) external nonReentrant {
        Market storage market = markets[marketId];
        PositionInfo storage pos = positions[marketId][msg.sender];
        
        require(market.status == MarketStatus.Resolved, "Market not resolved");
        require(pos.amount > 0, "No position");
        require(!pos.claimed, "Already claimed");
        
        // 確定獲勝方
        Position winningPosition = market.result ? Position.Yes : Position.No;
        
        // 計算獎勵
        // 注意:這裡使用簡化的獎勵計算,實際應用需要更複雜的公式
        uint256 winningTotal = market.result ? market.totalYes : market.totalNo;
        uint256 totalStaked = pos.amount;
        
        uint256 reward;
        if (winningTotal > 0) {
            reward = (totalStaked * market.totalYes * market.totalNo) / 
                     (winningTotal * (market.totalYes + market.totalNo));
            
            // 專業預測者加成
            if (hasVerifiedPrediction[msg.sender]) {
                reward = (reward * 110) / 100;  // 10% 加成
            }
        } else {
            reward = totalStaked;  // 退還本金
        }
        
        pos.claimed = true;
        
        require(
            token.transfer(msg.sender, reward),
            "Transfer failed"
        );
        
        emit RewardClaimed(marketId, msg.sender, reward);
    }
    
    // ============ 查詢函數 ============
    
    /**
     * @dev 計算用戶預期獎勵
     */
    function pendingReward(
        uint256 marketId,
        address user
    ) external view returns (uint256) {
        Market storage market = markets[marketId];
        PositionInfo storage pos = positions[marketId][user];
        
        if (market.status != MarketStatus.Resolved || pos.claimed || pos.amount == 0) {
            return 0;
        }
        
        uint256 winningTotal = market.result ? market.totalYes : market.totalNo;
        
        if (winningTotal == 0) return pos.amount;
        
        return (pos.amount * market.totalYes * market.totalNo) / 
               (winningTotal * (market.totalYes + market.totalNo));
    }
}

第三章:工程實踐與最佳實踐

3.1 性能優化

電路優化技術

// Circom 電路優化示例

// 優化1:使用信號批處理減少約束數量
// 原始版本
template UnoptimizedBatchVerify(n) {
    signal input values[n];
    signal input results[n];
    
    for (var i = 0; i < n; i++) {
        // 每個比較都是獨立的約束
        signal result <-- values[i] > 128 ? 1 : 0;
        result * (result - 1) === 0;
    }
}

// 優化版本:使用二進制分解
template OptimizedBatchVerify(n) {
    signal input values[n];
    signal input results[n];
    
    // 使用多個信號來表示範圍
    component comparators[n];
    
    for (var i = 0; i < n; i++) {
        comparators[i] = GreaterThan(8);
        comparators[i].in[0] <== 128;
        comparators[i].in[1] <== values[i];
        
        // 約束數量相同,但可以共享計算
    }
}

// 優化2:使用查找表減少約束複雜度
template OptimizedActivation() {
    signal input x;
    signal output y;
    
    // ReLU 的查找表實現
    // 將輸入域劃分為多個區間
    var N_RANGES = 8;
    var RANGE_SIZE = 32;  // 256 / 8 = 32
    
    // 計算區間索引
    component divider = IntegerDivide(8);
    divider.in[0] <== x;
    divider.in[1] <== RANGE_SIZE;
    
    // 計算區間內的偏移
    component multiplier = Multiply(8);
    multiplier.in[0] <== divider.out;
    multiplier.in[1] <== RANGE_SIZE;
    
    component subtractor = Subtractor(8);
    subtractor.in[0] <== x;
    subtractor.in[1] <== multiplier.out;
    
    // 使用選擇器實現分段線性近似
    component mux = Mux8(8);
    mux.s <== divider.out;
    
    // 這裡可以實現更高效的 ReLU
}

// 優化3:預編譯電路重用
// 定義可重用的組件
template PoseidonHash() {
    signal input[2] inputs;
    signal output hash;
    
    component hasher = Poseidon(2);
    hasher.inputs <== inputs;
    hash <== hasher.out;
}

// 在主電路中重複使用
template OptimizedHashVerification(n) {
    signal input leaves[n];
    signal input root;
    
    component hashers[n-1];
    
    // 預分配哈希組件
    for (var i = 0; i < n-1; i++) {
        hashers[i] = PoseidonHash();
    }
    
    // Merkle 樹構建
    // ...
}

3.2 安全性考量

ZKML 安全檢查清單

// ZKML 智慧合約安全檢查清單

/**
 * ZKML 應用安全檢查清單
 * 
 * 1. 證明驗證
 * □ 總是驗證零知識證明的有效性
 * □ 檢查公開輸入是否在預期範圍內
 * □ 防止重放攻擊(使用 nullifier)
 * 
 * 2. 時間相關
 * □ 防止預言機操縱(使用時間加權平均)
 * □ 設置合理的到期時間
 * □ 考慮網路延遲
 * 
 * 3. 資金安全
 * □ 實現訪問控制
 * □ 防止整數溢出
 * □ 實現速率限制
 * 
 * 4. 隱私保護
 * □ 不在鏈上存儲敏感數據
 * □ 使用承諾方案而非直接存儲
 * □ 實施選擇性揭露
 */

/**
 * @title ZKMLSecurityUtils
 * @dev ZKML 安全工具庫
 */
library ZKMLSecurityUtils {
    
    /**
     * @dev 驗證 nullifier 防止重放攻擊
     */
    function verifyNullifier(
        bytes32 nullifier,
        bytes32[] storage usedNullifiers
    ) internal returns (bool) {
        // 檢查 nullifier 是否已使用
        for (uint256 i = 0; i < usedNullifiers.length; i++) {
            if (usedNullifiers[i] == nullifier) {
                return false;  // 已使用,防止重放
            }
        }
        
        // 記錄新的 nullifier
        usedNullifiers.push(nullifier);
        return true;
    }
    
    /**
     * @dev 驗證輸入範圍
     */
    function validateInputRange(
        uint256 value,
        uint256 min,
        uint256 max
    ) internal pure returns (bool) {
        return value >= min && value <= max;
    }
    
    /**
     * @dev 安全的事件記錄(避免暴露敏感信息)
     */
    function emitSecureEvent(
        bytes32 indexed topic,
        uint256 value,
        bool success
    ) internal {
        // 只記錄必要的公開信息
        // 不記錄具體的輸入數據
        emit Verified(topic, success ? 1 : 0, block.timestamp);
    }
    
    event Verified(
        bytes32 indexed topic,
        uint256 result,
        uint256 timestamp
    );
}

3.3 部署清單

ZKML 應用部署檢查表

階段檢查項目說明
開發電路測試覆蓋率確保所有路徑都有測試
開發約束數量優化目標 < 100k 約束
測試Trusted Setup 儀式多人參與的設置過程
測試證明生成時間目標 < 30秒
測試驗證 Gas 成本目標 < 200k Gas
安全第三方審計至少兩家審計機構
安全漏洞賞金上線前啟動
部署多簽錢包升級需要多簽批准
監控告警系統異常交易立即通知

結論

ZKML 代表了區塊鏈與人工智慧融合的核心技術方向。通過本指南的學習,讀者應該能夠:

  1. 理解 ZKML 的技術原理和各框架特點
  2. 使用 Circom、Noir 編寫基本的 ZKML 電路
  3. 實現智慧合約中的 ZKML 證明驗證
  4. 設計和開發 ZKML 應用於 DeFi 場景
  5. 遵循安全和性能最佳實踐

隨著以太坊生態系統的不斷發展,ZKML 的應用場景將持續擴展。建議開發者持續關注這個領域的最新進展,並在實際項目中不斷積累經驗。


參考資源

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!