ceremonyclient/nekryptology/pkg/signatures/bbs/blind_signature_context.go
2023-07-05 00:32:28 -05:00

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
}