高级签名协议概述
一般地,一个数字签名体制包括:密钥生成(keygen)、签名(sign)、验签(verify)。对普通签名体制的安全需求是,攻击者即使知道签名人的公钥和若干签名对的有效信息也无法伪造该签名人的有效签名。但是随着对数字签名的不断研究,以及电子商务、电子政务的快速发展,简单模拟手写签名的一般数字签名已不能完全满足现实中的应用需求,研究具有特殊性质或特殊功能的数字签名成为数字签名研究的主要方向。在密码学的研究中有许多学者提出了具有特定性质的数字签名,本次博客主要介绍其中的盲签名、群签名、环签名和基于身份的数字签名。
盲签名
盲签名的基本概念
在一个盲签名体制中存在两个参与实体:签名人和用户,其中,签名人拥有自己的公私钥,用户有一个消息,并希望得到签名人对的签名。一个盲签名方案一般由满足如下条件的3个算法Setup、Sign、Verify构成。
- Setup是一个概率多项式时间算法。其输出为系统参数和签名人的公/私钥对。
- Sign是一个概率多项式时间的交互协议,公共输入是系统参数和签名人的公钥,签名人的秘密输入为自己的私钥,用户的秘密输入为待签名的消息,双方交互执行签名协议,在多项式时间内停止,停止后用户输出签名。
- Verify是一个多项式时间算法,其输入为系统公开参数、签名人的公钥和待验证的签名对,其输出为(表示签名有效)或(表示签名无效),记作或。
盲签名的安全性需求
盲签名的安全需求,通俗地说,我们称一个盲签名体制是安全的,它至少需要满足下面的3个性质:
- 正确性(也称完备性与一致性) 如果是算法正确执行后输出的对于消息的签名,则总有。
- 不可伪造性 任意不知道签名人私钥的人,无法有效地计算出一个能够通过签名验证方程地消息签名对。
- 盲性 除请求签名地用户外,任何人(包括签名人)都无法将交互协议产生地会话信息(签名人与用户在公共信道上交互的信息的集合)与最终的盲签名正确匹配起来。如果签名人能将其会话信息与最终所得的签名正确匹配,他就能够跟踪签名。
盲签名的基本设计思路
假设用户有一个消息,并希望得到签名人对该消息的签名,一种比较典型的设计盲签名体制的做法是:
- 在发送消息给签名人之前,用户先引入盲化因子,由消息计算出数据,发送给签名人,这个过程称为盲化。
- 签名人对执行签名操作得到签名,发回给用户。
- 用户从中计算出消息的签名,这个过程称为去盲。
在签名过程中,签名人不知道消息是什么;在签名过后,如果把签名交给签名人,他不知道这是什么时候签的,也不知道是签给谁的。盲签名的这些良好特性使得它在如电子货币、电子拍卖、电子选举等诸多同时需要匿名性和认证性的应用场合起到关键作用,比如可以用来实现不可跟踪电子货币或无记名选举。
Chaum盲签名协议
在接下来的描述中,将参照教材的语言进行描述。“本节描述算法时使用电子选举的语言,方案适用于其他应用。选举委员会发布候选人名单,投票人在选票上标记自己的意向候选人。为了使得选票有效,该选票需要经过选举委员会签名确认。既要得到选举委员会对选票的签名,又不能泄露选票的内容,此时投票人可以和选举委员会交互执行盲签名协议。”
协议流程:
\begin{flalign} &Setup:\\ &\quad p,q = getPrime(safe.bit\_length);n = p * q;Pubkey = (n, e);Pravitekey = d\\ &Sign:\\ &\quad 盲化:USER\ choose\ k \in R[1,n - 1],compute\ m'\equiv m\cdot k^e(mod\ n),m'\rightarrow Signer\\ &\quad 签名:Signer\ compute\ s'\equiv (m')^d(mod\ n),s'\rightarrow USER\\ &\quad 去盲;USER\ compute\ s \equiv k^{-1}\cdot s'(mod\ n),(m,s)\rightarrow Verifier\\ &Verify:\\ &\quad return\quad s^e(mod \ n) == m& \end{flalign}sequenceDiagram participant U as User participant S as Signer
Note over U: 计算 m' = kᵉ m mod n
U->>S: 发送 m'
Note over S: 计算 s' = m'ᵈ mod n
S->>U: 返回 s'
Note over U: 计算 s = k⁻¹ s' mod n一次关于可关联性的错误思路
阅读教材时发现,在选举系统中,投票结束后会公开**(m,s),本人最初认为如果公开(m,s)就不再满足防追踪性(不可关联性)**。具体攻击(后续也证明这种思路是不可行的)思路如下:
\begin{flalign} &Target:选定一组(m',s'),若寻找到该(m',s')所对应的(m,s)则攻击成功.\\ &\quad 设公开的所有选民的(m,s)为(m,s)group.由m_i'\equiv m_i \cdot k_i^e(mod\ n);s_i'\equiv m_i^d\cdot k_i(mod\ n);s_i\equiv m_i^d(mod\ n)\rightarrow k_i\equiv s_i'\cdot s_i^{-1}(mod\ n)\\ &Attack:\\ &设选定(m_j',s_j')\\ &\quad for\ i \ in\ 1..num\\ &\quad\quad k' \equiv s_j' \cdot s_i^{-1}(mod\ n)\\ &\quad\quad if\ m_i\cdot (k_i')^e(mod\ n) \equiv m_j'\\ &\quad\quad\quad j\ is\ i\\ &攻击思路:由(m,s)group我们可以根据s与s'的关系,计算一个kgroup。\\ &针对于选定的(m',s'),只有与之对应的(m,s)计算的k_{temp}与当初USER选定的盲化因子相同,但其一定存在于我们计算的kgroup.\\ &事实正确确实如此,但是理想协议执行状态下Signer不可获取所有USER的k,因此,我尝试采用m'==m\cdot k_{guess}^e(mod\ n)来判定.\\ &但是结果并没有达到Target.原因如下:\\ &\quad k_{guess_{i}}\equiv s_j'\cdot s_i^{-1}(mod\ n)\rightarrow m_i\cdot k_{guess_{i}}^e(mod\ n)\equiv m_i\cdot (s_j')^e \cdot s_i^{-e}(mod\ n)\equiv m_i\cdot (m_j^d\cdot k_j)^e\cdot m_i^{-1}\equiv m_j\cdot k_j^e(mod\ n)\equiv m_j'\\ &故m_i\cdot k_{guess_{i}}^e\equiv m_j'恒成立,攻击失效. \end{flalign}from Crypto.Util.number import getPrime, inverseimport random
###################### 初始化阶段 ######################p = getPrime(512); q = getPrime(512)n = p * qe = 65537 # RSA加密常用公钥指数phi = (p - 1) * (q - 1); d = inverse(e, phi)
###################### 预处理 ####################### 设有100名人员参与投票x = 100# k_list为每位选票人员盲化时生成的随机数k, k ∈ [1, n - 1]k_list = [random.randrange(1, n) for _ in range(x)]# m_list为每为选票人员所投出去的票,为验证结果情况,取值为 1..100m_list = [i for i in range(1, x + 1)]
###################### 签名阶段 ####################### ss_list 存储选举委员会签名后的 s'; s_list存储最终提交的验证结果; mm_list存储盲化后的 m'# 至此,我们有了 (m_list, s_list) 与 (mm_list, ss_list),前者对应教材公开后的(m, s); 后者对应选举委员会进行签名时收到的结果# k_list并非 kgroup; 设 kk_list 表示公开(m,s)后委员会计算的 kgroupss_list = []; s_list = []; mm_list = []for i in range(x): m = m_list[i]; k = k_list[i] # 盲化 mm 代替协议中的 m' mm = m * pow(k, e, n) % n # m' = m * k ^ e (mod n) ss = pow(mm, d, n) # s' = m' ^ d (mod n) s = ss * inverse(k, n) % n mm_list.append(mm); ss_list.append(ss); s_list.append(s)###################### 验签阶段 ####################### 若结果为 False ,输出 "Error" 后退出验证for i in range(x): m = m_list[i]; s = s_list[i] if pow(s, e, n) != m: print("Error") break###################### 攻击阶段 ######################print("Attack begin.")# 我们选取第 51 组的 (m', s'),找到其对应的(m, s)即攻击成功,即若后续结果校验时,在 i = 51 处显示正确,则说明攻击成功mm = mm_list[51]; ss = ss_list[51]# 1. 计算 kgroupkk_list = []for i in range(x): s = s_list[i] k_guess = ss * inverse(s, n) % n # k_guess = s' * s ^ {-1} (mod n) kk_list.append(k_guess)# 2. 令 m'' = m * k_guess ^ e (mod n) ,采用 m'' == m' 进行校验for i in range(x): m = m_list[i] k = kk_list[i] # mmm 表示当前计算的 m'', mm = m * k ^ e (mod n) mmm = m * pow(k, e, n) % n if mmm == mm: print(f"The origin of (m', s') is {i}")print("Attack over.")代码实现
from Crypto.Util.number import getPrime, bytes_to_longimport random
class TA:
def __init__(self): self.p, self.q = [getPrime(1024) for _ in range(2)] self.n = self.p * self.q self.e = 65537 phi = (self.p - 1) * (self.q - 1) self.d = pow(self.e, -1, phi)
class USER:
def __init__(self, TA): self.TA = TA self.k = random.randrange(1, self.TA.n)
def blinding(self, message): if isinstance(message, bytes): message = bytes_to_long(message) mm = message * pow(self.k, self.TA.e, self.TA.n) % self.TA.n return mm
def unblinding(self, ss): return pow(self.k, -1, self.TA.n) * ss
class Signer:
def __init__(self, TA): self.TA = TA
def sign(self, mm): return pow(mm, self.TA.d, self.TA.n)
class Verifier:
def __init__(self, TA): self.TA = TA
def verify(self, mspair:tuple) -> bool: m, s = mspair if isinstance(message, bytes): m = bytes_to_long(m) return m == pow(s, self.TA.e, self.TA.n)if __name__ == "__main__":
ta = TA() user = USER(ta) signer = Signer(ta) verifier = Verifier(ta)
message = b'I choose Alice to go.'
mm = user.blinding(message) ss = signer.sign(mm) s = user.unblinding(ss)
flag = verifier.verify((message, s))
if flag: print("Verify Successfully.") else: print("Error...")基于离散对数的盲签名
教材提要
设是一个阶为素数的乘法循环群,其上的问题和分别定义如下:
- 给定中的3个随机元素,计算。
- 给定中的4个元素,要么①中的随机元素,要么是②满足等式,且两种情况概率相等。如果是第一种情况输出为,如果是第二种情况则输出为。判断属于哪种情况。
给定一个素数阶群,如果存在一个有效的算法能够求解其上的问题,但不存在有效算法求解其上的问题,则将这样的群称为群。在2001年,斯坦福大学的3位学者、和利用群设计了一个短签名方案,以下称之为体制,该方案是这样工作的:为群,为一个哈希函数,签名人选择作为自己的私钥,将作为自己的公钥;对于任意消息,其数字签名为;给定消息对,如果,则验证者接受签名有效,否则拒绝。
利用体制,容易得到一个群上的盲签名方案。这个工作是由在2002年完成的。
协议流程
\begin{flalign} &1. Setup\\ &\quad 设G是一个阶为素数p的GDH群,g是它的任一生成元,H:\{0,1\}^*\rightarrow G^*为哈希函数,签名人Signer的公私钥为(y=g^x,x)\\ &2. Sign\\ &\quad 持有消息m的用户User为了获得签名人的签名,他与Signer执行如下交互,基于离散对数的盲签名:\\ &\quad ①盲化:User任选一随机数r,计算m'=g^rH(m),将m'发送给Signer;\\ &\quad ②签名:Signer计算s'=(m')^x,将结果返回给User;\\ &\quad ③去盲:User计算s=s'\cdot y^{-r},输出签名对(m,s).\\ &3. Verify\\ &\quad 给定Signer的公钥y和消息签名对(m,s),如果V_{DDH}(g,y,H(m),s)=1,则签名验证者相信这是Signer产生的有效签名。& \end{flalign}sequenceDiagram participant U as User participant S as Signer
Note over U: 选择 r ←ᵣ Zₚ*<br/>计算 m' = gʳ H(m)
U->>S: 发送 m'
Note over S: 计算 s' = m'ˣ
S->>U: 返回 s'
Note over U: 计算 s = s' y⁻ʳ双线性配对
在进行代码实现之前,对课本中提到的与进行以下详细的解释,更加直观一些:
\begin{flalign} &GDH\\ &\quad CDH(计算Diffie-Hellman,即给定g^a和g^b,求解g^{ab})困难,但存在一个神奇的算法V_{DDH}(·),它能有效地解决\\ &\quad DDH问题(判定Diffie-Hellman,即给定g^a,g^b,h,判断h是否等于g^{ab}.& \end{flalign}上述在描述协议时,只是在假设存在这样的一个算法,然后基于这个假设去推导盲签名以及签名的安全性,至于这个算法如何通过计算机实现,教材中并未提及。在计算机中,如何实现就是一个问题,在已知的数学中,除了穷举,该如何构建一个能够防住又能有效解决的群?密码学界找了很久发现:配对友好型椭圆曲线上的双线性配对(Bilinear Piring),完美契合了这个要求。
双线性配对天然具有一个性质,它可以把指数上的乘法“提取”出来:。联系到教材中描述的,以及上方我简化的问题:给定公钥,以及哈希点,验证者在那道签名,需要判断,所以如果我们用配对来实现,验证过程就会及其简单,我们只需要计算并对比两边的配对结果:,,如果签名真的是用私钥签名所得,即,那么等式成立。这个计算配对的等式,就是在数学中抽象的在计算机中的实现。
为何依然困难?虽然配对能够依据上述等式来判断是否正确,但是它并不能帮你算出签名。配对的结果会落在另一个群中,无法通过反推回原来的群元素,这就实现了判定容易,计算困难的属性。
代码实现
#include <pbc/pbc.h>#include <iostream>#include <cstring>
using namespace std;
int main(){ // 初始化配对环境 char param[1024] = "type a\nq 8780710799663312522437781984754049815806883199414208211028653399266475630880222957078625179422662221423155858769582317459277713367317481324925129998224791\nh 12016012264891146079388821366740534204802954401251311822919615131047207289359704531102844802183906537786776\nr 730750818665451621361119245571504901405976559617\nexp2 159\nexp1 107\nsign1 1\nsign0 1\n"; pairing_t pairing; pairing_init_set_buf(pairing, param, strlen(param)); // 定义需要的元素 element_t g, x, y, Hm, r, M_prime, S_prime, r_inv, s, temp_left, temp_right; // 初始化元素所在的群 element_init_G2(g, pairing); element_init_G2(y, pairing); element_init_G1(Hm, pairing); element_init_G1(M_prime, pairing); element_init_G1(S_prime, pairing); element_init_G1(s, pairing); element_init_Zr(x, pairing); element_init_Zr(r, pairing); element_init_Zr(r_inv, pairing); element_init_GT(temp_left, pairing); element_init_GT(temp_right, pairing); cout << "Setup: keygen." << endl; element_random(g); // 随机生成元g element_random(x); // 私钥x element_pow_zn(y, g, x); // 公钥y = g ** x cout << "Interactive..." << endl; // (1) 模拟消息哈希到群G1上的点Hm element_random(Hm); // (2) 盲化 M' = Hm ** r element_random(r); element_pow_zn(M_prime, Hm, r); // (3) Signer sign message S' = (M') ** x element_pow_zn(S_prime, M_prime, x); // (4) User 去盲 s = (S') ** (r ** -1) element_invert(r_inv, r); // 计算r的逆元 element_pow_zn(s, S_prime, r_inv); // 得到真实签名 cout << "Verify..." << endl; // 验证e(s, g) == e(Hm, y) element_pairing(temp_left, s, g); element_pairing(temp_right, Hm, y); if (!element_cmp(temp_left, temp_right)) { cout << "验证成功,双线性配对等式成立。" << endl; } else { cout << "验证失败!" << endl; }
// free the memory element_clear(g); element_clear(x); element_clear(y); element_clear(Hm); element_clear(M_prime); element_clear(S_prime); element_clear(s); element_clear(r); element_clear(r_inv); element_clear(temp_left); element_clear(temp_right); pairing_clear(pairing);
return 0;}部分盲签名
普通的盲签名体制中,被签名的消息完全由用户控制,签名人对此一无所知,也不知道关于最终签名的任何信息,这有可能造成签名被非法使用等问题。基于盲签名潜在的问题。部分盲签名缓解了相关问题,在一个部分盲签名方案中,签名人可以在签名中嵌入一个和用户实现约定好的公共信息。比如,在电子货币系统中银行可以将有效期作为公共信息嵌入电子货币,有效期过后所有电子货币均失效。这里还是采用教材中的例子,以等人利用双线性配对设计的部分盲签名为例,介绍部分盲签名体制。与盲签名一样,一个部分盲签名体制也包括系统初始化算法、签名生成算法、签名验证算法。
协议流程
\begin{flalign} &1. Setup\\ &\quad 令q为一大素数,点P为q阶加法循环群G_1的生成元,G_2为同阶的乘法循环群,双线性映射e:G_1×G_1\rightarrow G_2,H:\{0,1\}^*\rightarrow \mathbb{Z}^*_q\\ &\quad 和H_0:\{0,1\}^*\rightarrow G_1为哈希函数,分别将任意比特串映射为\mathbb{Z}^*_q中的整数和G_1中的点。签名人Signer选择x\leftarrow _R\mathbb{Z}^*_q作为自己的私钥\\ &\quad 将P_{pub}\leftarrow xP公开作为自己的公钥.\\ &2.Sign\\ &\quad ①User与Signer约定一个公共信息c;\\ &\quad ②User选择一个随机数r,计算U\leftarrow H_0(m||c)+r(H(c)P+P_{pub}),其中"||"表示比特串级联,将U提交给签名人;\\ &\quad ③Signer利用自己的私钥x、公共信息c和接收到的U,计算V={1\over H(c)+x}U,将结果返回给User;\\ &\quad ④User计算S=V-rP,输出消息签名对(m||c,S)。\\ &3.Verify\\ &\quad 给定一个待验证的部分盲签名(m||c,S),验证者接受该签名当且仅当下式成立:\\ &\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad e(H(c)P+P_{pub},S)=e(P,H_0(m||c)).& \end{flalign}sequenceDiagram participant U as User participant S as Signer
U<<->>S: 交互/协商公共信息 c
Note over U: 选择 r ←ᵣ Z_q*<br/>计算 U = H₀(m||c) + r(H(c)P + P_pub)
U->>S: 发送 U
Note over S: 计算 V = (H(c) + x)⁻¹ U
S->>U: 返回 V
Note over U: 计算 S = V - rP正确性验证
\begin{flalign} &\quad e(H(c)P+P_{pub},S)\\ &=e((H(c)+x)P, V-rP)\\ &=e((H(c)+x)P, {1\over H(c) +x}U-rP)\\ &=e((H(c) +x)P,{H_0(m||c)+r(H(c)P+P_{pub})\over H(c)+x}-rP)\\ &=e((H(c)+x)P, {H_0(m||c)+r(H(c)P+P_{pub})\over H(c)+x})e((H(c)+x)P,-rP)\\ &=e(P,H_0(m||c)+r(H(c)P+P_{pub}))e(H(c)P+P_{pub},-rP)\\ &=e(P,H_0(m||c))e(P,r(H(c)P+P_{pub}))e(H(c)P+P_{pub},-rP)\\ &=e(P,H_0(m||c))& \end{flalign} “一方面在产生$U$的过程中,由于$User$使用了随机数$r$,所以由此计算出的$U$是群$G_1$中的一个随机元素,签名者$Signer$无法从$U$和公共信息$c$中得到关于消息$m4的任何信息。另一方面,给定一个有效的消息签名对$(m||c,S)$,用户也无法私自将公共信息改成对自己有利的其他数据。对此,签名者可以拥有足够的自信。这是因为,如果$User$能够将$(m||c,S)$改为$(m||c',S)$的话,则下面两个式子同时成立”即,但是由于与都是哈希函数,这是计算上不可能的。因此,可以确幸用户无法修改公共信息。
代码实现
#include <pbc/pbc.h>#include <iostream>#include <cstring>
using namespace std;
int main(){ // 1. Setup 初始化对称配对环境 (Type A) char param[1024] = "type a\nq 8780710799663312522437781984754049815806883199414208211028653399266475630880222957078625179422662221423155858769582317459277713367317481324925129998224791\nh 12016012264891146079388821366740534204802954401251311822919615131047207289359704531102844802183906537786776\nr 730750818665451621361119245571504901405976559617\nexp2 159\nexp1 107\nsign1 1\nsign0 1\n"; pairing_t pairing; pairing_init_set_buf(pairing, param, strlen(param));
// 定义变量 element_t P, x, P_pub; // 基础参数 element_t hc, H0_mc; // 哈希值 H(c) 和 H0(m||c) element_t r, U, V, S; // 盲化与签名变量 element_t temp_G1, temp_Zr, P_neg_r; // 临时计算变量 element_t left, right;
// 初始化群 element_init_G1(P, pairing); element_init_G1(P_pub, pairing); element_init_G1(H0_mc, pairing); element_init_G1(U, pairing); element_init_G1(V, pairing); element_init_G1(S, pairing); element_init_G1(temp_G1, pairing); element_init_G1(P_neg_r, pairing);
element_init_Zr(x, pairing); element_init_Zr(hc, pairing); element_init_Zr(r, pairing); element_init_Zr(temp_Zr, pairing);
element_init_GT(left, pairing); element_init_GT(right, pairing);
cout << "1. Setup & KeyGen" << endl; element_random(P); // 生成元 element_random(x); // sk element_pow_zn(P_pub, P, x); // pk
cout << "2. Sign..." << endl; // 模拟哈希函数输出 element_random(hc); // 模拟H(c) 输出Zr中的整数 element_random(H0_mc); // 模拟H0(m||c) 输出 G1 中的点 // User choose random r, compute U element_random(r); // conpute (P ** H(c)) * Ppub element_pow_zn(temp_G1, P, hc); element_mul(temp_G1, temp_G1, P_pub); // conpute temp_G1 ** r element_pow_zn(temp_G1, temp_G1, r); // U = H0(m||c) * temp_G1 element_mul(U, H0_mc, temp_G1); cout << "[User] send U to [Signer]..." << endl;
// Signer conpute V = U ** (1 / (hc + x)) element_add(temp_Zr, hc, x); // temp_Zr = hc + x element_invert(temp_Zr, temp_Zr); // temp_Zr = (hc + x) ^ -1 element_pow_zn(V, U, temp_Zr); // V = U ** temp_Zr cout << "[Signer] reply V to [User]..." << endl;
// User compute S = V * P ^ (-r) element_neg(temp_Zr, r); // temp_Zr = -r element_pow_zn(P_neg_r, P, temp_Zr); // P_neg_r = P ^ (-r) element_mul(S, V, P_neg_r); // S = V * P_neg_r cout << "[User] 完成去盲操作,输出签名S..." << endl;
cout << "3. Verify..." << endl; // Verify e(P ** hc * Ppub, S) == e(P, H0_mc) // 重新计算左侧配对的第一个参数 temp_G1 = P ** hc * Ppub element_pow_zn(temp_G1, P, hc); element_mul(temp_G1, temp_G1, P_pub);
element_pairing(left, temp_G1, S); element_pairing(right, P, H0_mc);
if (!element_cmp(left, right)) { cout << "Successfully Verify!" << endl; } else { cout << "Failed..." << endl; }
// Free the memory element_clear(P); element_clear(P_pub); element_clear(x); element_clear(H0_mc); element_clear(U); element_clear(V); element_clear(S); element_clear(temp_G1); element_clear(P_neg_r); element_clear(hc); element_clear(r); element_clear(temp_Zr); element_clear(left); element_clear(right); pairing_clear(pairing);
return 0;}写在最后
针对盲签名的学习,是第一次接触双线性配对,其实现原理比较复杂,但是对于上层调用而言并没有过多的难点。这里实现的过程中我采用的是PBC库进行的上层调用,目前应该是存在有更好用的一些库,这里并没有进行深究。其中对于代码实现中的一个问题就是,对于椭圆曲线中点的运算在映射回整数域的时候,其加法运算以及乘法运算会映射到乘法以及幂运算上。因此在阅读代码的时候,如果发现协议流程与Code存在出入的话是正常的,后续没有使用Python去进行编程实现很大一部分原因是我的计算机上刚好存在PBC库,也不想花更多的经历去在sagemath中去配置环境了,所以存在前面所说的出入很大部分原因是PBC库有点老了的原因。😀😀
最后在这里写一下我对于盲签名的认知吧,其实对于所有的高级签名协议而言,我认为都是需求在驱动着研究。拿部分盲签名举个例子,我有一张钞票,如果我要花出去,商户需要确定这是一张经过银行认可的电子钞票,故其会接受,换句话说,认证成功才能实现电子钞票的最基本的功能。我既然需要银行来签名,就需要把钞票呈现给银行,如果没有盲签名,那么对于银行而言,他就可以跟踪用户“我”的开销,我这张钞票在哪里花了?买了什么?这属于用户的隐私,为了保证电子钞票的钞票职能以及用户的隐私,盲签名就可以完美解决这一问题,我用纸质的钞票去兑换电子钞票,此后这张钞票我如何花就不会被银行跟踪监测。
后续有时间会把群签名、环签名等高级签名协议也进行总结和编程复现验证一下。