高级签名协议 -- 群签名

高级签名协议 -- 群签名

群签名

群签名背景

在一个群签名体制中,群体(group)中的成员(member)可代表整个群体进行匿名签名。一方面,验证者只能确定签名是由群体中的某个成员产生的,但不能确定是哪个成员,即群签名的匿名性;另一方面,在必要的时候(比如发生争执的情况下)群管理员(group manager)可以打开(open)签名来揭示签名人的身份,使得签名人不能否认自己的签名行为,即群签名的可追踪性。将两者结合在一起,就可以说,群签名是一种同时提供匿名性和可追踪性的技术,匿名性为合法用户提供匿名保护,可追踪性又使得可信机构可以追踪违法行为。群签名还有无关联性,即在不打开群签名的条件下,任何人不能确定两个群签名是否为同一个成员产生的。可撤销匿名性和无关联性使得群签名在管理、军事、政治及经济等多个方面有着广泛的应用前景。

群签名的基本概念

在一个群签名方案中,一般包含一个群管理员和若干群成员,这些成员构成的集合称为群。群管理员负责产生系统参数、群公钥、群私钥,同时要为每个群成员产生签名私钥或是群成员的身份证书。全群成员用自己掌握的签名私钥代表整个群执行签名操作。有的群签名体制中,存在两个群管理者:一个负责为群成员颁发群成员私钥或群成员证书,另一个执行追踪功能。通常,一个群签名体制由下列算法组成:

  • 系统初始化算法:产生群公钥、群成员的公钥和私钥,以及群管理员用于打开签名的打开私钥。
  • 成员加入:一个新用户通过与群管理员交互,得到一个群成员公钥和私钥(或群成员的身份证书)
  • 签名:群成员可利用自己的群成员私钥和群成员证书对消息\(m\)进行签名。
  • 验证:验证消息\(m\)是否是一个合法的群签名。
  • 打开:群管理员利用打开私钥可以追踪一个签名对的签名者身份。

群签名的安全性需求

  • 正确性:老生常谈的一个性质,一个合法的群成员正确地执行签名协议得到的群签名验证一定通过。
  • 不可伪造性:非群成员产生一个合法的群签名是计算上不可行的。
  • 匿名性:除群管理员外,任何人无法确定给定群签名的签名者的真实身份。
  • 无关联性:在不打开签名的情况下,确定两个不同的签名对是否是来自于同一个群成员是不可行的。
  • 可追踪性:一个正确的群签名可由群管理员追踪产生该签名的群成员身份。
  • 不可诬陷性(\(non-frameability\)):包括群管理员在内的任何成员都不能以其他群成员的名义产生合法的群签名。

基于一次性密钥表的朴素群签名

假设有\(n\)个人构成一个群,\(GM\)是该群的群管理员。

  1. 系统初始化算法
    在这个算法里,群管理员\(GM\)为群中的每个成员分发一张秘密的密钥表,这些表互不相交(没有一个密钥同时出现在两张表中)。\(GM\)将各个群成员拥有的私钥汇总在一起,将这些私钥对应的公钥以一种随机的次序排成一张公钥表,并将这张公钥表公开。
  2. 签名
    每个群成员每次从自己的私钥表中选取一个没有使用过的私钥(保障无关联性),利用这个密钥对消息产生签名。
  3. 验证:
    如果接收者要验证某个群签名,他就遍历公钥表,当其发现所遍历到的公钥能够验证通过时,就说明这个签名是合法的群签名。
  4. 打开
    在发生争执时,由于群管理员知道所有群成员的私钥和公钥之间的对应关系,从而可以根据签名所对应的公钥确定签名人的身份。

在上述的这个方案中,他是没有满足前述安全性需求中的防陷害性和抗联合攻击的,简单理解的话就是上述群签名体制中群管理员所知道的信息太多了,其知晓群成员的签名私钥。

基于离散对数和 ElGamal 思想的群签名

\(p\)是一个大素数,在\(\mathbb{Z}_p\)上计算离散对数是困难的,\(g\)\(\mathbb{Z}_p\)的一个生成元。假设有\(n\)个人构成了一个群,他们的密钥分别是\(s_1, s_2, \cdots, s_n\),对应的公钥是\(y_i\equiv g^{s_i}\pmod{p},1≤i≤n\)

  1. 系统初始化算法
    群管理者\(GM\)有一张群成员的身份与他们的公钥相对应的表。\(GM\)为群成员\(P_i\)选取随机数\(r_i\in _R\mathbb{Z}_p^*(1≤i≤n)\),发送\(r_i\)给对应的群成员,令将\(y_i^{r_i}(1≤i≤n)\)以一种随机的次序排成一张“公钥表”并且公开。
  2. 签名
    每个群成员将\(r_is_i\pmod{p-1}\)作为私钥,利用\(ElGamal\)型数字签名算法(其中包含随机数,故可以满足无关联性)对消息产生群签名。
  3. 签名验证
    验证者遍历“公钥表”,当发现其中一个公钥验证通过时,则认为该群签名是合法有效的。
  4. 打开
    在发生争执时,由于管理员知晓\(r_i\in \mathbb{Z}_p^*(1≤i≤n)\),也知道所有群成员的公钥\(y_i\)和身份之间的对应关系,从而可以根据签名,找到其所对应的公钥,从而确定签发该签名的群成员身份。

在该签名方案中,每个群成员的签名私钥只有一个,即\(r_is_i\pmod{p-1}\),且群管理员并不知晓每个群成员的准确签名私钥(满足防陷害性以及抗联合攻击)。此外,群管理员可以定期更新颁发给每个群成员的\(r_i\in \mathbb{Z}_p^*(1≤i≤n)\),以使群成员具有更大的灵活性。但是这个群签名方案具有一个明显的缺点,那就是如果有新的群成员要加入,所有的群成员都不得不改变自己的密钥,否则接收者能够将旧的群成员和新的群成员区别开

经典短群签名 -- BBS04 方案的核心机制

前面两个群签名方案,群公钥的长度是群成员个数\(n\)的线性函数;与此同时,上述两种方案中的群签名长度依赖其所使用的签名算法。比如,“另一个简单的群签名体制”中签名者使用的是\(\mathbb{Z}_p(log_2p=1024)\)上的\(ElGamal\)数字签名,则最终的签名长度为\(2048bit\),这样的签名长度对于计算机而言是比较昂贵的了。\(Boneh\)等人在\(2004\)年美密会上提出了基于双线性对短的群签名体制。该方案假设系统中存在两个群管理员:其中一个称为\(issuer\),负责为群成员颁发用于执行群签名操作所需要的私钥;另一个群管理员被称为\(opener\),负责在发生争执时打开群签名以确定群成员身份。为了叙述方便,以下使用对称配对/加法记号:

  1. 系统初始化算法
    \(p\)为一个大素数,点\(P\)\(p\)阶加法循环群\(G_1\)的生成元,\(H\)\(G_1\)任意非单位元的点,\(G_2\)为同阶的乘法循环群,\(e:G_1 × G_1→G_2\)为双线性映射,\(Hash:\{0,1\}^*→\mathbb{Z}_p\)为哈希函数,\(\xi_1,\xi_2,\gamma\in _R\mathbb{Z}_p^*\),令\(U,V\in G_1\)使得\(\xi_1 U = \xi_2 V = H\),置\(W=\gamma P\)。将群公钥\(gpk=(P,H,U,V,W)\)公开,而\(gmsk=(\xi_1,\xi_2)\)由群管理员\(opener\)作为群私钥存储,用于追踪给定群签名的签名人身份,\(\gamma\)由群管理员\(issuer\)持有。

  2. 加入
    假设系统中由\(n\)个群成员,群管理员\(issuer\)为第\(i\)个群成员选取\(x_i\in _R\mathbb{Z}_p^*(1≤i≤n)\),计算\(A_i={1\over \gamma+x_i}P\),第\(i\)个群成员的群私钥就是\((A_i, x_i)\)。此外,为了便于\(opener\)执行打开操作,每个群成员将自己的\(A_i\)也交给\(opener\),这样\(opener\)知道群成员的\(A_i\)与群成员身份之间的对应关系。

  3. 签名
    给定群公钥\(gpk=(P,H,U,V,W)\),某群成员的私钥\((A_i,x_i)\),以及待签名的消息\(m\in\{0,1\}^*\),持有该私钥的群成员执行下列操作:
    ① 选择\(\alpha, \beta \in _R\mathbb{Z}_p^*\);
    ② 选择\(\delta_1=x_i\alpha,\delta_2=x_i\beta;T_1=\alpha U,T_2=\beta V,T_3=A_i+(\alpha + \beta)H\);
    ③ 选择\(r_{\alpha},r_{\beta},r_x,r_{\delta_1},r_{\delta_2}\in _R\mathbb{Z}_p^*\);
    ④ 计算\(R_1=r_{\alpha} U,R_2=r_{\beta} V,R_3=e(T_3,P)^{r_x}e(H,W)^{-r_{\alpha}-r_{\beta}}e(H,P)^{-r_{\delta_1}-r_{\delta_2}},R_4=r_x T_1-r_{\delta_1} U,R_5=r_x T_2-r_{\delta_2} V\);
    ⑤ 计算\(c=Hash(m,T_1,T_2,T_3,R_1,R_2,R_3,R_4,R_5)\);
    ⑥ 计算\(s_{\alpha}=r_{\alpha}+c\alpha,s_{\beta}=r_{\beta}+c\beta,s_x=r_x+cx_i,s_{\delta_1}=r_{\delta_1}+c\delta_1,s_{\delta_2}=r_{\delta_2}+c\delta_2\);
    ⑦ 输出\((T_1,T_2,T_3,c,s_{\alpha},s_{\beta},s_x,s_{\delta_1},s_{\delta_2})\).

  4. 验证
    给定群公钥\(gpk=(P,H,U,V,W)\)、消息\(m\)和签名\((T_1,T_2,T_3,c,s_{\alpha},s_{\beta},s_x,s_{\delta_1},s_{\delta_2})\),任意接收者通过执行以下操作来核验该签名是否为合法签名。
    ① 计算

    \[\begin{flalign} &R_1'=s_{\alpha}U - cT_1,R_2'=s_{\beta}V-cT_2\\ &R_3'=e(T_3,P)^{s_x}e(H,W)^{-s_{\alpha}-s_{\beta}}e(H,P)^{-s_{\delta_1}-s_{\delta_2}}\cdot ({e(T_3,W)\over e(P,P)})^c\\ &R_4'=s_xT_1-s_{\delta_1}U,R_5'=s_xT_2-s_{\delta_2}V& \end{flalign} \]

    ② 接收该签名为有效的群签名,当且仅当\(c=Hash(m,T_1,T_2,T_3,R_1',R_2',R_3',R_4',R_5')\).
    方案的正确性验证如下:
    \(R_1=r_{\alpha}U;R_1'=s_{\alpha}U-cT_1=(r_{\alpha}+c\alpha) U - c (\alpha U)=r_{\alpha} U=R_1\)
    \(R_2=r_{\beta}V;R_2'=s_{\beta} V-cT_2=(r_{\beta}+c\beta)V-c(\beta V)=r_{\beta}V=R_2\)

    \[\begin{flalign} &R_3=e(T_3,P)^{r_x}e(H,W)^{-r_{\alpha}-r_{\beta}}e(H,P)^{-r_{\delta_1}-r_{\delta_2}}\\ &R_3'=e(T_3,P)^{s_x}e(H,W)^{-s_{\alpha}-s_{\beta}}e(H,P)^{-s_{\delta_1}-s_{\delta_2}}\cdot ({e(T_3,W)\over e(P,P)})^c\\ &\quad\ =e(T_3,P)^{r_x+cx_i}e(H,W)^{-r_{\alpha}-r_{\beta}-c(\alpha+\beta)}e(H,P)^{-r_{\delta_1}-r_{\delta_2}-c(\delta_1+\delta_2)}\cdot ({e(T_3,W)\over e(P,P)})^c\\ &\quad\ =e(T_3,P)^{r_x}e(H,W)^{-r_{\alpha}-r_{\beta}}e(H,P)^{-r_{\delta_1}-r_{\delta_2}}[e(T_3,P)^{cx_i}e(H,W)^{-c(\alpha+\beta)}e(H,P)^{-c(\delta_1+\delta_2)}\cdot ({e(T_3,W)\over e(P,P)})^c]\\ &\quad\ =R_3\cdot [e(T_3,P)^{x_i}e(H,W)^{-(\alpha+\beta)}e(H,P)^{-(\delta_1+\delta_2)}\cdot {e(T_3,W)\over e(P,P)}]^c\\ &\quad\ =R_3\cdot \{{[e(A_i+(\alpha+\beta)H,P)^{x_i}e(H,\gamma P)^{-(\alpha+\beta)}e(H,P)^{-(\delta_1+\delta_2)}e(A_i+(\alpha+\beta)H, \gamma P)]\over e(P,P)}\}^c\\ &\quad\ =R_3\cdot\{{[e(A_i,P)^{x_i}e(A_i,P)^{\gamma}]\cdot[e(H,P)^{(\alpha+\beta)x_i}e(H,P)^{-\gamma(\alpha+\beta)}e(H,P)^{-(\delta_1+\delta_2)}e(H,P)^{\gamma(\alpha+\beta)}]\over e(P,P)}\}\\ &\quad\ =R_3\cdot ({e(P,P)\over e(P,P)})^c\\ &\quad\ =R_3& \end{flalign} \]

    \[\begin{flalign} &R_4=r_xT_1-r_{\delta_1}U=(r_x\alpha - r_{\delta_1})U\\ &R_4'=s_xT_1-s_{\delta_1}U=((r_x+cx_i)\alpha - (r_{\delta_1}+c\delta_1)) U=((r_x\alpha-r_{\delta_1})+(cx_i\alpha-cx_i\alpha))U=R_4& \end{flalign} \]

    \[\begin{flalign} &R_5=r_xT_2-r_{\delta_2}V=(r_x\beta-r_{\delta_2})V\\ &R_5'=s_xT_2-s_{\delta_2}V=((r_x+cx_i)\beta-(r_{\delta_2}+c\delta_2))V=((r_x\beta-r_{\delta_2})+(cx_i\beta-cx_i\beta))V=R_5& \end{flalign} \]

  5. 打开
    给定群公钥\(gpk=(P,H,U,V,W)\),消息\(m\)和待追踪的签名\((T_1,T_2,T_3,c,s_{\alpha},s_{\beta},s_{x},s_{\delta_1},s_{\delta_2})\),持有群私钥\(gmsk=(\xi_1,\xi_2)\)的群管理员\(opener\)执行下述操作即可追踪出产生该签名的群成员的身份。
    ① 执行签名验证算法验证该签名为有效的群签名;
    ② 计算\(A=T_3-(\xi_1T_1+\xi_2T_2)\);
    ③ 检查自己拥有的\(A_i\)与群成员身份的对应关系表,从而确定产生该签名的群成员身份。

  6. 成员撤销

    • 安全有效地撤销群成员,是指某个群成员撤销之后,他拥有的私钥和成员证书不能再用于产生有效的群签名的一种机制。对于群成员撤销的问题,较为常用的方式时群管理员发布一个身份撤销列表给所有群成员以及签名验证者。对于上述的短的群签名体制可以给出以下成员撤销的方法。
    • 方案中的群公钥为\(gpk=(P,H,U,V,W)\),其中\(H、U、V\)为群\(G_1\)中的随机元素,\(W=\gamma P,\gamma \in _R\mathbb{Z}_p^*\)。群成员的私钥为\((A_i,x_i)\),其中\(A_i={1\over \gamma + x_i}P\)。假设在不影响其他群成员签名能力的前提下撤销群成员\(1,\cdots,r\),群管理员公布上述的撤销列表,设为\(L=\{(A_1,x_1),\cdots,(A_r,x_r)\}\).群管理员将\(L\)公开,群成员以及群签名验证者根据\(L\)更新群公钥和签名私钥。
    • 不妨设此次撤销群成员\(1\)。撤销群成员\(1\)之后的群公钥构造方法为:
      1. 计算\(P'=A_1,W'=P-x_1A_1;gpk'=(P',H,U,V,W')\),由于\(W'=P-x_1A_1=P-x_1{1\over \gamma+x_1}P=\gamma\cdot {1\over\gamma + x_1}P=\gamma A_1=\gamma P'\),满足群公钥需要具备的形式,故将\(gpk'\)作为新的群公钥依旧满足签名、验证等算法的正常进行。这个过程重复\(r\)次,即可得到撤销群成员\(1,\cdots,r\)后的群公钥。
      2. 对于群成员\(r+1,\cdots,n\),也需要对应更新自己所拥有的私钥,不妨设\(i(r+1≤i≤n)\),其计算\(A_i'={1\over x_i-x_1}A_1-{1\over x_i-x_1}A_i\),置\((A_i',x_i)\)为自己的新的群成员私钥。此时,\((\gamma+x_i)A_i'=(\gamma + x_i){1\over x_i-x_1}A_1-(\gamma + x_i){1\over x_i-x_1}A_i={\gamma+x_i \over x_i-x_1}{1\over \gamma+x_1}P-{\gamma+x_i\over x_i-x_1}{1\over \gamma+x_i}P={1\over \gamma +x_1}P\),故此时得到的群私钥时满足群成员私钥的生成方式,这个过程重复\(r\)次,即可完成群成员私钥的更新。

上述就是\(Boneh\)等人的短的群签名体制,该方案最大的一个优点就是每个群成员产生的群签名都是固定长度的,并且每个群签名由\(3\)个群\(G_1\)中的元素和\(6\)\(\mathbb{Z}_p\)中的元素构成,以\(p\)\(170bit\)的素数,\(G_1\)中的每个元素为\(171bit\)为例,群签名的长度即为\(1533bit(192B)\)。该方案产生的签名之所以能够达到这个长度,很重要的原因是椭圆曲线群上点的压缩技术。

此外,对于签名、验证过程中的很多量,针对于同一群公钥下,比如\(e(H,W).e(H,P),e(P,P)\),都是可以预先计算出来的;每个群成员也可以预计算\(e(A_i,P)\),从而在执行签名操作时无需执行双线性配对就可以得到\(e(T_3,P)\)的值。总的来说,每产生一个群签名需要\(8\)个指数(或多指数,\(multi-exponentation\)),而不涉及任何双线性对运算。由于\(e(T_3,P)^{s_x}e(T_3,W)^c=e(T_3,cW+s_xP)\),在执行签名操作时,验证者只需要执行\(6\)个多指数运算和一个双线性对运算。

最后,根据成员加入或退出时群公钥或群私钥是否变化,可以将群签名方案分为动态(\(dynamic\))群签名和静态(\(static\))群签名两种。当一个群成员加入或撤销时,需要更新群公钥或群成员的私钥,则称这样的群签名体制为静态群签名;反之则称为动态群签名。

BBS04示例代码

#include <pbc/pbc.h>
#include <openssl/evp.h>

#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

namespace demo {

enum class FieldKind { G1, G2, GT, ZR };

class Element {
private:
    element_t _v; // Internal storage hidden from C++ const checks

public:
    element_s* v; // Mutable pointer exposed to PBC C functions
    bool initialized;

    Element() : v(_v), initialized(false) {}

    Element(pairing_s* pairing, FieldKind kind) : v(_v), initialized(false) {
        init(pairing, kind);
    }

    Element(const Element &other) : v(_v), initialized(false) {
        if (other.initialized) {
            element_init_same_as(v, other.v);
            element_set(v, other.v);
            initialized = true;
        }
    }

    Element(Element &&other) noexcept : v(_v), initialized(false) {
        if (other.initialized) {
            element_init_same_as(v, other.v);
            element_set(v, other.v);
            initialized = true;
        }
    }

    Element &operator=(const Element &other) {
        if (this == &other) {
            return *this;
        }
        if (!other.initialized) {
            if (initialized) {
                element_clear(v);
                initialized = false;
            }
            return *this;
        }
        if (!initialized) {
            element_init_same_as(v, other.v);
            initialized = true;
        }
        element_set(v, other.v);
        return *this;
    }

    Element &operator=(Element &&other) noexcept {
        if (this == &other) {
            return *this;
        }
        if (!other.initialized) {
            if (initialized) {
                element_clear(v);
                initialized = false;
            }
            return *this;
        }
        if (!initialized) {
            element_init_same_as(v, other.v);
            initialized = true;
        }
        element_set(v, other.v);
        return *this;
    }

    ~Element() {
        if (initialized) {
            element_clear(v);
        }
    }

    void init(pairing_s* pairing, FieldKind kind) {
        if (initialized) {
            element_clear(v);
            initialized = false;
        }
        switch (kind) {
            case FieldKind::G1:
                element_init_G1(v, pairing);
                break;
            case FieldKind::G2:
                element_init_G2(v, pairing);
                break;
            case FieldKind::GT:
                element_init_GT(v, pairing);
                break;
            case FieldKind::ZR:
                element_init_Zr(v, pairing);
                break;
        }
        initialized = true;
    }
};

struct Signature {
    Element T1, T2, T3;
    Element c, s_alpha, s_beta, s_x, s_delta1, s_delta2;

    explicit Signature(pairing_s* pairing)
        : T1(pairing, FieldKind::G1),
          T2(pairing, FieldKind::G1),
          T3(pairing, FieldKind::G1),
          c(pairing, FieldKind::ZR),
          s_alpha(pairing, FieldKind::ZR),
          s_beta(pairing, FieldKind::ZR),
          s_x(pairing, FieldKind::ZR),
          s_delta1(pairing, FieldKind::ZR),
          s_delta2(pairing, FieldKind::ZR) {}
};

struct MemberKey {
    int id;
    Element A;
    Element x;

    explicit MemberKey(pairing_s* pairing, int member_id)
        : id(member_id), A(pairing, FieldKind::G1), x(pairing, FieldKind::ZR) {}
};

class BBS04System {
private:
    pairing_t _pairing;

public:
    pbc_param_t param;
    pairing_s* pairing; // Mutable pointer proxy
    bool initialized;

    Element g1;      // G1
    Element g2;      // G2
    Element h;       // G1
    Element u;       // G1
    Element v;       // G1
    Element w;       // G2 = g2^gamma
    Element xi1;     // tracing secret, Zr
    Element xi2;     // tracing secret, Zr
    Element gamma;   // issuer secret, Zr

    // precomputations for current public key
    Element e_h_w;   // GT
    Element e_h_g2;  // GT
    Element e_g1_g2; // GT

    std::map<int, MemberKey> members;

    BBS04System()
        : pairing(_pairing),
          initialized(false),
          g1(), g2(), h(), u(), v(), w(), xi1(), xi2(), gamma(),
          e_h_w(), e_h_g2(), e_g1_g2() {}

    ~BBS04System() {
        if (initialized) {
            members.clear();
            auto clear_element = [](Element& e) {
            if (e.initialized) {
                element_clear(e.v);
                e.initialized = false;
                }
            };

            clear_element(g1); clear_element(g2); clear_element(h);
            clear_element(u); clear_element(v); clear_element(w);
            clear_element(xi1); clear_element(xi2); clear_element(gamma);
            clear_element(e_h_w); clear_element(e_h_g2); clear_element(e_g1_g2);

            pairing_clear(pairing);
            pbc_param_clear(param);
            initialized = false;
        }
    }

    void setup(int rbits, int qbits) {
        if (initialized) {
            throw std::runtime_error("system already initialized");
        }

        pbc_param_init_a_gen(param, rbits, qbits);
        pairing_init_pbc_param(pairing, param);

        if (!pairing_is_symmetric(pairing)) {
            throw std::runtime_error("This demo requires a symmetric pairing (Type A in PBC).");
        }

        g1.init(pairing, FieldKind::G1);
        g2.init(pairing, FieldKind::G2);
        h.init(pairing, FieldKind::G1);
        u.init(pairing, FieldKind::G1);
        v.init(pairing, FieldKind::G1);
        w.init(pairing, FieldKind::G2);
        xi1.init(pairing, FieldKind::ZR);
        xi2.init(pairing, FieldKind::ZR);
        gamma.init(pairing, FieldKind::ZR);
        e_h_w.init(pairing, FieldKind::GT);
        e_h_g2.init(pairing, FieldKind::GT);
        e_g1_g2.init(pairing, FieldKind::GT);

        initialized = true;

        random_non_identity_G1(g1);
        copy_G1_to_G2_symmetric(g2, g1);

        random_non_identity_G1(h);
        random_non_zero_Zr(xi1);
        random_non_zero_Zr(xi2);
        random_non_zero_Zr(gamma);

        // u = h^{1/xi1}, v = h^{1/xi2}, so u^{xi1} = v^{xi2} = h
        Element inv_xi1(pairing, FieldKind::ZR);
        Element inv_xi2(pairing, FieldKind::ZR);
        element_invert(inv_xi1.v, xi1.v);
        element_invert(inv_xi2.v, xi2.v);
        element_pow_zn(u.v, h.v, inv_xi1.v);
        element_pow_zn(v.v, h.v, inv_xi2.v);

        // w = g2^gamma
        element_pow_zn(w.v, g2.v, gamma.v);

        refresh_precomputes();
    }

    MemberKey join_member(int member_id) {
        ensure_initialized();
        if (members.find(member_id) != members.end()) {
            throw std::runtime_error("member id already exists");
        }

        MemberKey mk(pairing, member_id);
        Element denom(pairing, FieldKind::ZR);
        Element inv_denom(pairing, FieldKind::ZR);

        while (true) {
            element_random(mk.x.v);
            element_add(denom.v, gamma.v, mk.x.v);
            if (!element_is0(denom.v)) {
                break;
            }
        }

        element_invert(inv_denom.v, denom.v);
        element_pow_zn(mk.A.v, g1.v, inv_denom.v);

        members.emplace(member_id, mk);
        return mk;
    }

    Signature sign(const MemberKey &mk, const std::string &message) const {
        ensure_initialized();

        Signature sig(pairing);

        Element alpha(pairing, FieldKind::ZR);
        Element beta(pairing, FieldKind::ZR);
        Element delta1(pairing, FieldKind::ZR);
        Element delta2(pairing, FieldKind::ZR);
        Element alpha_plus_beta(pairing, FieldKind::ZR);

        Element r_alpha(pairing, FieldKind::ZR);
        Element r_beta(pairing, FieldKind::ZR);
        Element r_x(pairing, FieldKind::ZR);
        Element r_delta1(pairing, FieldKind::ZR);
        Element r_delta2(pairing, FieldKind::ZR);

        element_random(alpha.v);
        element_random(beta.v);
        element_mul(delta1.v, mk.x.v, alpha.v);
        element_mul(delta2.v, mk.x.v, beta.v);
        element_add(alpha_plus_beta.v, alpha.v, beta.v);

        element_pow_zn(sig.T1.v, u.v, alpha.v);
        element_pow_zn(sig.T2.v, v.v, beta.v);

        Element h_ab(pairing, FieldKind::G1);
        element_pow_zn(h_ab.v, h.v, alpha_plus_beta.v);
        element_mul(sig.T3.v, mk.A.v, h_ab.v);

        element_random(r_alpha.v);
        element_random(r_beta.v);
        element_random(r_x.v);
        element_random(r_delta1.v);
        element_random(r_delta2.v);

        Element R1(pairing, FieldKind::G1);
        Element R2(pairing, FieldKind::G1);
        Element R3(pairing, FieldKind::GT);
        Element R4(pairing, FieldKind::G1);
        Element R5(pairing, FieldKind::G1);

        build_commitments(sig.T1, sig.T2, sig.T3,
                          r_alpha, r_beta, r_x, r_delta1, r_delta2,
                          R1, R2, R3, R4, R5);

        compute_challenge(sig.c, message, sig.T1, sig.T2, sig.T3, R1, R2, R3, R4, R5);

        Element tmp(pairing, FieldKind::ZR);
        element_mul(tmp.v, sig.c.v, alpha.v);
        element_add(sig.s_alpha.v, r_alpha.v, tmp.v);

        element_mul(tmp.v, sig.c.v, beta.v);
        element_add(sig.s_beta.v, r_beta.v, tmp.v);

        element_mul(tmp.v, sig.c.v, mk.x.v);
        element_add(sig.s_x.v, r_x.v, tmp.v);

        element_mul(tmp.v, sig.c.v, delta1.v);
        element_add(sig.s_delta1.v, r_delta1.v, tmp.v);

        element_mul(tmp.v, sig.c.v, delta2.v);
        element_add(sig.s_delta2.v, r_delta2.v, tmp.v);

        return sig;
    }

    bool verify(const Signature &sig, const std::string &message) const {
        ensure_initialized();

        Element R1(pairing, FieldKind::G1);
        Element R2(pairing, FieldKind::G1);
        Element R3(pairing, FieldKind::GT);
        Element R4(pairing, FieldKind::G1);
        Element R5(pairing, FieldKind::G1);

        // R1 = u^{s_alpha} / T1^c
        Element t1_c(pairing, FieldKind::G1);
        element_pow_zn(R1.v, u.v, sig.s_alpha.v);
        element_pow_zn(t1_c.v, sig.T1.v, sig.c.v);
        element_div(R1.v, R1.v, t1_c.v);

        // R2 = v^{s_beta} / T2^c
        Element t2_c(pairing, FieldKind::G1);
        element_pow_zn(R2.v, v.v, sig.s_beta.v);
        element_pow_zn(t2_c.v, sig.T2.v, sig.c.v);
        element_div(R2.v, R2.v, t2_c.v);

        // R4 = T1^{s_x} / u^{s_delta1}
        Element u_sd1(pairing, FieldKind::G1);
        element_pow_zn(R4.v, sig.T1.v, sig.s_x.v);
        element_pow_zn(u_sd1.v, u.v, sig.s_delta1.v);
        element_div(R4.v, R4.v, u_sd1.v);

        // R5 = T2^{s_x} / v^{s_delta2}
        Element v_sd2(pairing, FieldKind::G1);
        element_pow_zn(R5.v, sig.T2.v, sig.s_x.v);
        element_pow_zn(v_sd2.v, v.v, sig.s_delta2.v);
        element_div(R5.v, R5.v, v_sd2.v);

        // R3 = e(T3,g2)^{s_x} * e(h,w)^{-s_alpha-s_beta} * e(h,g2)^{-s_delta1-s_delta2} * (e(T3,w)/e(g1,g2))^c
        Element e_t3_g2(pairing, FieldKind::GT);
        Element term1(pairing, FieldKind::GT);
        Element term2(pairing, FieldKind::GT);
        Element term3(pairing, FieldKind::GT);
        Element term4(pairing, FieldKind::GT);
        Element e_t3_w(pairing, FieldKind::GT);
        Element frac(pairing, FieldKind::GT);

        element_pairing(e_t3_g2.v, sig.T3.v, g2.v);
        element_pow_zn(term1.v, e_t3_g2.v, sig.s_x.v);

        Element sum_ab(pairing, FieldKind::ZR);
        Element neg_sum_ab(pairing, FieldKind::ZR);
        element_add(sum_ab.v, sig.s_alpha.v, sig.s_beta.v);
        element_neg(neg_sum_ab.v, sum_ab.v);
        element_pow_zn(term2.v, e_h_w.v, neg_sum_ab.v);

        Element sum_d(pairing, FieldKind::ZR);
        Element neg_sum_d(pairing, FieldKind::ZR);
        element_add(sum_d.v, sig.s_delta1.v, sig.s_delta2.v);
        element_neg(neg_sum_d.v, sum_d.v);
        element_pow_zn(term3.v, e_h_g2.v, neg_sum_d.v);

        element_pairing(e_t3_w.v, sig.T3.v, w.v);
        element_div(frac.v, e_t3_w.v, e_g1_g2.v);
        element_pow_zn(term4.v, frac.v, sig.c.v);

        element_mul(R3.v, term1.v, term2.v);
        element_mul(R3.v, R3.v, term3.v);
        element_mul(R3.v, R3.v, term4.v);

        Element c_check(pairing, FieldKind::ZR);
        compute_challenge(c_check, message, sig.T1, sig.T2, sig.T3, R1, R2, R3, R4, R5);
        return element_cmp(c_check.v, sig.c.v) == 0;
    }

    int open(const Signature &sig, const std::string &message) const {
        ensure_initialized();
        if (!verify(sig, message)) {
            return -1;
        }

        Element t1_xi1(pairing, FieldKind::G1);
        Element t2_xi2(pairing, FieldKind::G1);
        Element denom(pairing, FieldKind::G1);
        Element A(pairing, FieldKind::G1);

        element_pow_zn(t1_xi1.v, sig.T1.v, xi1.v);
        element_pow_zn(t2_xi2.v, sig.T2.v, xi2.v);
        element_mul(denom.v, t1_xi1.v, t2_xi2.v);
        element_div(A.v, sig.T3.v, denom.v);

        for (const auto &kv : members) {
            if (element_cmp(A.v, kv.second.A.v) == 0) {
                return kv.first;
            }
        }
        return -1;
    }

    MemberKey get_member_key_copy(int member_id) const {
        auto it = members.find(member_id);
        if (it == members.end()) {
            throw std::runtime_error("member not found");
        }
        return it->second;
    }

    void revoke_member(int revoked_id) {
        ensure_initialized();
        auto it = members.find(revoked_id);
        if (it == members.end()) {
            throw std::runtime_error("member not found");
        }

        const MemberKey revoked = it->second;

        // In the original paper, revocation uses A1* in G2 and psi(A1*) in G1.
        // Under this demo's symmetric pairing, we treat psi as the identity map.
        Element new_g1(pairing, FieldKind::G1);
        Element new_g2(pairing, FieldKind::G2);
        Element new_w(pairing, FieldKind::G2);

        element_set(new_g1.v, revoked.A.v);
        copy_G1_to_G2_symmetric(new_g2, revoked.A);
        element_pow_zn(new_w.v, new_g2.v, gamma.v);

        std::map<int, MemberKey> updated;
        for (const auto &kv : members) {
            if (kv.first == revoked_id) {
                continue;
            }

            MemberKey mk(pairing, kv.first);
            element_set(mk.x.v, kv.second.x.v);

            // A_hat = revoked.A^{1/(x - x_rev)} / A^{1/(x - x_rev)}
            Element diff(pairing, FieldKind::ZR);
            Element inv_diff(pairing, FieldKind::ZR);
            element_sub(diff.v, kv.second.x.v, revoked.x.v);
            if (element_is0(diff.v)) {
                throw std::runtime_error("revocation update encountered zero denominator");
            }
            element_invert(inv_diff.v, diff.v);

            Element left(pairing, FieldKind::G1);
            Element right(pairing, FieldKind::G1);
            element_pow_zn(left.v, revoked.A.v, inv_diff.v);
            element_pow_zn(right.v, kv.second.A.v, inv_diff.v);
            element_div(mk.A.v, left.v, right.v);

            updated.emplace(kv.first, mk);
        }

        element_set(g1.v, new_g1.v);
        element_set(g2.v, new_g2.v);
        element_set(w.v, new_w.v);
        members.swap(updated);
        refresh_precomputes();
    }

    std::string element_to_string(const Element &e) const {
        int n = element_length_in_bytes(e.v);
        std::vector<unsigned char> buf(static_cast<size_t>(n));
        element_to_bytes(buf.data(), e.v);
        std::ostringstream oss;
        oss << std::hex << std::setfill('0');
        const size_t show = std::min<size_t>(buf.size(), 12);
        for (size_t i = 0; i < show; ++i) {
            oss << std::setw(2) << static_cast<unsigned int>(buf[i]);
        }
        if (buf.size() > show) {
            oss << "...";
        }
        return oss.str();
    }

private:
    void ensure_initialized() const {
        if (!initialized) {
            throw std::runtime_error("system is not initialized");
        }
    }

    void random_non_identity_G1(Element &e) const {
        do {
            element_random(e.v);
        } while (element_is1(e.v));
    }

    void random_non_zero_Zr(Element &e) const {
        do {
            element_random(e.v);
        } while (element_is0(e.v));
    }

    void copy_G1_to_G2_symmetric(Element &dst_g2, const Element &src_g1) const {
        int n = element_length_in_bytes(src_g1.v);
        std::vector<unsigned char> buf(static_cast<size_t>(n));
        element_to_bytes(buf.data(), src_g1.v);
        element_from_bytes(dst_g2.v, buf.data());
    }

    void refresh_precomputes() {
        element_pairing(e_h_w.v, h.v, w.v);
        element_pairing(e_h_g2.v, h.v, g2.v);
        element_pairing(e_g1_g2.v, g1.v, g2.v);
    }

    void append_element_bytes(std::vector<unsigned char> &out, const Element &e) const {
        int n = element_length_in_bytes(e.v);
        std::vector<unsigned char> buf(static_cast<size_t>(n));
        element_to_bytes(buf.data(), e.v);

        uint32_t len = static_cast<uint32_t>(buf.size());
        for (int i = 3; i >= 0; --i) {
            out.push_back(static_cast<unsigned char>((len >> (i * 8)) & 0xffU));
        }
        out.insert(out.end(), buf.begin(), buf.end());
    }

    std::array<unsigned char, 32> sha256(const std::vector<unsigned char> &data) const {
        std::array<unsigned char, 32> digest{};
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (ctx == nullptr) {
            throw std::runtime_error("EVP_MD_CTX_new failed");
        }
        unsigned int out_len = 0;
        if (EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) != 1 ||
            EVP_DigestUpdate(ctx, data.data(), data.size()) != 1 ||
            EVP_DigestFinal_ex(ctx, digest.data(), &out_len) != 1) {
            EVP_MD_CTX_free(ctx);
            throw std::runtime_error("EVP digest failed");
        }
        EVP_MD_CTX_free(ctx);
        if (out_len != digest.size()) {
            throw std::runtime_error("unexpected SHA-256 output length");
        }
        return digest;
    }

    void compute_challenge(Element &c_out,
                           const std::string &message,
                           const Element &T1,
                           const Element &T2,
                           const Element &T3,
                           const Element &R1,
                           const Element &R2,
                           const Element &R3,
                           const Element &R4,
                           const Element &R5) const {
        std::vector<unsigned char> data;
        data.insert(data.end(), message.begin(), message.end());
        data.push_back(0x00);
        append_element_bytes(data, T1);
        append_element_bytes(data, T2);
        append_element_bytes(data, T3);
        append_element_bytes(data, R1);
        append_element_bytes(data, R2);
        append_element_bytes(data, R3);
        append_element_bytes(data, R4);
        append_element_bytes(data, R5);

        const auto digest = sha256(data);
        element_from_hash(c_out.v, digest.data(), static_cast<int>(digest.size()));
    }

    void build_commitments(const Element &T1,
                           const Element &T2,
                           const Element &T3,
                           const Element &r_alpha,
                           const Element &r_beta,
                           const Element &r_x,
                           const Element &r_delta1,
                           const Element &r_delta2,
                           Element &R1,
                           Element &R2,
                           Element &R3,
                           Element &R4,
                           Element &R5) const {
        // R1 = u^{r_alpha}, R2 = v^{r_beta}
        element_pow_zn(R1.v, u.v, r_alpha.v);
        element_pow_zn(R2.v, v.v, r_beta.v);

        // R3 = e(T3,g2)^{r_x} * e(h,w)^{-r_alpha-r_beta} * e(h,g2)^{-r_delta1-r_delta2}
        Element e_t3_g2(pairing, FieldKind::GT);
        Element term1(pairing, FieldKind::GT);
        Element term2(pairing, FieldKind::GT);
        Element term3(pairing, FieldKind::GT);
        element_pairing(e_t3_g2.v, T3.v, g2.v);
        element_pow_zn(term1.v, e_t3_g2.v, r_x.v);

        Element sum_ab(pairing, FieldKind::ZR);
        Element neg_sum_ab(pairing, FieldKind::ZR);
        element_add(sum_ab.v, r_alpha.v, r_beta.v);
        element_neg(neg_sum_ab.v, sum_ab.v);
        element_pow_zn(term2.v, e_h_w.v, neg_sum_ab.v);

        Element sum_d(pairing, FieldKind::ZR);
        Element neg_sum_d(pairing, FieldKind::ZR);
        element_add(sum_d.v, r_delta1.v, r_delta2.v);
        element_neg(neg_sum_d.v, sum_d.v);
        element_pow_zn(term3.v, e_h_g2.v, neg_sum_d.v);

        element_mul(R3.v, term1.v, term2.v);
        element_mul(R3.v, R3.v, term3.v);

        // R4 = T1^{r_x} / u^{r_delta1}
        Element u_rdelta1(pairing, FieldKind::G1);
        element_pow_zn(R4.v, T1.v, r_x.v);
        element_pow_zn(u_rdelta1.v, u.v, r_delta1.v);
        element_div(R4.v, R4.v, u_rdelta1.v);

        // R5 = T2^{r_x} / v^{r_delta2}
        Element v_rdelta2(pairing, FieldKind::G1);
        element_pow_zn(R5.v, T2.v, r_x.v);
        element_pow_zn(v_rdelta2.v, v.v, r_delta2.v);
        element_div(R5.v, R5.v, v_rdelta2.v);
    }
};

static void print_banner(const std::string &title) {
    std::cout << "\n----- " << title << " -----\n";
}

} // namespace demo

int main(int argc, char **argv) {
    try {
        int rbits = 160;
        int qbits = 512;
        if (argc == 3) {
            rbits = std::atoi(argv[1]);
            qbits = std::atoi(argv[2]);
        }

        demo::BBS04System sys;
        sys.setup(rbits, qbits);

        demo::print_banner("Setup");
        std::cout << "g1 = " << sys.element_to_string(sys.g1) << "\n";
        std::cout << "g2 = " << sys.element_to_string(sys.g2) << "\n";
        std::cout << "h  = " << sys.element_to_string(sys.h) << "\n";
        std::cout << "u  = " << sys.element_to_string(sys.u) << "\n";
        std::cout << "v  = " << sys.element_to_string(sys.v) << "\n";
        std::cout << "w  = " << sys.element_to_string(sys.w) << "\n";

        demo::print_banner("Join members");
        auto m1 = sys.join_member(1);
        auto m2 = sys.join_member(2);
        auto m3 = sys.join_member(3);
        std::cout << "member 1 joined, A = " << sys.element_to_string(m1.A) << "\n";
        std::cout << "member 2 joined, A = " << sys.element_to_string(m2.A) << "\n";
        std::cout << "member 3 joined, A = " << sys.element_to_string(m3.A) << "\n";

        demo::print_banner("Member 2 signs");
        const std::string msg1 = "group signature before revocation";
        auto sig1 = sys.sign(sys.get_member_key_copy(2), msg1);
        std::cout << "verify(sig1) = " << (sys.verify(sig1, msg1) ? "OK" : "FAIL") << "\n";
        std::cout << "open(sig1)   = member " << sys.open(sig1, msg1) << "\n";

        demo::print_banner("Revoke member 2");
        auto stale_m2 = sys.get_member_key_copy(2); // keep a stale copy to show failure after revocation
        sys.revoke_member(2);
        std::cout << "member 2 revoked; public key and unrevoked members updated\n";

        // Old signature was created under the old group public key; it should not verify under the updated key.
        std::cout << "verify(sig1, current_pk) = " << (sys.verify(sig1, msg1) ? "OK" : "FAIL") << "\n";

        demo::print_banner("Unrevoked member 1 signs after revocation");
        const std::string msg2 = "group signature after revocation";
        auto sig2 = sys.sign(sys.get_member_key_copy(1), msg2);
        std::cout << "verify(sig2) = " << (sys.verify(sig2, msg2) ? "OK" : "FAIL") << "\n";
        std::cout << "open(sig2)   = member " << sys.open(sig2, msg2) << "\n";

        demo::print_banner("Revoked member 2 tries using stale key");
        const std::string msg3 = "revoked member attempt";
        auto forged_like = sys.sign(stale_m2, msg3);
        std::cout << "verify(revoked_member_signature) = "
                  << (sys.verify(forged_like, msg3) ? "UNEXPECTED-OK" : "FAIL (expected)")
                  << "\n";
        std::cout << "open(revoked_member_signature)   = member " << sys.open(forged_like, msg3) << "\n";
        return 0;
    } catch (const std::exception &ex) {
        std::cerr << "[!] Error: " << ex.what() << "\n";
        return 1;
    }
}

写在最后

再回头看群签名体制的时候感觉其就是依照者“基于一次性密钥表的朴素群签名”的思想在现有的签名方案基础上做出的改进。如果你看过上一篇高级签名协议 -- 盲签名的话,盲签名相较于普通的数字签名体制的优点就在于其匿名性和不可追踪性是现实生活中所需要的,其中最主要的技术就是盲化和去盲。但是到了群签名这里,特别是编辑BBS04方案的时候去看了一下原论文,我的感触就是群签名体制很多情况下他的签名方法是很简单的,例如BBS04,对于消息\(m\)的签名就是在特定的数据下对消息\(m\)进行了一次哈希操作,但是它从又引入了双线性配对等更复杂的内容,就是想要完成的概念,在这个中,谁都可以签名,但是并不能真正地体现出来身份,这个过程对于密码学而言是设计困难的,对于其中的签名安全性的保证,简单的哈希操作即可完成,但是需要确保的是,除去消息\(m\)之外的内容足以保证满足的特性:合法群成员签名后不可被随意追踪,且任一合法群成员的签名都可被验证通过。这是普通数字签名不具备的特性,也是设计中最为注重和困难的部分。

另外就是BBS04示例代码的编写,依靠了PBC库。另外,刚开始学习这种工程化的编程,所以像类的定义以及命令行传参可能写的不是非常符合工程式编程的要求,正好利用这个BBS04的Demo来练习一下。

posted @ 2026-04-08 11:49  chen_xing  阅读(10)  评论(0)    收藏  举报