mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-26 08:35:17 +00:00
256 lines
8.1 KiB
Go
256 lines
8.1 KiB
Go
//
|
|
// Copyright Coinbase, Inc. All Rights Reserved.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package bbs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/gtank/merlin"
|
|
"golang.org/x/crypto/sha3"
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
|
|
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
|
|
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/signatures/common"
|
|
)
|
|
|
|
// BlindSignatureContext contains the data used for computing
|
|
// a blind signature and verifying proof of hidden messages from
|
|
// a future signature holder. A potential holder commits to messages
|
|
// that the signer will not know during the signing process
|
|
// rendering them hidden, but requires the holder to
|
|
// prove knowledge of those messages so a malicious party
|
|
// doesn't add just random data from anywhere.
|
|
type BlindSignatureContext struct {
|
|
// The blinded signature commitment
|
|
commitment common.Commitment
|
|
// The challenge hash for the Fiat-Shamir heuristic
|
|
challenge curves.Scalar
|
|
// The proofs of hidden messages.
|
|
proofs []curves.Scalar
|
|
}
|
|
|
|
// NewBlindSignatureContext creates the data needed to
|
|
// send to a signer to complete a blinded signature
|
|
// `msgs` is an index to message map where the index
|
|
// corresponds to the index in `generators`
|
|
// msgs are hidden from the signer but can also be empty
|
|
// if no messages are hidden but a blind signature is still desired
|
|
// because the signer should have no knowledge of the signature
|
|
func NewBlindSignatureContext(curve *curves.PairingCurve, msgs map[int]curves.Scalar, generators *MessageGenerators, nonce common.Nonce, reader io.Reader) (*BlindSignatureContext, common.SignatureBlinding, error) {
|
|
if curve == nil || generators == nil || nonce == nil || reader == nil {
|
|
return nil, nil, internal.ErrNilArguments
|
|
}
|
|
points := make([]curves.Point, 0)
|
|
secrets := make([]curves.Scalar, 0)
|
|
|
|
committing := common.NewProofCommittedBuilder(&curves.Curve{
|
|
Scalar: curve.Scalar,
|
|
Point: curve.Scalar.Point().(curves.PairingPoint).OtherGroup(),
|
|
Name: curve.Name,
|
|
})
|
|
|
|
// C = h0^blinding_factor*h_i^m_i.....
|
|
for i, m := range msgs {
|
|
if i > generators.length || i < 0 {
|
|
return nil, nil, fmt.Errorf("invalid index")
|
|
}
|
|
secrets = append(secrets, m)
|
|
pt := generators.Get(i + 1)
|
|
points = append(points, pt)
|
|
err := committing.CommitRandom(pt, reader)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
blinding, ok := curve.Scalar.Random(reader).(common.SignatureBlinding)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("unable to create signature blinding")
|
|
}
|
|
secrets = append(secrets, blinding)
|
|
|
|
h0 := generators.Get(0)
|
|
points = append(points, h0)
|
|
err := committing.CommitRandom(h0, reader)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Create a random commitment, compute challenges and response.
|
|
// The proof of knowledge consists of a commitment and responses
|
|
// Holder and signer engage in a proof of knowledge for `commitment`
|
|
commitment := curve.Scalar.Point().(curves.PairingPoint).OtherGroup().SumOfProducts(points, secrets)
|
|
transcript := merlin.NewTranscript("new blind signature")
|
|
transcript.AppendMessage([]byte("random commitment"), committing.GetChallengeContribution())
|
|
transcript.AppendMessage([]byte("blind commitment"), commitment.ToAffineCompressed())
|
|
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
|
okm := transcript.ExtractBytes([]byte("blind signature context challenge"), 64)
|
|
challenge, err := curve.Scalar.SetBytesWide(okm)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
proofs, err := committing.GenerateProof(challenge, secrets)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &BlindSignatureContext{
|
|
commitment: commitment,
|
|
challenge: challenge,
|
|
proofs: proofs,
|
|
}, blinding, nil
|
|
}
|
|
|
|
func (bsc *BlindSignatureContext) Init(curve *curves.PairingCurve) *BlindSignatureContext {
|
|
bsc.challenge = curve.NewScalar()
|
|
bsc.commitment = curve.Scalar.Point().(curves.PairingPoint).OtherGroup()
|
|
bsc.proofs = make([]curves.Scalar, 0)
|
|
return bsc
|
|
}
|
|
|
|
// MarshalBinary store the generators as a sequence of bytes
|
|
// where each point is compressed.
|
|
// Needs (N + 1) * ScalarSize + PointSize
|
|
func (bsc BlindSignatureContext) MarshalBinary() ([]byte, error) {
|
|
buffer := append(bsc.commitment.ToAffineCompressed(), bsc.challenge.Bytes()...)
|
|
for _, p := range bsc.proofs {
|
|
buffer = append(buffer, p.Bytes()...)
|
|
}
|
|
return buffer, nil
|
|
}
|
|
|
|
func (bsc *BlindSignatureContext) UnmarshalBinary(in []byte) error {
|
|
scSize := len(bsc.challenge.Bytes())
|
|
ptSize := len(bsc.commitment.ToAffineCompressed())
|
|
if len(in) < scSize*2+ptSize {
|
|
return fmt.Errorf("insufficient number of bytes")
|
|
}
|
|
|
|
if (len(in)-ptSize)%scSize != 0 {
|
|
return fmt.Errorf("invalid byte sequence")
|
|
}
|
|
commitment, err := bsc.commitment.FromAffineCompressed(in[:ptSize])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
challenge, err := bsc.challenge.SetBytes(in[ptSize:(ptSize + scSize)])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nProofs := ((len(in) - ptSize) / scSize) - 1
|
|
proofs := make([]curves.Scalar, nProofs)
|
|
for i := 0; i < nProofs; i++ {
|
|
proofs[i], err = bsc.challenge.SetBytes(in[(ptSize + (i+1)*scSize):(ptSize + (i+2)*scSize)])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
bsc.commitment = commitment
|
|
bsc.challenge = challenge
|
|
bsc.proofs = proofs
|
|
return nil
|
|
}
|
|
|
|
// Verify validates a proof of hidden messages
|
|
func (bsc BlindSignatureContext) Verify(knownMsgs []int, generators *MessageGenerators, nonce common.Nonce) error {
|
|
known := make(map[int]bool, len(knownMsgs))
|
|
for _, i := range knownMsgs {
|
|
if i > generators.length {
|
|
return fmt.Errorf("invalid message index")
|
|
}
|
|
known[i] = true
|
|
}
|
|
|
|
points := make([]curves.Point, 0)
|
|
for i := 0; i < generators.length; i++ {
|
|
if _, contains := known[i]; !contains {
|
|
points = append(points, generators.Get(i+1))
|
|
}
|
|
}
|
|
points = append(points, generators.Get(0), bsc.commitment)
|
|
scalars := append(bsc.proofs, bsc.challenge.Neg())
|
|
|
|
commitment := points[0].SumOfProducts(points, scalars)
|
|
transcript := merlin.NewTranscript("new blind signature")
|
|
transcript.AppendMessage([]byte("random commitment"), commitment.ToAffineCompressed())
|
|
transcript.AppendMessage([]byte("blind commitment"), bsc.commitment.ToAffineCompressed())
|
|
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
|
okm := transcript.ExtractBytes([]byte("blind signature context challenge"), 64)
|
|
challenge, err := bsc.challenge.SetBytesWide(okm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if challenge.Cmp(bsc.challenge) != 0 {
|
|
return fmt.Errorf("invalid proof")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ToBlindSignature converts a blind signature where
|
|
// msgs are known to the signer
|
|
// `msgs` is an index to message map where the index
|
|
// corresponds to the index in `generators`
|
|
func (bsc BlindSignatureContext) ToBlindSignature(msgs map[int]curves.Scalar, sk *SecretKey, generators *MessageGenerators, nonce common.Nonce) (*BlindSignature, error) {
|
|
if sk == nil || generators == nil || nonce == nil {
|
|
return nil, internal.ErrNilArguments
|
|
}
|
|
if sk.value.IsZero() {
|
|
return nil, fmt.Errorf("invalid secret key")
|
|
}
|
|
tv1 := make([]int, 0, len(msgs))
|
|
for i := range msgs {
|
|
if i > generators.length {
|
|
return nil, fmt.Errorf("not enough message generators")
|
|
}
|
|
tv1 = append(tv1, i)
|
|
}
|
|
sort.Ints(tv1)
|
|
signingMsgs := make([]curves.Scalar, len(msgs))
|
|
for i, index := range tv1 {
|
|
signingMsgs[i] = msgs[index]
|
|
}
|
|
err := bsc.Verify(tv1, generators, nonce)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
drbg := sha3.NewShake256()
|
|
_, _ = drbg.Write(sk.value.Bytes())
|
|
addDeterministicNonceData(generators, signingMsgs, drbg)
|
|
// Should yield non-zero values for `e` and `s`, very small likelihood of being zero
|
|
e := getNonZeroScalar(sk.value, drbg)
|
|
s := getNonZeroScalar(sk.value, drbg)
|
|
|
|
exp, err := e.Add(sk.value).Invert()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// B = g1+h_0^s+h_1^m_1...
|
|
points := make([]curves.Point, len(msgs)+3)
|
|
scalars := make([]curves.Scalar, len(msgs)+3)
|
|
points[0] = bsc.commitment
|
|
points[1] = bsc.commitment.Generator()
|
|
points[2] = generators.Get(0)
|
|
scalars[0] = sk.value.One()
|
|
scalars[1] = sk.value.One()
|
|
scalars[2] = s
|
|
i := 3
|
|
for idx, m := range msgs {
|
|
points[i] = generators.Get(idx + 1)
|
|
scalars[i] = m
|
|
i++
|
|
}
|
|
b := bsc.commitment.SumOfProducts(points, scalars)
|
|
return &BlindSignature{
|
|
a: b.Mul(exp).(curves.PairingPoint),
|
|
e: e,
|
|
s: s,
|
|
}, nil
|
|
}
|