Skip to content

Latest commit

 

History

History
743 lines (673 loc) · 27.6 KB

quorum隐私分析.md

File metadata and controls

743 lines (673 loc) · 27.6 KB

quorum 的隐私分析

一、隐私性设计

privacy(隐私性)是Quorum的重要的部分,Quorum的一个重要特点就是在以太坊的基础上增加了隐私性(参看白皮书)。将交易和数据进行了隐私性隔离,包括加密和零知识证明等。在将隐私性相关抽象出来以后,导致的一个结果就是,状态数据库的分裂。
在以太坊中,MTP主宰的状态树控制着整个以太坊的世界,任何的风吹草动,都必须纳入状态数据库的更新。但是在Quorum中,公有的数据仍然保持在全局状态的更新,但是私有的数据不被更新到全局状态中,而是被加密保存到节点上,同样通过分布式的事务等同步到所有的节点上。

privacy
通过上图可以看到,隐私分成了两部分即 ZSL和Constellation两部分。
ZSL:Zero-knowledge Security Layer,即零知识证明安全层。它主要使用ZCASH的零知识证明的zk-SNARKs来增强Constellation机制。
Constellation:P2P的加密消息交换系统,提供整个Quorum的信息安全交换的模块。

一个私有交易的简单流程:

privacy-d
合约交易状态:
下面的交易是允许的:

  1. S -> A -> B

2) S -> (A) -> (B)
3) S -> (A) -> [B -> C]

而下面的交易是不支持的:

  1. (S) -> A

2) (S) -> (A)

说明: S = sender

(X) = private
X = public
-> = direction
[] = read only mode


通过上述的分析可以看出,Quorum的隐私设计是基于加密的安全算法来实现的。不管是在ZSL还是在Constellation,它都提供了相关的Token来进行相应的支持。

二、Constellation


Constellation模块
在Quorum的架构文档中可以看到,Constellation是其重要的一个模块。你可以想象它是一个分布式的密钥服务器,等同于用PGP加密消息的MTA(消息传输代理)网络,做为一个独立的模块,它可以适用于包括区块链在内的许多类型的应用程序,它是实现Quorum的“隐私引擎”,目前使用的版本是使用Haskell实现。
Constellation模块由两个子模块组成:

arch

1、事务管理器(Transaction Manager):


事务管理器,允许访问私有事务的加密交易数据、管理器本地数据存储以及与其他事务管理器的通信。它做隐私数据的数据层,提供数据的安全访问并利用Enclave来实现数据的安全加密。

2、飞地(Enclave)


Enclave就是为了解决区块链中记帐的真实性和安全性而实现的一个模块。它通过事务隔离和特定的加密措施来提供并行操作,大大提高了性能。正如上面所讲,隐私的数据的安全处理基本都是通过Enclave来实现的。

三、ZSL


ZSL在Quorum中是单独提供的,它提供了两种支持的方法,一种是直接在其JS平台中调用相关的接口,另外一种是使用C++ libsnark library提供的基本库来供Golang使用。具体的使用办法和接口参看下面的地址:
https://github.com/jpmorganchase/zsl-q/blob/master/README.md
在Quorum中,1.5和1.6版本都提供了对ZSL的支持,但在最新的版本中,没有提供对ZSL的支持。猜测可能是JP想把ZSL专门做为一个特别的部分来提供相应的服务,而不是广泛的集成到整体的Quorum这个项目中去。
下图是一个相关的简单股权交易用例:

ZSL


ZSL的使用还是比较复杂的,需要下载相关的库和相关的模块。如果想完整运行一个ZSL的Quorum项目,请参照下面的地址:
https://github.com/jpmorganchase/zsl-q/blob/master/README.md

四、源码分析

1、隐私交易流程介绍


Quorum的交易有两类即”Public Transaction” 和 “Privat Transaction”,也就是公共交易类型和隐私交易类型。在实际的交易中,前者完全兼容以太坊的交易。而在后者的隐私交易中,在原有以太坊的 Transaction 模型上,进行了部分修改。在Quorum 交易tx 模型基础上增加了 “privateFor” 字段。另外在还其内部增加了一个判断是否公共或者隐私交易类型的方法 “IsPrivate”。
先看一个官方WIKI上的隐私交易的流程图,这个图中,一笔交易与用户A和用户B有关系,但是和用户C没有关系:

private-t
说明:
1)、用户A将隐私交易发送到Quorum节点,节点指定交易的有效载荷(内容),并为A和B指定PrivateFor参数为A和B的公钥。
2)、节点将Tx发送到对应的Transaction Manager并存储相关的交易数据。
3)、Transaction Manager调用加密的Enclave模块相关函数请求对Tx加密。
4)、节点A的Enclave检验A的私钥,如果验证通过,就进行下列动作:
第一步,生成一个对称密钥和随机值(说明一下这里使用对称密钥的作用是为了加快速度)。
第二步,使上一步生成的密钥加密Tx及相关内容。
第三步,由SHA3-512来计算加密后的Tx的内容的HASH值。
第四步,遍历交易方列表(此处为A和B)使用第一步产生的密钥对用第一步中的public key加密,然后生成一个新的值(加密方法PGP)。
第五步,将上述二三四步的结果返回给Transaction Manager.
5)、A的Transaction Manager使用从 Enclave 中获取的 hash 值作为索引把加密后的TX以及加密后的密钥保存到本地。同时,Transaction Manager会把hash值,加密后的TX,public_key_B加密的密钥这三项通过HTTPS发送给PartyB的Transaction Manager。PartyB的Tx Manager收到数据后,会进行ACK/NACK的应答。需要注意的同如果A没有收到应答,那么交易不会在网上传播,也就是说,接收人存储通信的有效载荷是网络传播的前提条件。
6)、一旦发往B的Transaction Manager的交易成功,A的事务管理器便将hash值返回给其对应的Quorum节点。该节点用hash值来替换原来TX的交易内容(有效载荷)。修改TX的 V 值为 37 或者 38(Private Transaction的标识)。其他节点可以通过这个 V 的值来判断其是否为已加密交易内容的相关私有交易;否则,即为一个无意义的字节码相关的公开交易。
7)、使用标准的以太坊的通信协议将节点通过P2P方式广播给整个网络。
8)、此交易被打包到区块中,并分发到各个网络用户。
9)、节点收到这个TX的区块后,发现这个TX的 V 值为37或38。表明这个Tx是隐私的交易,需要解密,需要调用本地的事务管理器,最终决定是否同意这笔交易(使用hash索引查找)。
10)、因为用户C的节点不能控制这笔TX,所以它只会接收到一个NotARecipient的消息,从而忽略这笔交易——c用户不会更新自己的私有状态数据库。A和B将会在自己的事务管理器中查找哈希值,识别他们的确同意该交易,然后它们调用对应的Enclave模块,传递已加密交易内容和加密的系统密钥和签名。
11)、Enclave模块验证签名,然后使用在Enclave中保存的该用户的私钥解密对称密钥,使用解密的密钥对交易内容进行解密,然后将解密的交易内容返回给事务管理器。
12)、用户A和B的事务管理器,将解密的TX后通过EVM执行。执行完成后将执行结果返回给Quorum节点,并更新Quorum节点的私有状态。注意:一旦代码被执行将会无效,因此在不经过上述过程的情况下,它无法被读取。

2、隐私交易流程的源码


隐私交易的部分在Quorum中只有Transaction Manager部分,Enclave部分是在另外的项目constellation中,用haskell语言编写完成。下面分别介绍这两个部分。
在了解了私有交易的流程后,看一下相关的代码:
1)、隐私交易
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
......
	isPrivate := args.PrivateFor != nil

	if isPrivate {
    //判断私有交易
		//Send private transaction to local Constellation node
		log.Info("sending private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
    //这里会调用下面的PrivateTransactionManager的Send,二者就联系到一起了
		data, err = private.P.Send(data, args.PrivateFrom, args.PrivateFor)
		log.Info("sent private tx", "data", fmt.Sprintf("%x", data), "privatefrom", args.PrivateFrom, "privatefor", args.PrivateFor)
		if err != nil {
			return common.Hash{}, err
		}
		args.Data = data
	}

......
  //处理Quorum交易相关值,见上面流程说明
	isQuorum := tx.IsPrivate()
......
//签名和加密
	signed, err := wallet.SignTx(account, tx, chainID, isQuorum)
......
	return submitTransaction(ctx, s.b, signed, isPrivate)
}

//交易相关
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
type SendTxArgs struct {
	From     common.Address  `json:"from"`
	To       *common.Address `json:"to"`
	Gas      *hexutil.Big    `json:"gas"`
	GasPrice *hexutil.Big    `json:"gasPrice"`
	Value    *hexutil.Big    `json:"value"`
	Data     hexutil.Bytes   `json:"data"`
	Nonce    *hexutil.Uint64 `json:"nonce"`

	PrivateFrom string   `json:"privateFrom"`
  //PrivateFor这个关键字段
	PrivateFor  []string `json:"privateFor"`
}
//增加的是否为私有交易判断
func (m Message) IsPrivate() bool {
	return m.isPrivate
}

func (tx *Transaction) IsPrivate() bool {
	if tx.data.V == nil {
		return false
	}
	return tx.data.V.Uint64() == 37 || tx.data.V.Uint64() == 38
}

func (tx *Transaction) SetPrivate() {
  //设置V的值,见上面的流程
	if tx.data.V.Int64() == 28 {
		tx.data.V.SetUint64(38)
	} else {
		tx.data.V.SetUint64(37)
	}
}

2)、Transaction Manager模块
这个模块包含private.go和node.go以及constellation.go几个模块。在private.go中实现了:

type PrivateTransactionManager interface {
	Send(data []byte, from string, to []string) ([]byte, error)
	Receive(data []byte) ([]byte, error)
}

而在Constellation这个结构体中实现了这个接口:
func (g *Constellation) Send(data []byte, from string, to []string) (out []byte, err error) {
	if g.isConstellationNotInUse {
		return nil, ErrConstellationIsntInit
	}
	out, err = g.node.SendPayload(data, from, to)
	if err != nil {
		return nil, err
	}
	g.c.Set(string(out), data, cache.DefaultExpiration)
	return out, nil
}

func (g *Constellation) Receive(data []byte) ([]byte, error) {
	if g.isConstellationNotInUse {
		return nil, nil
	}
	if len(data) == 0 {
		return data, nil
	}
	// Ignore this error since not being a recipient of
	// a payload isn't an error.
	// TODO: Return an error if it's anything OTHER than
	// 'you are not a recipient.'
	dataStr := string(data)
	x, found := g.c.Get(dataStr)
	if found {
		return x.([]byte), nil
	}
	pl, _ := g.node.ReceivePayload(data)
	g.c.Set(dataStr, pl, cache.DefaultExpiration)
	return pl, nil
}

Constellation通过RPC调用Haskell的接口:
type Client struct {
	httpClient *http.Client
}
func (c *Client) SendPayload(pl []byte, b64From string, b64To []string) ([]byte, error) {
	buf := bytes.NewBuffer(pl)
	req, err := http.NewRequest("POST", "http+unix://c/sendraw", buf)
	if err != nil {
		return nil, err
	}
	if b64From != "" {
		req.Header.Set("c11n-from", b64From)
	}
	req.Header.Set("c11n-to", strings.Join(b64To, ","))
	req.Header.Set("Content-Type", "application/octet-stream")
	res, err := c.httpClient.Do(req)

	if res != nil {
		defer res.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	if res.StatusCode != 200 {
		return nil, fmt.Errorf("Non-200 status code: %+v", res)
	}

	return ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body))
}

通过上述的一系列调用,就产生一个RPC的请求,传递到Enclave.
3)、Enclave模块
在Haskell的项目中主要有以下几个模块:主模块、Enclave模块、Node模块和相关的工具模块,这里重点介绍Enclave模块。
--加密交易内容数据结构
data EncryptedPayload = EncryptedPayload
    { eplSender    :: Box.PublicKey
    , eplCt        :: ByteString
    , eplNonce     :: SBox.Nonce
    , eplRcptBoxes :: [ByteString]
    , eplRcptNonce :: Box.Nonce
    } deriving Eq
--实例化为Show类型
instance Show EncryptedPayload where
    show = show . encodeable

--Hahkell语法,类似数据学函数语法,=号后即为函数体
--交易内容的加密,包括下面的重载
encodeable :: EncryptedPayload
           -> (ByteString, ByteString, ByteString, [ByteString], ByteString)
encodeable EncryptedPayload{..} =
    ( S.encode eplSender
    , eplCt
    , S.encode eplNonce
    , eplRcptBoxes
    , S.encode eplRcptNonce
    )
instance Binary EncryptedPayload where
    put = put . encodeable
    --后面以\表示lambda表达式
    get = get >>= \(sender, ct, nonce, rcptBoxes, rcptNonce) -> return EncryptedPayload
        { eplSender    = fromJust $ S.decode sender
        , eplCt        = ct
        , eplNonce     = fromJust $ S.decode nonce
        , eplRcptBoxes = rcptBoxes
        , eplRcptNonce = fromJust $ S.decode rcptNonce
        }
encrypt :: ByteString
          -> Box.PublicKey
          -> Box.SecretKey
          -> [Box.PublicKey]
          -> IO EncryptedPayload    --IO表示不纯函数,即非重入函数
encrypt pl sender pk rcpts = encrypt' pl sender cks
      where
      cks = map (safeBeforeNM sender pk) rcpts

--重载函数
encrypt' :: ByteString
         -> Box.PublicKey
         -> [Box.CombinedKey]
         -> IO EncryptedPayload
encrypt' pl eplSender cks = do
    (mk, eplNonce, eplCt) <- sboxSeal pl
    eplRcptNonce          <- Box.newNonce
    let eplRcptBoxes = map (\ck -> Box.boxAfterNM ck eplRcptNonce emk) cks
        emk          = S.encode mk
    return EncryptedPayload{..}

相关的RPC:
--主线程启动监听网络
defaultMain :: IO ()
defaultMain = do
    args     <- getArgs
    --通过配置文件来配置网线参数
    (cfg, _) <- extractConfig args
    if cfgJustShowVersion cfg
        then putStrLn ("Constellation Node " ++ version)
        else do
            _ <- case cfgWorkDir cfg of
                Nothing -> return ()
                Just wd -> do
                    createDirectoryIfMissing True wd
                    setCurrentDirectory wd
            case cfgJustGenerateKeys cfg of
                 [] -> withStderrLogging $ run cfg
                 ks -> mapM_ generateKeyPair ks
--启动并开户线程处理
run :: Config -> IO ()
run cfg@Config{..} = do
    setupLogging cfgVerbosity
    debugf' "Configuration: {}" [pShowNoColor cfg]
    sanityCheckConfig cfg
    setupParallelism
    logf' "Constructing Enclave using keypairs {}"
        [show $ zip cfgPublicKeys cfgPrivateKeys]
    (crypt, pubs) <- setupCrypt cfgPublicKeys cfgPrivateKeys cfgPasswords
    ast           <- mustLoadPublicKeys cfgAlwaysSendTo
    (selfPub, _)  <- newKeyPair
    logf' "Throwaway public key for self-sending: {}" [show selfPub]
    storage                  <- setupStorage cfgStorage
    (warpFunc, m, setSecure) <- setupTls cfg
    nvar <- newTVarIO =<<
        newNode crypt storage cfgUrl pubs ast selfPub cfgOtherNodes m
        setSecure
        --启动线程处理监听
    _    <- forkIO $ do
        let mwl = if null cfgIpWhitelist
                then Nothing
                else Just $ whitelist cfgIpWhitelist
        logf' "Public API listening on 0.0.0.0 port {} with whitelist: {}"
            ( cfgPort
            , Shown $ if isNothing mwl then ["Disabled"] else cfgIpWhitelist
            )
        warpFunc (Warp.setPort cfgPort Warp.defaultSettings) $
            apiApp mwl Public nvar
    _       <- case cfgSocket of
        Just sockPath -> void $ forkIO $ runPrivateApi sockPath nvar
        Nothing       -> return ()
    registerAtExit $ do
        log "Shutting down... (Interrupting this will cause the next startup to take longer)"
        case cfgSocket of
            Just sockPath -> resetSocket sockPath
            Nothing       -> return ()
        readTVarIO nvar >>= closeStorage . nodeStorage
    log "Node started"
    withAtExit $ runNode nvar

API接口消息处理:
--request的接口处理
request :: ApiType -> TVar Node -> Wai.Application
request apiType nvar req resp = do
    b <- Wai.lazyRequestBody req
    let h    = Wai.requestHeaders req
        path = Wai.pathInfo req
    case parseRequest path b h of
        Left err     -> do
            warnf "Failed to decode '{}' ({}) request: {}"
                ( TE.decodeUtf8 $ Wai.rawPathInfo req
                , TE.decodeUtf8 $ Wai.requestMethod req
                , err
                )
            resp badRequest
        Right apiReq -> if authorizedRequest apiType apiReq
            then do
                eapiRes <- performRequest nvar apiReq
                case eapiRes of
                    Left err     -> do
                        warnf "Error performing API request: {}; {}" (Shown apiReq, err)
                        resp internalServerError
                    Right apiRes -> do
                        debugf "Request from {}: {}; Response: {}"
                            ( Shown $ Wai.remoteHost req
                            , Shown apiReq
                            , Shown apiRes
                            )
                        resp $ ok $ response apiRes
            else do
                warnf "Blocked unauthorized request from {}: {}"
                    (Shown $ Wai.remoteHost req, Shown apiReq)
                resp unauthorized

parseRequest :: [Text] -> BL.ByteString -> RequestHeaders -> Either String ApiRequest
-----
-- Node client
-----
parseRequest ["send"]       b _ = ApiSend       <$> AE.eitherDecode' b
parseRequest ["receive"]    b _ = ApiReceive    <$> AE.eitherDecode' b
parseRequest ["sendraw"]    b h = ApiSendRaw    <$> decodeSendRaw b h
parseRequest ["receiveraw"] _ h = ApiReceiveRaw <$> decodeReceiveRaw h
parseRequest ["delete"]     b _ = ApiDelete     <$> AE.eitherDecode' b

逻辑节点的消息处理:
refreshLoop :: StdGen -> TVar Node -> IO ()
refreshLoop g nvar = do
    nodeRefresh nvar
    -- Wait approximately five minutes
    let (jitteredDelay, ng) = randomR (280, 320) g
    threadDelay (seconds jitteredDelay) >> refreshLoop ng nvar

nodeRefresh :: TVar Node -> IO ()
nodeRefresh nvar = do
    node <- readTVarIO nvar
    let PartyInfo{..} = nodePi node
    epis <- mapM
        (getRemotePartyInfo nvar)
        (HS.toList $ HS.delete piUrl piParties)
    let (ls, rs) = partitionEithers epis
    --符号‘$’的优先级最低的意义就是:它和附近的元素发生结合是最晚的。等价于其后跟()
    forM_ ls $ \err -> warnf "Synchronization failed: {}" [err]
    atomically $ mergePartyInfos nvar rs
--读取IO消息
getRemotePartyInfo :: TVar Node -> Text -> IO (Either String PartyInfo)
getRemotePartyInfo nvar url = trys $ do
    logf "Starting synchronization with {}" [url]
    Node{..} <- atomically $ readTVar nvar
    res      <- simplePostLbs nodeManager nodeSetSecure
        (T.unpack url ++ "partyinfo") (encode nodePi)
    logf "Finished synchronization with {}" [url]
    evaluate $ decode (responseBody res)

haskell的代码纯数学的,理解起来有点头大啊,又回到从前的感觉。

2、ZSL的分析


在Quorum中,ZSL是定位于增强Constellation模块的。下面简要的分析一下ZSL的代码:
1)、在Quorum中增加的ZSL部分
在1.6版本中,core,internal,params,eth中都增加ZSL的相关代码或者模块,在vendor的引用库中,也增加了相关的zsl的库的引用。
其中主要的代码在core/zsl/api.go中,它主要引用了用GO封装的ZSL相关的接口,在代码中有一个注释说明:
Example to generate a proof in geth:

sk = "0xf0f0f0f00f0f0ffffffffff000000f0f0f0f0f00f0000f0f00f00f0f0f0f00ff"
pk = "0xe8e55f617b4b693083f883f70926dd5673fa434cefa3660828759947e2276348"
rho = "0xdedeffdddedeffdddedeffdddedeffdddedeffdddedeffdddedeffdddedeffdd"
value = 2378237
zsl.createShielding(rho, pk, value);

Results in:
cm = "0x58e38183982c6f7981e9f3ce0a735fdd4ca2f0cd88db6ee608c2fe1e84142d0d"
send_nf = "0xc813e257232fae0fee5244aadf98d7ab7a676724c128cd3c0d52d3a01739a3da"
ztoken.addCommitment(cm, {from:eth.accounts[0], gas:470000})
rt = ztoken.root()
treeIndex = ztoken.getWitness(cm)[0]
authPath = ztoken.getWitness(cm)[1]
zsl.createUnshielding(rho, sk, value, treeIndex, authPath)
Verify with:
zsl.verifyUnshielding(proof, spend_nf, rt, value)
//生成接口实例
func NewPublicZSLAPI() *PublicZSLAPI {
	return &PublicZSLAPI{}
}
func (api *PublicZSLAPI) CreateShielding(rho common.Hash, pk common.Hash, value float64) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	snark.Init()
	proof := snark.ProveShielding(rho, pk, uint64(value))
	send_nf := computeSendNullifier(rho[:])
	cm := computeCommitment(rho, pk, uint64(value))
	result["proof"] = "0x" + hex.EncodeToString(proof[:])
	result["cm"] = common.BytesToHash(cm)
	result["send_nf"] = common.BytesToHash(send_nf)
	return result, nil
}
func (api *PublicZSLAPI) CreateUnshielding(rho common.Hash, sk common.Hash, value float64, treeIndex float64, authPath []string) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	// copy authentication path array into two dimensional array (as required by snark.ProveUnshielding())
	if len(authPath) != ZSL_TREE_DEPTH {
		return result, errors.New(fmt.Sprintf("Authentiction path must be %d in length", ZSL_TREE_DEPTH))
	}
	var authenticationPath [ZSL_TREE_DEPTH][32]byte
	for i := 0; i < ZSL_TREE_DEPTH; i++ {
		b, err := hex.DecodeString(strings.TrimPrefix(authPath[i], "0x"))
		if err != nil {
			return result, err
		}
		var uncle [32]byte
		copy(uncle[:], b[:32])
		authenticationPath[i] = uncle
	}
  //初始化snark库,这个是零知识证明的必须
	snark.Init()
	proof := snark.ProveUnshielding(rho, sk, uint64(value), uint64(treeIndex), authenticationPath)
	send_nf := computeSendNullifier(rho[:])
	spend_nf := computeSpendNullifier(rho[:], sk)
	result["proof"] = "0x" + hex.EncodeToString(proof[:])
	result["send_nf"] = common.BytesToHash(send_nf)
	result["spend_nf"] = common.BytesToHash(spend_nf)
	return result, nil
}
func (api *PublicZSLAPI) VerifyShielding(proofHex string, send_nf common.Hash, cm common.Hash, value float64) (bool, error) {

	proof, err := getProofFromHex(proofHex)
	if err != nil {
		return false, err
	}

	snark.Init()
	result := snark.VerifyShielding(proof, send_nf, cm, uint64(value))
	return result, nil
}

注意,这里只简要说明一下零知识证明,不详细阐述零知识证明的相关算法,如果对其有兴趣,可在网上查找相关的资料。
零知识证明过程即参与的一方向另一方证明自己掌握某个秘密(或者说证明者向验证者证明自己掌握某个秘密)。在这个过程中,证明的一方并不想让另外一方知道这个秘密是什么。所以二者要按照一个约定,进行一系列的交互,然后最终由另外一方验证一方确实知道某个秘密。
在计算机领域,一般做法是把原始问题映射到NP问题。验证者只要验证证明者给出的NP问题的解即可,这个计算量需求不大。Quorum是和zcash合作,在Zash中使用的NP问题是QAP(把向量表达式表示为多项式,从而把向量的验证转化为多项式的验证,这个过程称为QAP(Quadratic Arithmetic Programs))
零知识证明的缺点,就在交互上,交互就意味着时间的延长,所以目前使用零知识证明的相关的程序,耗时都是一个瓶颈。
其它的几个模块中,也增加了相应的接口说明和相关的模块,但基本都是给这个API接口做服务的。
2)零知识证明的snark相关
在Quorum中,使用Golang封装了相关的库。形成了一个单独的项目zsl-q(还有一个与其相关的zsl-q-params项目)。
在封装的snark.go中:
func Init() {
	onceInit.Do(func() {
		C.zsl_initialize()
	})
}

func VerifyTransfer(...) bool {
	ret := C.zsl_verify_transfer(unsafe.Pointer(&proof[0]),
		unsafe.Pointer(&anchor[0]),
		unsafe.Pointer(&spend_nf_1[0]),
		unsafe.Pointer(&spend_nf_2[0]),
		unsafe.Pointer(&send_nf_1[0]),
		unsafe.Pointer(&send_nf_2[0]),
		unsafe.Pointer(&cm_1[0]),
		unsafe.Pointer(&cm_2[0]))

......
}

func ProveShielding(rho [32]byte, pk [32]byte, value uint64) [584]byte {
	var proof_buf [584]byte
......
	C.zsl_prove_shielding(rho_ptr, pk_ptr, C.uint64_t(value), unsafe.Pointer(&proof_buf[0]))

.......
}

func VerifyShielding(proof [584]byte, send_nf [32]byte, cm [32]byte, value uint64) bool {
	send_nf_ptr := C.CBytes(send_nf[:])
	cm_ptr := C.CBytes(cm[:])
	ret := C.zsl_verify_shielding(unsafe.Pointer(&proof[0]), send_nf_ptr, cm_ptr, C.uint64_t(value))

.......
}

func VerifyUnshielding(proof [584]byte, spend_nf [32]byte, rt [32]byte, value uint64) bool {
	ret := C.zsl_verify_unshielding(unsafe.Pointer(&proof[0]),
		unsafe.Pointer(&spend_nf[0]),
		unsafe.Pointer(&rt[0]),
		C.uint64_t(value))
......
}

这些代码和在Quorum中的Golang代码互相响应,但是真正的算法调用在下面:
void zsl_initialize()
{
    default_r1cs_ppzksnark_pp::init_public_params();
    inhibit_profiling_info = true;
    inhibit_profiling_counters = true;
}

bool zsl_verify_unshielding(
    void *proof_ptr,
    void *spend_nf_ptr,
    void *rt_ptr,
    uint64_t value
)
{
......

    auto witness_map = UnshieldingCircuit<FieldT>::witness_map(
        std::vector<unsigned char>(spend_nf, spend_nf+32),
        std::vector<unsigned char>(rt, rt+32),
        value
    );

    r1cs_ppzksnark_verification_key<default_r1cs_ppzksnark_pp> verification_key;
    loadFromFile("unshielding.vk", verification_key);

    if (!r1cs_ppzksnark_verifier_strong_IC<default_r1cs_ppzksnark_pp>(verification_key, witness_map, proof_obj)) {
        return false;
    } else {
        return true;
    }
}

void zsl_prove_unshielding(
    void *rho_ptr,
    void *pk_ptr,
    uint64_t value,
    uint64_t tree_position,
    void *authentication_path_ptr,
    void *output_proof_ptr
)
{
......

    g.generate_r1cs_witness(
        std::vector<unsigned char>(rho, rho + 32),
        std::vector<unsigned char>(pk, pk + 32),
        value,
        tree_position,
        auth_path
    );
    pb.constraint_system.swap_AB_if_beneficial();
    assert(pb.is_satisfied());

    r1cs_ppzksnark_proving_key<default_r1cs_ppzksnark_pp> proving_key;
    loadFromFile("unshielding.pk", proving_key);

    auto proof = r1cs_ppzksnark_prover<default_r1cs_ppzksnark_pp>(proving_key, pb.primary_input(), pb.auxiliary_input(), pb.constraint_system);

......
}

在向下的代码,就是零知识证明的c++实现和库了,这里就不再分析,有兴趣的可以将源码下来,学习一下。

五、总结


通过上述的分析,可以看到,Quorum把privacy独立出来,确实想法比较不错,既可以依赖以太坊的公网技术实现区块链的功能,又可以在其基础上开发相应的专门的联盟链的分支,达到自己的目的。
不管是从设计上还是技术的实现上,Quorum力求简单明了,在整个源码的阅读过程中,看不到什么特别的设计技巧,显得非常的接地气。