// // 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 }