HOME
BLOG
fabric全同态加密合约的设计与实现
10月 21 2022

隐私计算

隐私计算定义

隐私计算是一类在处理和分析计算数据的过程中能保持数据不透明、不泄露、无法被计算方法以及其他非授权方获取技术方案。

隐私计算流派

安全多方计算

双方或多方参与,在不暴露自己所有的信息,得到计算结果。大都基于不经意传输、混淆电路与秘密分享实现,例如百万富翁问题。

两个富翁,分别为张三和李四。他们自己都清楚自己有几千万财产,
也即,他们心里清楚 1~10中的一个数(代表自己千万级的财富);
他们想知道到底谁的数更大一些。

1.张三找10个一模一样的箱子,按照1~10的顺序摆好,
并按照自己的财富值分别往里面放入苹果梨和香蕉,具体放法为:
如果序号小于自己的财富之,放入苹果,相等,则放入梨,大于自己的财富值,放入香蕉;

2.李四在10个箱子里选择符合自己财产的箱子,并销毁其他箱子
(其实只要保证张三不知道李四选择那个箱子即可)

3.张三和李四一起打开箱子:
-- 如果是苹果,张三比李四富有
-- 如果是梨,两人一样有钱
-- 如果是香蕉,李四比张三富有

百万富翁问题采取的就是不经意传输,不经意传输的本质是让发送者发送一些消息,使得发送者不知道接收者需要那条消息,而接收者又只能获取其中自己需要的消息。

1. 假定发送者要发送N条消息,发送者生成N对公私钥,并将N个公钥发送给接收者。

2. 接收者生成一个随机数,并用收到的N个公钥之一加密随机数(用哪个公钥钥取决于想获取哪条数据),
并将密文结果回复给发送者。

3. 发送者用自己的N个私钥分别解密收到随机数密文,
并得到N个解密结果并将N个结果分别与要发送的N条信息进行异或运算,并将结果发给接收者。

4. 接收者用自己的真实随机数与收到的消息分别做异或操作,
得到的N个结果中只有一条为真实数据,其他的为随机数。

联邦学习

联邦学习是一种分布式的机器学习技术,系统中各节点对本地数据进行计算生成模型,再在这些模型上建立一个统一模型,例如输入预测。

输入预测是指输入法的智能联想,输入法本身集成有有联邦学习中的一个节点,
用来学习用户输入习惯,这里并未泄露用户输入数据,输入数据仍留存在本地,
只是通过输入数据构建了输入预测模型,系统中各个节点将用户的输入预测模
型上传到云端,云端服务器根据这些模型计算出统一模型,返还给各个节点。
当然,在真实的场景中,统一模型因该只是用户的个人模型的补充。
在这个过程中,两次模糊了用户的个人信息,所以联邦学习是一种近似计算。

隐私计算层次模型

  • 可信执行环境是通过软硬件方法在中央处理器中构建一个安全区域,保证其内部加载的程序和数据在机密性和完整性上得到保护,使得数据在硬件层面的计算具备可信性,这是保障上层体系的正确性的基石。
  • 秘密分享、同态加密、差分隐私、混淆电路、不经意传输是密码学中的协议或者加密算法,被上层体系所依赖。
  • 安全多方计算、联邦学习本质上是用来处理实际的业务场景的体系,建立在密码学的基础之上。


如果把数据的状态划分为静止、传输、操作三种,那么根本意义上的数据有效性应在在这三种状态下都得到保障。但就实际状况而言,严格的保障数据有效性付出的成本远不及收益,所以密码学通常都会考虑恶意行为。

  • 静止态:指数据处于存储状态,例如存储在外部存储之中。
  • 传输态:指数据从一个位置转移到另一个位置的状态
  • 操作态:指数据正在被使用,进行计算

比如区块链在数据的静止态保障了数据的安全性,https协议在数据的传输态的安全性,可信执行环境保障了数据在操作态的安全性。

同态加密

同态加密:简单来说同态加密就是使得明文和密文在进行相同的运算后结果仍保持一致,
同态加密可以使得计算能在密文上进行

同态加密的分类

  • 部分同态加密:只能做无限次同态加法或者只能做无限次同态乘法操作(只能累加或者累乘)
  • 些许同态加密:可以进行有限次数的任意同态操作(可以做加法或者乘法,但是次数有限)
  • 全同态加密:可以进行无限次的任意同态操作(可以计算任何函数)

为什么说实现加法和乘法就可以计算任何函数?从门电路的角度上,如下与非门真值表所示,“1+AB”可以得到与非门的所有输出(当A和B都为1时,1+AB=2,而非0,因为是门电路是二进制,1+1发生了进位,然而简单门电路,只能记录最低位,所以结果是0),而与非门可以构建任意的门电路。

A B 输出
0 0 1
1 0 1
0 1 1
1 1 0

全同态加密合约的设计与实现

方案的灵感来源于论文《边缘计算下全同态加密智能合约设计》,随后我检索了相关信息,TBaas中fabric的智能合约也使用了相同的模式,论文中所使用的库是微软的SEAL,TBaas使用的是paillierb部分同态加密的内部库。随后我有在github上检索了基于go实现的同态加密库,从中挑选了有过业务与落地经验的Lattigo全同态加密库

Lattigo

Lattigo:基于格的多方同态加密库,具备如下特性:

  • 全RNS BFV和CKKS方案及其各自多方版本的实现。
  • 性能可与最先进的C++库相媲美。
  • 密集键和稀疏键高效、高精度的自举过程,适用于全RNS CKKS。
  • 支持跨平台构建的纯 Go 实现,包括浏览器客户端的 WASM 编译。

相关名词释义

  • 格:是指Lattigo是基于格密码方案实现的,格是一种数学结构,本质上由N维空间中的一组点组成,并具有一定的周期结构,格密码方案是指基于基向量来生成最短的独立向量作为秘密向量(数学家和密码学家们普遍认为,对于一个维数足够高的格,通过一组随机选取的格基找到一组短格基,或得到一组线性无关的短格向量是困难的。这个问题称作最短独立向量问题)
  • BFV和CKKS:是两种全同态加密的两种方案
  • RNS:剩余数系统,用较少的数表示较多的数

源码结构

  • lattigo/ring:RNS基中多项式的模块化算术运算
  • lattigo/bfv:提供对整数的模块化算术。
  • lattigo/ckks:在复数和实数上提供近似算术。
  • lattigo/dbfv和:BFV和CKKS方案的多方版本,支持具有秘密共享密钥的安全多方计算解决方案。
  • lattigo/rlwe和 :基于 RLWE 的通用多方同态加密的通用基础。

Lattigo方案的可行性分析

使用lattigo/bfv来作为全同态加密合约的支撑工具,使得智能合约失去了浮点数计算能力,本质上来讲,对精度要求要求严苛的场景都不应使用浮点数,浮点数应通过同比扩大或者同比缩小来消除小数或还原小数。同时lattigo/bfv提供有加运算、减运算、非运算、模运算、乘运算、除运算等,满足基本运算需求。fabric的合约具有完整且独立的运行环境,理论上任意的应用程序在接入合约API后都可以作为合约运行,满足lattigo的运行需求。

业务场景

A集团需要统计下属子公司的财报,为了保障财务报表的不可篡改性,财务报表被写入区块链中,但是由于区块链数据本质上是公开的,可能被泄露。如果采用公钥加密财务报表,那统计报表时仍需先获取密文在本地解密后在进行计算,这个过程并不能保证数据不被篡改。

同态加密解决方案

  • 总部为这次报表统计生成专业密钥对
  • 总部使用公钥向合约注册报表统计事件
  • 总部告知分公司事件ID并通知其提交报表
  • 分公司使用事件ID向合约获取事件公钥
  • 分公司使用公钥加密报表数据并提交
  • 总部在所有分公司完成提交后可向合约获取统计结果
  • 合约进行统计,返回统计结果
  • 总部使用私钥解密统计结果

    在整个过程中只有各分公司知道自己提交的内容,链上存储的数据都是密文,需要私钥进行解密,而私钥掌握在总部手中。

合约实现

package main

import (
    "fmt"

    "github.com/hyperledger/fabric-chaincode-go/shim"
    pb "github.com/hyperledger/fabric-protos-go/peer"
    "github.com/tuneinsight/lattigo/v3/bfv"
    "github.com/tuneinsight/lattigo/v3/rlwe"
)

type TurnoverCC struct{}

func (c *TurnoverCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success(nil)
}

func (c *TurnoverCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    args := stub.GetArgs()

    function := string(args[0])

    args = args[1:]

    switch function {
    case "setPubKey":
        err := setPubkey(stub, string(args[0]), args[1])
        if err != nil {
            return shim.Error(err.Error())
        }
        return shim.Success(nil)
    case "getPubKey":
        index := string(args[0])
        payload, err := getPubKey(stub, index)
        if err != nil {
            return shim.Error(err.Error())
        }
        if payload == nil {
            return shim.Error(fmt.Sprintf("not found %s", index))
        }
        return shim.Success(payload)
    case "submit":
        err := submit(stub, string(args[0]), string(args[1]), args[2])
        if err != nil {
            return shim.Error(err.Error())
        }
        return shim.Success(nil)
    case "sum":
        payload, err := sum(stub, string(args[0]))
        if err != nil {
            return shim.Error(err.Error())
        }
        return shim.Success(payload)
    }
    return shim.Error(fmt.Sprintf("function %s is not found!", function))
}

func setPubkey(stub shim.ChaincodeStubInterface, index string, pub_key []byte) error {
    return stub.PutState(index, pub_key)
}

func getPubKey(stub shim.ChaincodeStubInterface, index string) ([]byte, error) {
    return stub.GetState(index)
}

func submit(stub shim.ChaincodeStubInterface, index, key string, text []byte) error {
    compositeKey, err := stub.CreateCompositeKey("indexs", []string{index, key})
    if err != nil {
        return err
    }
    return stub.PutState(compositeKey, text)
}

func sum(stub shim.ChaincodeStubInterface, index string) ([]byte, error) {
    paramDef := bfv.PN13QP218
    paramDef.T = 0x3ee0001
    params, err := bfv.NewParametersFromLiteral(paramDef)
    if err != nil {
        return nil, err
    }

    evaluator := bfv.NewEvaluator(params, rlwe.EvaluationKey{})

    iterator, err := stub.GetStateByPartialCompositeKey("indexs", []string{index})
    if err != nil {
        return nil, err
    }
    res := new(bfv.Ciphertext)
    if iterator.HasNext() {
        raw, err := iterator.Next()
        if err != nil {
            return nil, err
        }
        err = res.UnmarshalBinary(raw.Value)
        if err != nil {
            return nil, err
        }
    }
    for iterator.HasNext() {
        raw, err := iterator.Next()
        if err != nil {
            return nil, err
        }
        text := new(bfv.Ciphertext)
        err = text.UnmarshalBinary(raw.Value)
        if err != nil {
            return nil, err
        }
        evaluator.Add(res, text, res)
    }
    return res.MarshalBinary()
}

func main() {
    err := shim.Start(new(TurnoverCC))
    if err != nil {
        fmt.Printf("Error starting TurnoverCC chaincode: %s", err)
    }
}
package main

import (
    "log"
    "testing"

    "github.com/hyperledger/fabric-chaincode-go/shimtest"
    "github.com/tuneinsight/lattigo/v3/bfv"
    "github.com/tuneinsight/lattigo/v3/rlwe"
)

func TestTurnover(t *testing.T) {
    // 生成参数
    paramDef := bfv.PN13QP218
    paramDef.T = 0x3ee0001
    params, err := bfv.NewParametersFromLiteral(paramDef)
    if err != nil {
        log.Fatalln(err)
    }

    // 生成密钥对
    kgen := bfv.NewKeyGenerator(params)
    priv_key, pub_key := kgen.GenKeyPair()

    // 初始化链码
    cc := new(TurnoverCC)
    stub := shimtest.NewMockStub("turnover", cc)
    pubKeyArg, err := pub_key.MarshalBinary()
    if err != nil {
        log.Fatalln(err)
    }

    setEnve(stub, "test", pubKeyArg)

    submitData(stub, "test", "1", 2000)
    submitData(stub, "test", "2", 5000)
    submitData(stub, "test", "3", -3000)

    // 解密数据
    ciphertext := sumData(stub, "test")
    decryptor := bfv.NewDecryptor(params, priv_key)
    text := decryptor.DecryptNew(ciphertext)

    // 解码数据
    encoder := bfv.NewEncoder(params)
    res := encoder.DecodeIntNew(text)
    log.Println(res[0])
}

func setEnve(stub *shimtest.MockStub, index string, pubKeyArg []byte) {
    res := stub.MockInvoke("1", [][]byte{[]byte("setPubKey"), []byte(index), pubKeyArg})
    if res.Status == 500 {
        log.Println(res.Message)
    }
}

func sumData(stub *shimtest.MockStub, index string) *bfv.Ciphertext {
    res := stub.MockInvoke("1", [][]byte{[]byte("sum"), []byte(index)})
    ciphertext := new(bfv.Ciphertext)
    err := ciphertext.UnmarshalBinary(res.Payload)
    if err != nil {
        log.Fatalln(err)
    }
    return ciphertext
}

func submitData(stub *shimtest.MockStub, index, key string, data int64) {
    // 生成参数
    paramDef := bfv.PN13QP218
    paramDef.T = 0x3ee0001
    params, err := bfv.NewParametersFromLiteral(paramDef)
    if err != nil {
        log.Fatalln(err)
    }
    // 获取公钥
    res := stub.MockInvoke("1", [][]byte{[]byte("getPubKey"), []byte(index)})
    if res.Status == 500 {
        log.Println(res.Message)
    }
    var pubKey rlwe.PublicKey
    err = pubKey.UnmarshalBinary(res.Payload)
    if err != nil {
        log.Fatalln(err)
    }

    text := bfv.NewPlaintext(params)

    // 生成一个编码器,并将数据编码到文本中
    encoder := bfv.NewEncoder(params)
    encoder.Encode([]int64{data}, text)

    // 生成公钥加密器,并加密文本
    encryptor := bfv.NewEncryptor(params, &pubKey)
    ciphertext := encryptor.EncryptNew(text)
    raw, err := ciphertext.MarshalBinary()
    if err != nil {
        log.Fatalln(err)
    }
    res = stub.MockInvoke("1", [][]byte{[]byte("submit"), []byte(index), []byte(key), raw})
    if res.Status == 500 {
        log.Println(res.Message)
    }
}