Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions ecc/bls12381/ff/scalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"io"

"github.com/cloudflare/circl/internal/conv"
"golang.org/x/crypto/cryptobyte"
)

// ScalarSize is the length in bytes of a Scalar.
Expand Down Expand Up @@ -64,6 +65,31 @@ func (z *Scalar) SetBytes(data []byte) {
z.toMont(s)
}

func (z *Scalar) Marshal(b *cryptobyte.Builder) error {
x := z.fromMont()
for i := len(x) - 1; i >= 0; i-- {
b.AddUint64(x[i])
}

return nil
}

func (z *Scalar) Unmarshal(s *cryptobyte.String) bool {
var b [ScalarSize]byte
ok := s.CopyBytes(b[:])
if !ok {
return false
}

in64, err := setBytesBounded(b[:], scOrder[:])
if err != nil {
return false
}

z.toMont((*scRaw)(in64[:ScalarSize/8]))
return true
}

// MarshalBinary returns a slice of ScalarSize bytes that contains the minimal
// residue of z such that 0 <= z < ScalarOrder (in big-endian order).
func (z *Scalar) MarshalBinary() ([]byte, error) {
Expand Down
8 changes: 7 additions & 1 deletion ecc/bls12381/g1.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,14 @@ func (g *G1) Encode(input, dst []byte) {
// an optional domain separation tag. This function is safe to use when a
// random oracle returning points in G1 be required.
func (g *G1) Hash(input, dst []byte) {
g.HashWithExpander(expander.NewExpanderMD(crypto.SHA256, dst), input)
}

// HashWithExpander is similar to [G1.Hash] but allows to specify an
// expander created from a hash function or an extendable-output function.
func (g *G1) HashWithExpander(exp expander.Expander, input []byte) {
const L = 64
pseudo := expander.NewExpanderMD(crypto.SHA256, dst).Expand(input, 2*L)
pseudo := exp.Expand(input, 2*L)

var u0, u1 ff.Fp
u0.SetBytes(pseudo[0*L : 1*L])
Expand Down
84 changes: 84 additions & 0 deletions sign/bbs/bbs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package bbs

import (
"errors"
"slices"
)

const (
PublicKeySize = 96 // Size in bytes of public keys.
PrivateKeySize = 32 // Size in bytes of private keys.
SignatureSize = 80 // Size in bytes of signatures.
KeyMaterialMinSize = 32 // Minimum size in bytes of private key material.
)

// [Msg] is a byte slice marked either as [Disclosed] or [Concealed].
type Msg interface{ get() []byte }

// Disclosed marks a message as disclosed. Implements the [Msg] interface.
type Disclosed []byte

func (b Disclosed) get() []byte { return b }

// Concealed marks a message as concealed. Implements the [Msg] interface.
type Concealed []byte

func (b Concealed) get() []byte { return b }

// Disclose returns a list of messages specifying the messages to be disclosed,
// and the others are concealed.
// Indexes must be unique and lesser than len(messages),
// otherwise returns an error.
func Disclose(messages [][]byte, disclosed []uint) ([]Msg, error) {
return choose[Disclosed, Concealed](messages, disclosed)
}

// Conceal returns a list of messages specifying the messages to be concealed,
// and the others are disclosed.
// Indexes must be unique and lesser than len(messages),
// otherwise returns an error.
func Conceal(messages [][]byte, concealed []uint) ([]Msg, error) {
return choose[Concealed, Disclosed](messages, concealed)
}

func choose[
This, Other interface {
~[]byte
Msg
},
](msgs [][]byte, indexes []uint) ([]Msg, error) {
indexesNoDup := slices.Clone(indexes)
slices.Sort(indexesNoDup)
indexesNoDup = slices.Compact(indexesNoDup)
l := len(indexesNoDup)
// check for duplicates.
if l != len(indexes) {
return nil, ErrIndexes
}

// check for out-of-range indexes.
if l > 0 && indexesNoDup[l-1] >= uint(len(msgs)) {
return nil, ErrIndexes
}

choices := make([]Msg, len(msgs))
for i := range msgs {
choices[i] = Other(msgs[i])
}

for _, idx := range indexesNoDup {
choices[idx] = This(msgs[idx])
}

return choices, nil
}

var (
ErrInvalidSuiteID = errors.New("bbs: invalid suite identifier")
ErrKeyMaterial = errors.New("bbs: invalid keyMaterial size")
ErrKeyInfo = errors.New("bbs: invalid keyGen keyInfo")
ErrInvalidOpts = errors.New("bbs: invalid options")
ErrIndexes = errors.New("bbs: invalid indexes")
ErrSignature = errors.New("bbs: invalid signature")
ErrGenerators = errors.New("bbs: invalid generators")
)
136 changes: 136 additions & 0 deletions sign/bbs/bbs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package bbs_test

import (
"crypto/rand"
"testing"

"github.com/cloudflare/circl/ecc/bls12381"
"github.com/cloudflare/circl/internal/test"
"github.com/cloudflare/circl/sign/bbs"
)

func TestConstants(t *testing.T) {
test.CheckOk(
bbs.PublicKeySize == bls12381.G2SizeCompressed,
"wrong PublicKeySize", t)
test.CheckOk(
bbs.PrivateKeySize == bls12381.ScalarSize,
"wrong PrivateKeySize", t)
test.CheckOk(
bbs.SignatureSize == bls12381.G1SizeCompressed+bls12381.ScalarSize,
"wrong SignatureSize", t)
}

func TestBBS(t *testing.T) {
t.Run("BLS12381Shake256", func(t *testing.T) { testBBS(t, bbs.SuiteBLS12381Shake256) })
t.Run("BLS12381Sha256", func(t *testing.T) { testBBS(t, bbs.SuiteBLS12381Sha256) })
}

func testBBS(t *testing.T, suite bbs.SuiteID) {
var ikm [32]byte
_, err := rand.Read(ikm[:])
test.CheckNoErr(t, err, "failed rand.Read")

keyInfo := []byte("Key Information")
keyDst := []byte("Domain separation Tag")

key, err := bbs.KeyGen(suite, ikm[:], keyInfo, keyDst)
test.CheckNoErr(t, err, "failed KeyGen")

pub := key.PublicKey()
messages := [][]byte{
[]byte("hero: Spider-Man"),
[]byte("name: Peter Parker"),
[]byte("age: 19"),
[]byte("city: New York"),
[]byte("lemma: with great power comes great responsibility"),
}

sOpts := bbs.SignOptions{ID: suite, Header: []byte("signature header")}
sig := bbs.Sign(key, messages, sOpts)
valid := bbs.Verify(pub, &sig, messages, sOpts)
test.CheckOk(valid, "failed Verify", t)

choices, err := bbs.Disclose(messages, []uint{0, 3, 4})
test.CheckNoErr(t, err, "failed Disclose")

pOpts := bbs.ProveOptions{[]byte("presentation header"), sOpts}
proof, disclosed, err := bbs.Prove(rand.Reader, pub, &sig, choices, pOpts)
test.CheckNoErr(t, err, "failed Prove")

valid = bbs.VerifyProof(pub, proof, disclosed, pOpts)
test.CheckOk(valid, "failed VerifyProof", t)

test.CheckMarshal(t, key, new(bbs.PrivateKey))
test.CheckMarshal(t, pub, new(bbs.PublicKey))
test.CheckMarshal(t, &sig, new(bbs.Signature))
test.CheckMarshal(t, proof, new(bbs.Proof))
}

func BenchmarkBBS(b *testing.B) {
b.Run("BLS12381Shake256", func(b *testing.B) { benchmarkBBS(b, bbs.SuiteBLS12381Shake256) })
b.Run("BLS12381Sha256", func(b *testing.B) { benchmarkBBS(b, bbs.SuiteBLS12381Sha256) })
}

func benchmarkBBS(b *testing.B, suite bbs.SuiteID) {
var ikm [32]byte
_, err := rand.Read(ikm[:])
test.CheckNoErr(b, err, "failed rand Read")

keyInfo := []byte("Key Information")
keyDst := []byte("Domain separation Tag")

key, err := bbs.KeyGen(suite, ikm[:], keyInfo, keyDst)
test.CheckNoErr(b, err, "failed KeyGen")

pub := key.PublicKey()
messages := [][]byte{
[]byte("hero: Spider-Man"),
[]byte("name: Peter Parker"),
[]byte("age: 19"),
[]byte("city: New York"),
[]byte("lemma: with great power comes great responsibility"),
}

sOpts := bbs.SignOptions{ID: suite, Header: []byte("signature header")}
sig := bbs.Sign(key, messages, sOpts)
valid := bbs.Verify(pub, &sig, messages, sOpts)
test.CheckOk(valid, "failed Verify", b)

choices, err := bbs.Disclose(messages, []uint{0, 3, 4})
test.CheckNoErr(b, err, "failed Disclose")

pOpts := bbs.ProveOptions{[]byte("presentation header"), sOpts}
proof, disclosed, err := bbs.Prove(rand.Reader, pub, &sig, choices, pOpts)
test.CheckNoErr(b, err, "failed Prove")

valid = bbs.VerifyProof(pub, proof, disclosed, pOpts)
test.CheckOk(valid, "failed VerifyProof", b)

b.Run("KeyGen", func(b *testing.B) {
for range b.N {
key, _ = bbs.KeyGen(suite, ikm[:], keyInfo, keyDst)
_ = key.Public()
}
})
b.Run("Sign", func(b *testing.B) {
for range b.N {
_ = bbs.Sign(key, messages, sOpts)
}
})
b.Run("Verify", func(b *testing.B) {
for range b.N {
_ = bbs.Verify(pub, &sig, messages, sOpts)
}
})
b.Run("Prove", func(b *testing.B) {
for range b.N {
_, _, _ = bbs.Prove(rand.Reader, pub, &sig, choices, pOpts)
}
})
b.Run("VerifyProof", func(b *testing.B) {
for range b.N {
_ = bbs.VerifyProof(pub, proof, disclosed, pOpts)
}
})
}
27 changes: 27 additions & 0 deletions sign/bbs/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Package bbs provides an implementation of the BBS signature scheme.
//
// # Signing
//
// Unlike other signature schemes, BBS allows to sign multiple messages at once.
// Verification works as usual but it is sensitive to the order in which
// the messages are signed.
//
// # Proof of Knowledge of a Signature
//
// Anyone with a valid signature (over a set of messages) can generate a proof
// that attests knowledge of the signature.
// Proof verification works as usual but it is sensitive to the order in which
// the messages are processed.
//
// # Message Disclosure
//
// The prover can conceal some of the messages, while disclosing the others.
// For verification, only the disclosed messages are necessary to validate
// the proof.
//
// # Specification
//
// This package is compliant with draft-irtf-cfrg-bbs-signatures [v08].
//
// [v08] https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-08
package bbs
83 changes: 83 additions & 0 deletions sign/bbs/doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package bbs_test

import (
"crypto/rand"
"fmt"

"github.com/cloudflare/circl/sign/bbs"
)

const Suite = bbs.SuiteBLS12381Shake256

var (
sOpts = bbs.SignOptions{ID: Suite}
pOpts = bbs.ProveOptions{SignOptions: sOpts}
)

func ExampleSign() {
var keyMaterial [bbs.KeyMaterialMinSize]byte
_, _ = rand.Read(keyMaterial[:])
key, _ := bbs.KeyGen(Suite, keyMaterial[:], nil, nil)
pub := key.PublicKey()

msg0 := []byte("Document")
msg1 := []byte("Picture")
msg2 := []byte("Table")
sig := bbs.Sign(key, [][]byte{msg0, msg1, msg2}, sOpts)

valid := bbs.Verify(pub, &sig, [][]byte{msg0, msg1, msg2}, sOpts)
fmt.Println(valid)

// Fails because messages are in a wrong order.
invalid := bbs.Verify(pub, &sig, [][]byte{msg1, msg2, msg0}, sOpts)
fmt.Println(invalid)
// Output: true
// false
}

func ExampleProve() {
var keyMaterial [bbs.KeyMaterialMinSize]byte
_, _ = rand.Read(keyMaterial[:])
key, _ := bbs.KeyGen(Suite, keyMaterial[:], nil, nil)
pub := key.PublicKey()

allMsgs := [][]byte{[]byte("Document"), []byte("Picture"), []byte("Table")}
sig := bbs.Sign(key, allMsgs, sOpts)

// Disclose the second and third messages.
// Equivalently:
// msgsProve, _ := bbs.Disclose(allMsgs, []uint{1, 2})
// or
// msgsProve, _ := bbs.Conceal(allMsgs, []uint{0})
msgsProve := []bbs.Msg{
bbs.Concealed(allMsgs[0]),
bbs.Disclosed(allMsgs[1]),
bbs.Disclosed(allMsgs[2]),
}

for i, m := range msgsProve {
fmt.Printf("[%v] %T: %s\n", i, m, m)
}

proof, disclosed, _ := bbs.Prove(rand.Reader, pub, &sig, msgsProve, pOpts)

// Only disclosed messages.
for _, m := range disclosed {
fmt.Printf("[%v] %T: %s\n", m.Index, m.Message, m.Message)
}

valid := bbs.VerifyProof(pub, proof, disclosed, pOpts)
fmt.Println(valid)

// Fails because the disclosed messages are incomplete.
invalid := bbs.VerifyProof(pub, proof, disclosed[1:], pOpts)
fmt.Println(invalid)
// Output:
// [0] bbs.Concealed: Document
// [1] bbs.Disclosed: Picture
// [2] bbs.Disclosed: Table
// [1] bbs.Disclosed: Picture
// [2] bbs.Disclosed: Table
// true
// false
}
Loading