Skip to main content

ed25519 step1,2,3

code

func sign_p1_p3(p1Data, p3Data *tss.KeyStep3Data, publicKey *edwards.PublicKey, message []byte) {
fmt.Println("=========sign_p1_p3========")
partList := []int{1, 3}
p1 := NewEd25519Sign(1, 2, partList, p1Data.ShareI, publicKey, hex.EncodeToString(message))
p3 := NewEd25519Sign(3, 2, partList, p3Data.ShareI, publicKey, hex.EncodeToString(message))

p1Step1, _ := p1.SignStep1()
p3Step1, _ := p3.SignStep1()

p1Step2, _ := p1.SignStep2([]*tss.Message{p3Step1[1]})
p3Step2, _ := p3.SignStep2([]*tss.Message{p1Step1[3]})

si_1, r, _ := p1.SignStep3([]*tss.Message{p3Step2[1]})
fmt.Println("si_1: ", si_1, r)
si_3, r, _ := p3.SignStep3([]*tss.Message{p1Step2[3]})
fmt.Println("si_3: ", si_3, r)

s := new(big.Int).Add(si_1, si_3)
// outputs the signature
signature := edwards.NewSignature(r, s)
ret := signature.Verify(message, publicKey)
fmt.Println("Verify: ", ret)
}

在门限签名(Threshold Signature)协议中,每个参与者都需要执行所有的步骤(step1, step2, step3)以确保整个签名过程的安全性和正确性。以下是每个步骤的详细解释,以及为什么每个参与者都需要执行这些步骤:

Step 1: 生成并广播承诺(Commitment)

每个参与者生成一个随机数ki并计算其相应的点Ri,然后生成并广播一个承诺(commitment)。这一过程确保了每个参与者的随机数ki在后续步骤中不会被篡改。

Step 2: 接收并验证承诺,生成零知识证明(zk-SNARK)

每个参与者接收其他参与者的承诺,并验证这些承诺的正确性。同时,每个参与者生成自己的零知识证明,证明其ki的正确性。这个步骤确保了所有参与者的ki都是有效的,并且没有被篡改。

Step 3: 计算签名

每个参与者接收并验证其他参与者的零知识证明,计算最终的签名siR。这个步骤是门限签名协议的核心,确保了所有参与者共同生成一个有效的签名。

具体到你的代码

在你的代码中,P1 和 P3 都执行了 step1, step2, step3,这是因为:

  1. Step 1:P1 和 P3 各自生成并广播自己的承诺。
  2. Step 2:P1 和 P3 接收并验证对方的承诺,并生成自己的零知识证明。
  3. Step 3:P1 和 P3 接收并验证对方的零知识证明,计算最终的签名。

通过这种方式,P1 和 P3 共同生成了一个有效的签名。每个参与者都执行所有步骤的原因是为了确保整个过程的正确性和安全性。每个步骤都是必不可少的,缺少任何一个步骤都会导致签名过程的失败或不安全。

流程图

  1. P1 和 P3 执行 Step 1

    • P1 生成k1并计算R1,生成承诺并广播。
    • P3 生成k3并计算R3,生成承诺并广播。
  2. P1 和 P3 执行 Step 2

    • P1 接收并验证 P3 的承诺,生成零知识证明并广播。
    • P3 接收并验证 P1 的承诺,生成零知识证明并广播。
  3. P1 和 P3 执行 Step 3

    • P1 接收并验证 P3 的零知识证明,计算si_1R
    • P3 接收并验证 P1 的零知识证明,计算si_3R

最终,P1 和 P3 各自计算出部分签名si_1si_3,并将它们相加得到完整的签名。

为什么每个参与者都要执行所有步骤?

  • 安全性:每个步骤都有其独特的安全性保障,确保没有参与者能够欺骗或篡改签名过程。
  • 正确性:每个步骤都确保所有参与者的输入和输出都是正确的,最终生成的签名是有效的。

因此,在门限签名协议中,每个参与者都需要执行所有步骤,以确保整个签名过程的安全性和正确性。

顺序无关

在门限签名(Threshold Signature)协议中,si_1si_3等部分签名的顺序通常是没有严格要求的,因为最终的签名是通过将这些部分签名相加得到的。然而,确保所有部分签名被正确收集和处理仍然是至关重要的。以下是一些详细的解释和注意事项:

数学原理

门限签名协议的核心思想是通过将多个参与者的部分签名相加,生成一个完整的签名。假设有两个参与者 P1 和 P3,各自生成部分签名si_1si_3,最终的签名S是通过相加这些部分签名得到的:

S = si_1 + si_3

由于加法的交换性和结合性,si_1si_3的顺序不影响最终的结果:

si_1 + si_3 = si_3 + si_1

实际应用中的注意事项

尽管数学上顺序无关紧要,但在实际应用中,仍需注意以下几点:

  1. 数据完整性:确保所有部分签名都被正确收集和处理。任何一个部分签名的丢失或错误都会导致最终签名的失败。
  2. 数据验证:在收集部分签名时,可以对每个部分签名进行验证,确保其有效性。这可以通过零知识证明等方法实现。
  3. 并发处理:如果部分签名是并发生成和传输的,需要确保在最终汇总时所有部分签名都已到达。

示例代码

以下是一个示例代码,展示如何汇总部分签名并生成最终签名:

package main

import (
"crypto/ed25519"
"crypto/rand"
"fmt"
"math/big"
)

func main() {
// 假设已经生成了ed25519公钥和私钥对
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)

// 假设消息是"Hello, world!"
message := []byte("Hello, world!")

// P1和P3的部分签名
si_1 := new(big.Int).SetBytes(ed25519.Sign(privateKey, message))
si_3 := new(big.Int).SetBytes(ed25519.Sign(privateKey, message))

// 汇总部分签名
finalSignature := new(big.Int).Add(si_1, si_3)

// 生成最终签名
signature := append(finalSignature.Bytes(), publicKey...)

// 验证签名
valid := ed25519.Verify(publicKey, message, signature[:ed25519.SignatureSize])
fmt.Println("Signature valid:", valid)
}

总结

  • 顺序无关si_1si_3的顺序在数学上无关紧要,因为加法具有交换性。
  • 数据完整性和验证:确保所有部分签名都被正确收集和验证。
  • 并发处理:在并发环境下,确保所有部分签名都已到达并被正确处理。

通过以上方法,可以确保在实际应用中正确汇总部分签名并生成最终签名。

签名片段收集方式

在正式的应用中,如何将各个参与者生成的部分签名(如si_1si_3)汇集到一个客户端上,取决于具体的应用场景和安全需求。以下是几种常见的方法:

1. 网络接口调用(API)

各个参与者通过网络接口(API)将部分签名发送到一个集中处理的服务器或客户端。这种方法适合于网络环境下的应用,具有较高的自动化程度。

优点:

  • 自动化程度高,适合大规模应用。
  • 可以实现实时性和高效性。

缺点:

  • 需要处理网络安全问题,如数据传输加密和身份验证。

示例:

// 假设有一个API接口用于接收部分签名
func submitPartialSignature(partialSignature *big.Int, participantID int) error {
// 将部分签名提交到服务器
// 例如通过HTTP POST请求
// ...
return nil
}

// P1和P3生成部分签名后,调用API接口提交
submitPartialSignature(si_1, 1)
submitPartialSignature(si_3, 3)

2. 扫码(QR Code)

各个参与者生成部分签名后,通过生成二维码(QR Code)展示出来,另一个客户端可以扫码获取部分签名。这种方法适合于物理环境下的应用,如多方签名钱包。

优点:

  • 安全性较高,适合于物理隔离的环境。
  • 适合于没有网络连接的场景。

缺点:

  • 自动化程度低,依赖于人工操作。
  • 可能不适合大规模应用。

示例:

// 生成QR Code展示部分签名
func generateQRCode(data string) error {
// 使用QR Code库生成二维码
// ...
return nil
}

// P1和P3生成部分签名后,生成二维码展示
generateQRCode(si_1.String())
generateQRCode(si_3.String())

3. 离线文件传输

各个参与者生成部分签名后,通过离线文件(如 USB 驱动器、SD 卡)将部分签名传输到一个集中处理的客户端。这种方法适合于高度安全的环境,如不允许网络连接的场景。

优点:

  • 安全性最高,适合于高度敏感的环境。
  • 不依赖于网络连接。

缺点:

  • 自动化程度低,依赖于人工操作。
  • 传输速度较慢,可能不适合实时应用。

示例:

// 将部分签名保存到文件
func saveToFile(partialSignature *big.Int, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(partialSignature.String())
return err
}

// P1和P3生成部分签名后,保存到文件
saveToFile(si_1, "si_1.txt")
saveToFile(si_3, "si_3.txt")

选择方法的考虑因素

  1. 安全性:确保部分签名在传输过程中不会被篡改或窃取。
  2. 实时性:根据应用需求选择是否需要实时传输部分签名。
  3. 自动化程度:考虑应用的规模和操作的便利性。
  4. 环境约束:根据具体应用环境(如是否有网络连接)选择合适的方法。

结论

在实际应用中,选择适合的方法将各个参与者生成的部分签名汇集到一个客户端上,需要综合考虑安全性、实时性、自动化程度和环境约束等因素。对于高安全性要求的应用,可能需要结合多种方法以确保安全和可靠性。

每个参与者之间计算一遍

func sign_p1_p3(p1Data, p3Data *tss.KeyStep3Data, publicKey *edwards.PublicKey, message []byte) {
fmt.Println("=========sign_p1_p3========")
partList := []int{1, 3}
p1 := NewEd25519Sign(1, 2, partList, p1Data.ShareI, publicKey, hex.EncodeToString(message))
p3 := NewEd25519Sign(3, 2, partList, p3Data.ShareI, publicKey, hex.EncodeToString(message))

p1Step1, _ := p1.SignStep1()
p3Step1, _ := p3.SignStep1()

p1Step2, _ := p1.SignStep2([]*tss.Message{p3Step1[1]})
p3Step2, _ := p3.SignStep2([]*tss.Message{p1Step1[3]})

si_1, r, _ := p1.SignStep3([]*tss.Message{p3Step2[1]})
fmt.Println("si_1: ", si_1, r)
si_3, r, _ := p3.SignStep3([]*tss.Message{p1Step2[3]})
fmt.Println("si_3: ", si_3, r)

s := new(big.Int).Add(si_1, si_3)
// outputs the signature
signature := edwards.NewSignature(r, s)
ret := signature.Verify(message, publicKey)
fmt.Println("Verify: ", ret)
}
看这个测试用例中P1和P3都执行了 step1, step2,step3,为什么都要执行一遍?

在门限签名(Threshold Signature)协议中,每个参与者都需要执行所有的步骤(step1, step2, step3)以确保整个签名过程的安全性和正确性。以下是每个步骤的详细解释,以及为什么每个参与者都需要执行这些步骤:

Step 1: 生成并广播承诺(Commitment)

每个参与者生成一个随机数ki并计算其相应的点Ri,然后生成并广播一个承诺(commitment)。这一过程确保了每个参与者的随机数ki在后续步骤中不会被篡改。

Step 2: 接收并验证承诺,生成零知识证明(zk-SNARK)

每个参与者接收其他参与者的承诺,并验证这些承诺的正确性。同时,每个参与者生成自己的零知识证明,证明其ki的正确性。这个步骤确保了所有参与者的ki都是有效的,并且没有被篡改。

Step 3: 计算签名

每个参与者接收并验证其他参与者的零知识证明,计算最终的签名siR。这个步骤是门限签名协议的核心,确保了所有参与者共同生成一个有效的签名。

具体到你的代码

在你的代码中,P1 和 P3 都执行了 step1, step2, step3,这是因为:

  1. Step 1:P1 和 P3 各自生成并广播自己的承诺。
  2. Step 2:P1 和 P3 接收并验证对方的承诺,并生成自己的零知识证明。
  3. Step 3:P1 和 P3 接收并验证对方的零知识证明,计算最终的签名。

通过这种方式,P1 和 P3 共同生成了一个有效的签名。每个参与者都执行所有步骤的原因是为了确保整个过程的正确性和安全性。每个步骤都是必不可少的,缺少任何一个步骤都会导致签名过程的失败或不安全。

流程图

  1. P1 和 P3 执行 Step 1

    • P1 生成k1并计算R1,生成承诺并广播。
    • P3 生成k3并计算R3,生成承诺并广播。
  2. P1 和 P3 执行 Step 2

    • P1 接收并验证 P3 的承诺,生成零知识证明并广播。
    • P3 接收并验证 P1 的承诺,生成零知识证明并广播。
  3. P1 和 P3 执行 Step 3

    • P1 接收并验证 P3 的零知识证明,计算si_1R
    • P3 接收并验证 P1 的零知识证明,计算si_3R

最终,P1 和 P3 各自计算出部分签名si_1si_3,并将它们相加得到完整的签名。

为什么每个参与者都要执行所有步骤?

  • 安全性:每个步骤都有其独特的安全性保障,确保没有参与者能够欺骗或篡改签名过程。
  • 正确性:每个步骤都确保所有参与者的输入和输出都是正确的,最终生成的签名是有效的。

因此,在门限签名协议中,每个参与者都需要执行所有步骤,以确保整个签名过程的安全性和正确性。

step3 验证对方的零知识证明,怎么会计算出签名呢,理论依据是什么

在门限签名(Threshold Signature)协议中,Step 3 的核心任务是将所有参与者的部分签名(partial signatures)组合成一个完整的签名。这个步骤的理论依据是基于多方计算(MPC)和零知识证明(ZKP)的组合,确保每个参与者的输入都是有效的,并且最终生成的签名是正确的。

理论依据

  1. 零知识证明(ZKP)

    • 每个参与者在 Step 2 中生成零知识证明,证明其部分签名的有效性。
    • 在 Step 3 中,其他参与者验证这些零知识证明,确保没有参与者作弊。
  2. 多方计算(MPC)

    • 门限签名协议利用多方计算技术,确保多个参与者可以共同计算一个签名,而无需任何单个参与者拥有完整的签名密钥。
  3. 线性组合

    • 门限签名协议中的部分签名是线性可组合的,这意味着可以将多个部分签名相加,得到完整的签名。

数学基础

在 Ed25519 门限签名中,签名由两个部分组成:Rs

  • R 是所有参与者的随机数 ki 生成的点 Ri 的和。
  • s 是所有参与者的部分签名 si 的和。

签名公式如下:

s=k+hx s = k + h \cdot x

其中,k 是随机数,h 是哈希值,x 是私钥。

在门限签名中,每个参与者有自己的部分签名 si

si=ki+hwi si = ki + h \cdot wi

其中,wi 是参与者的部分私钥。

Step 3 的具体操作

  1. 验证零知识证明

    • 每个参与者接收并验证其他参与者的零知识证明,确保其部分签名 si 的有效性。
    • 验证通过后,计算出对方的 Ri
  2. 计算签名 R

    • 将所有参与者的 Ri 相加,得到签名的第一部分 R
  3. 计算部分签名 si

    • 每个参与者计算自己的部分签名 si
    • 将所有部分签名 si 相加,得到签名的第二部分 s
  4. 组合签名

    • 最终签名由 Rs 组成。

代码解析

在你的代码中,SignStep3 方法的具体实现如下:

func (ed25519 *Ed25519Sign) SignStep3(msgs []*tss.Message) (*big.Int, *big.Int, error) {
if ed25519.RoundNumber != 3 {
return nil, nil, fmt.Errorf("round error")
}
ed25519.RoundNumber = -1
if len(msgs) != (ed25519.Threshold - 1) {
return nil, nil, fmt.Errorf("messages number error")
}
// R = sum(Ri)
R := curves.ScalarToPoint(curve, ed25519.ki)
for _, msg := range msgs {
if msg.To != ed25519.DeviceNumber {
return nil, nil, fmt.Errorf("message sending error")
}
var data Step2Data
err := json.Unmarshal([]byte(msg.Data), &data)
if err != nil {
return nil, nil, err
}
// check Ri commitment
commit := commitment.HashCommitment{}
commit.C = ed25519.CommitmentMap[msg.From]
commit.Msg = data.Witness
ok, DeC := commit.Open()
if !ok {
return nil, nil, fmt.Errorf("commitment DeCommit fail")
}
Rj, err := curves.NewECPoint(curve, DeC[0], DeC[1])
if err != nil {
return nil, nil, err
}
// ki schnorr verify, Rj = kj*G
verify := schnorr.Verify(data.Proof, Rj)
if !verify {
return nil, nil, fmt.Errorf("schnorr verify fail")
}
R, err = R.Add(Rj)
if err != nil {
return nil, nil, err
}
}
RR := edwards.NewPublicKey(R.X, R.Y)

bytes, err := hex.DecodeString(ed25519.message)
if err != nil {
return nil, nil, err
}
// h = hash512(R || Pub || M)
h := sha512.New()
h.Reset()
h.Write(RR.Serialize())
h.Write(ed25519.PublicKey.Serialize())
h.Write(bytes)

var lambda [64]byte
h.Sum(lambda[:0])
var lambdaReduced [32]byte
edwards25519.ScReduce(&lambdaReduced, &lambda)

xBytes := bigIntToEncodedBytes(ed25519.wi)
rBytes := bigIntToEncodedBytes(ed25519.ki)

// si = ri + h * xi
var sBytes [32]byte
edwards25519.ScMulAdd(&sBytes, &lambdaReduced, xBytes, rBytes)
si := encodedBytesToBigInt(&sBytes)
var RBytes = copyBytes(RR.Serialize())
r := encodedBytesToBigInt(RBytes)

return si, r, nil
}

总结

Step 3 的主要任务是验证其他参与者的零知识证明,并计算出最终的签名。这一步骤确保了所有参与者的部分签名 si 的有效性,并将这些部分签名相加,得到完整的签名 s。整个过程基于零知识证明和多方计算技术,确保签名过程的安全性和正确性。

对完整签名验签

对生成的完整签名进行验证(验签)是确保消息的真实性和完整性的重要步骤。验签过程通常涉及以下几个步骤:计算消息的哈希值、使用公钥验证签名是否有效。

验签的基本步骤

  1. 计算消息的哈希值: 使用相同的哈希函数计算消息的哈希值。

  2. 验证签名: 使用公钥和签名来验证消息的哈希值是否匹配。如果匹配,签名验证通过,消息被认为是有效的。

Ed25519 验签过程

以下是一个基于 Ed25519 算法的验签过程的示例代码:

package main

import (
"crypto/sha512"
"fmt"
"github.com/oasisprotocol/curve25519-voi/curve"
"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
)

// Signature 结构体
type Signature struct {
R *curve.EdwardsPoint
S [32]byte
}

// Verify 方法用于签名验证
func (sig *Signature) Verify(message []byte, pubKey *curve.EdwardsPoint) bool {
// 计算消息的哈希值
h := sha512.New()
h.Write(sig.R.Bytes())
h.Write(pubKey.Bytes())
h.Write(message)
var lambda [64]byte
h.Sum(lambda[:0])
var lambdaReduced [32]byte
ed25519.ScReduce(&lambdaReduced, &lambda)

// 验证签名
var R curve.EdwardsPoint
ed25519.GeDoubleScalarMultVartime(&R, &lambdaReduced, pubKey, &sig.S)
return R.Equal(sig.R)
}

func main() {
// 假设我们有一个消息、签名和公钥
message := []byte("Hello, world!")
publicKey := // 从某处获取公钥
signature := &Signature{
R: // 从某处获取 R 部分
S: // 从某处获取 S 部分
}

// 验证签名
isValid := signature.Verify(message, publicKey)
fmt.Println("签名验证结果: ", isValid)
}

详细解释

  1. 计算消息的哈希值: 使用 SHA-512 哈希函数计算消息的哈希值,并将结果缩减为 32 字节(ScReduce)。

  2. 验证签名: 使用公钥 pubKey 和签名的两个部分 RS 来验证消息的哈希值是否匹配。

  3. 双标量乘法: 在 Ed25519 中,双标量乘法 GeDoubleScalarMultVartime 用于验证签名。它计算一个新的点 R 并与签名中的 R 进行比较。

注意事项

  • 公钥和签名格式:确保公钥和签名的格式正确。
  • 哈希函数一致性:使用与签名生成时相同的哈希函数。
  • 库的选择:根据具体需求选择合适的密码学库,确保其安全性和性能。

总结

验签过程是确保消息真实性和完整性的重要步骤。通过计算消息的哈希值并使用公钥验证签名,可以确保消息确实由声明的发送方签署,并且在传输过程中没有被篡改。上述代码示例展示了如何在 Go 语言中使用 Ed25519 算法进行签名验证。