mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 15:31:38 +00:00
No commit suggestions generated
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/signatures/common"
|
||||
)
|
||||
|
||||
// BlindSignature is a BBS+ blind signature
|
||||
// structurally identical to `Signature` but
|
||||
// is used to help avoid misuse and confusion.
|
||||
//
|
||||
// 1 or more message have been hidden by the
|
||||
// potential signature holder so the signer
|
||||
// only knows a subset of the messages to be signed
|
||||
type BlindSignature struct {
|
||||
a curves.PairingPoint
|
||||
e, s curves.Scalar
|
||||
}
|
||||
|
||||
// Init creates an empty signature to a specific curve
|
||||
// which should be followed by UnmarshalBinary
|
||||
func (sig *BlindSignature) Init(curve *curves.PairingCurve) *BlindSignature {
|
||||
sig.a = curve.NewG1IdentityPoint()
|
||||
sig.e = curve.NewScalar()
|
||||
sig.s = curve.NewScalar()
|
||||
return sig
|
||||
}
|
||||
|
||||
func (sig BlindSignature) MarshalBinary() ([]byte, error) {
|
||||
out := append(sig.a.ToAffineCompressed(), sig.e.Bytes()...)
|
||||
out = append(out, sig.s.Bytes()...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (sig *BlindSignature) UnmarshalBinary(data []byte) error {
|
||||
pointLength := len(sig.a.ToAffineCompressed())
|
||||
scalarLength := len(sig.s.Bytes())
|
||||
expectedLength := pointLength + scalarLength*2
|
||||
if len(data) != expectedLength {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
a, err := sig.a.FromAffineCompressed(data[:pointLength])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e, err := sig.e.SetBytes(data[pointLength:(pointLength + scalarLength)])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := sig.s.SetBytes(data[(pointLength + scalarLength):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ok bool
|
||||
sig.a, ok = a.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
sig.e = e
|
||||
sig.s = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sig BlindSignature) ToUnblinded(blinder common.SignatureBlinding) *Signature {
|
||||
return &Signature{
|
||||
a: sig.a,
|
||||
e: sig.e,
|
||||
s: sig.s.Add(blinder),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// 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"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
"github.com/sonr-io/sonr/crypto/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
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
func TestBlindSignatureContext(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pk)
|
||||
require.NotNil(t, sk)
|
||||
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
msgs := make(map[int]curves.Scalar, 1)
|
||||
msgs[0] = curve.Scalar.Hash([]byte("identifier"))
|
||||
|
||||
ctx, blinding, err := NewBlindSignatureContext(curve, msgs, generators, nonce, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, blinding)
|
||||
require.False(t, blinding.IsZero())
|
||||
require.NotNil(t, ctx)
|
||||
require.False(t, ctx.commitment.IsIdentity())
|
||||
require.False(t, ctx.challenge.IsZero())
|
||||
for _, p := range ctx.proofs {
|
||||
require.False(t, p.IsZero())
|
||||
}
|
||||
|
||||
delete(msgs, 0)
|
||||
msgs[1] = curve.Scalar.Hash([]byte("firstname"))
|
||||
msgs[2] = curve.Scalar.Hash([]byte("lastname"))
|
||||
msgs[3] = curve.Scalar.Hash([]byte("age"))
|
||||
blindSig, err := ctx.ToBlindSignature(msgs, sk, generators, nonce)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, blindSig)
|
||||
require.False(t, blindSig.a.IsIdentity())
|
||||
require.False(t, blindSig.e.IsZero())
|
||||
require.False(t, blindSig.s.IsZero())
|
||||
sig := blindSig.ToUnblinded(blinding)
|
||||
msgs[0] = curve.Scalar.Hash([]byte("identifier"))
|
||||
var sigMsgs [4]curves.Scalar
|
||||
for i := 0; i < 4; i++ {
|
||||
sigMsgs[i] = msgs[i]
|
||||
}
|
||||
err = pk.Verify(sig, generators, sigMsgs[:])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBlindSignatureContextMarshalBinary(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pk)
|
||||
require.NotNil(t, sk)
|
||||
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
msgs := make(map[int]curves.Scalar, 1)
|
||||
msgs[0] = curve.Scalar.Hash([]byte("identifier"))
|
||||
|
||||
ctx, blinding, err := NewBlindSignatureContext(curve, msgs, generators, nonce, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, blinding)
|
||||
require.False(t, blinding.IsZero())
|
||||
require.NotNil(t, ctx)
|
||||
require.False(t, ctx.commitment.IsIdentity())
|
||||
require.False(t, ctx.challenge.IsZero())
|
||||
for _, p := range ctx.proofs {
|
||||
require.False(t, p.IsZero())
|
||||
}
|
||||
|
||||
data, err := ctx.MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, data)
|
||||
ctx2 := new(BlindSignatureContext).Init(curve)
|
||||
err = ctx2.UnmarshalBinary(data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ctx.challenge.Cmp(ctx2.challenge), 0)
|
||||
require.True(t, ctx.commitment.Equal(ctx2.commitment))
|
||||
require.Equal(t, len(ctx.proofs), len(ctx2.proofs))
|
||||
for i, p := range ctx.proofs {
|
||||
require.Equal(t, p.Cmp(ctx2.proofs[i]), 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// MessageGenerators are used to sign a vector of commitments for
|
||||
// a BBS+ signature. These must be the same generators used by
|
||||
// sign, verify, prove, and open
|
||||
//
|
||||
// These are generated in a deterministic manner. By using
|
||||
// MessageGenerators in this way, the generators do not need to be
|
||||
// stored alongside the public key and the same key can be used to sign
|
||||
// an arbitrary number of messages
|
||||
// Generators are created by computing
|
||||
// H_i = H_G1(W || I2OSP(0, 4) || I2OSP(0, 1) || I2OSP(length, 4))
|
||||
// where I2OSP means Integer to Octet Stream Primitive and
|
||||
// I2OSP represents an integer in a statically sized byte array.
|
||||
// `W` is the BBS+ public key.
|
||||
// Internally we store the 201 byte state since the only value that changes
|
||||
// is the index
|
||||
type MessageGenerators struct {
|
||||
// Blinding factor generator, stored, so we know what points to return in `Get`
|
||||
h0 curves.PairingPoint
|
||||
length int
|
||||
state [201]byte
|
||||
}
|
||||
|
||||
// Init set the message generators to the default state
|
||||
func (msgg *MessageGenerators) Init(w *PublicKey, length int) (*MessageGenerators, error) {
|
||||
if length < 0 {
|
||||
return nil, errors.New("length should be nonnegative")
|
||||
}
|
||||
msgg.length = length
|
||||
for i := range msgg.state {
|
||||
msgg.state[i] = 0
|
||||
}
|
||||
copy(msgg.state[:192], w.value.ToAffineUncompressed())
|
||||
msgg.state[197] = byte(length >> 24)
|
||||
msgg.state[198] = byte(length >> 16)
|
||||
msgg.state[199] = byte(length >> 8)
|
||||
msgg.state[200] = byte(length)
|
||||
var ok bool
|
||||
msgg.h0, ok = w.value.OtherGroup().Hash(msgg.state[:]).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return nil, errors.New("incorrect type conversion")
|
||||
}
|
||||
|
||||
return msgg, nil
|
||||
}
|
||||
|
||||
func (msgg MessageGenerators) Get(i int) curves.PairingPoint {
|
||||
if i <= 0 {
|
||||
return msgg.h0
|
||||
}
|
||||
if i > msgg.length {
|
||||
return nil
|
||||
}
|
||||
state := msgg.state
|
||||
state[193] = byte(i >> 24)
|
||||
state[194] = byte(i >> 16)
|
||||
state[195] = byte(i >> 8)
|
||||
state[196] = byte(i)
|
||||
point, ok := msgg.h0.Hash(msgg.state[:]).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return point
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/gtank/merlin"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/signatures/common"
|
||||
)
|
||||
|
||||
// PokSignature a.k.a. Proof of Knowledge of a Signature
|
||||
// is used by the prover to convince a verifier
|
||||
// that they possess a valid signature and
|
||||
// can selectively disclose a set of signed messages
|
||||
type PokSignature struct {
|
||||
// These values correspond to values with the same name
|
||||
// as section 4.5 in <https://eprint.iacr.org/2016/663.pdf>
|
||||
aPrime, aBar, d curves.PairingPoint
|
||||
// proof1 for proving signature
|
||||
// proof2 for selective disclosure
|
||||
proof1, proof2 *common.ProofCommittedBuilder
|
||||
// secrets1 for proving signature
|
||||
// secrets2 for proving relation
|
||||
// g1 * h1^m1 * h2^m2.... for all disclosed messages
|
||||
// m_i == d^r3 * h_0^{-s_prime} * h1^-m1 * h2^-m2.... for all undisclosed messages m_i
|
||||
secrets1, secrets2 []curves.Scalar
|
||||
}
|
||||
|
||||
// NewPokSignature creates the initial proof data before a Fiat-Shamir calculation
|
||||
func NewPokSignature(sig *Signature,
|
||||
generators *MessageGenerators,
|
||||
msgs []common.ProofMessage,
|
||||
reader io.Reader,
|
||||
) (*PokSignature, error) {
|
||||
if len(msgs) != generators.length {
|
||||
return nil, fmt.Errorf("mismatch messages and generators")
|
||||
}
|
||||
|
||||
r1 := getNonZeroScalar(sig.s, reader)
|
||||
r2 := getNonZeroScalar(sig.s, reader)
|
||||
r3, err := r1.Invert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigMsgs := make([]curves.Scalar, len(msgs))
|
||||
for i, m := range msgs {
|
||||
sigMsgs[i] = m.GetMessage()
|
||||
}
|
||||
|
||||
b := computeB(sig.s, sigMsgs, generators)
|
||||
|
||||
aPrime, ok := sig.a.Mul(r1).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
aBar, ok := b.Mul(r1).Sub(aPrime.Mul(sig.e)).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
// d = b * r1 + h0 * r2
|
||||
d, ok := aPrime.SumOfProducts([]curves.Point{b, generators.h0}, []curves.Scalar{r1, r2}).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
|
||||
// s' = s + r2r3
|
||||
sPrime := sig.s.Add(r2.Mul(r3))
|
||||
|
||||
// For proving relation aBar - d = aPrime * -e + h0 * r2
|
||||
curve := curves.Curve{
|
||||
Scalar: sig.s.Zero(),
|
||||
Point: sig.a.Identity(),
|
||||
}
|
||||
proof1 := common.NewProofCommittedBuilder(&curve)
|
||||
// For aPrime * -e
|
||||
err = proof1.CommitRandom(aPrime, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = proof1.CommitRandom(generators.h0, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secrets1 := []curves.Scalar{sig.e, r2}
|
||||
|
||||
// For selective disclosure
|
||||
proof2 := common.NewProofCommittedBuilder(&curve)
|
||||
// For d * -r3
|
||||
err = proof2.CommitRandom(d.Neg(), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// For h0 * s'
|
||||
err = proof2.CommitRandom(generators.h0, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secrets2 := make([]curves.Scalar, 0, len(msgs)+2)
|
||||
secrets2 = append(secrets2, r3)
|
||||
secrets2 = append(secrets2, sPrime)
|
||||
|
||||
for i, m := range msgs {
|
||||
if m.IsHidden() {
|
||||
err = proof2.Commit(generators.Get(i+1), m.GetBlinding(reader))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secrets2 = append(secrets2, m.GetMessage())
|
||||
}
|
||||
}
|
||||
|
||||
return &PokSignature{
|
||||
aPrime,
|
||||
aBar,
|
||||
d,
|
||||
proof1,
|
||||
proof2,
|
||||
secrets1,
|
||||
secrets2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetChallengeContribution returns the bytes that should be added to
|
||||
// a sigma protocol transcript for generating the challenge
|
||||
func (pok *PokSignature) GetChallengeContribution(transcript *merlin.Transcript) {
|
||||
transcript.AppendMessage([]byte("A'"), pok.aPrime.ToAffineCompressed())
|
||||
transcript.AppendMessage([]byte("Abar"), pok.aBar.ToAffineCompressed())
|
||||
transcript.AppendMessage([]byte("D"), pok.d.ToAffineCompressed())
|
||||
transcript.AppendMessage([]byte("Proof1"), pok.proof1.GetChallengeContribution())
|
||||
transcript.AppendMessage([]byte("Proof2"), pok.proof2.GetChallengeContribution())
|
||||
}
|
||||
|
||||
// GenerateProof converts the blinding factors and secrets into Schnorr proofs
|
||||
func (pok *PokSignature) GenerateProof(challenge curves.Scalar) (*PokSignatureProof, error) {
|
||||
proof1, err := pok.proof1.GenerateProof(challenge, pok.secrets1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof2, err := pok.proof2.GenerateProof(challenge, pok.secrets2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PokSignatureProof{
|
||||
aPrime: pok.aPrime,
|
||||
aBar: pok.aBar,
|
||||
d: pok.d,
|
||||
proof1: proof1,
|
||||
proof2: proof2,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gtank/merlin"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/signatures/common"
|
||||
)
|
||||
|
||||
// PokSignatureProof is the actual proof sent from a prover
|
||||
// to a verifier that contains a proof of knowledge of a signature
|
||||
// and the selective disclosure proof
|
||||
type PokSignatureProof struct {
|
||||
aPrime, aBar, d curves.PairingPoint
|
||||
proof1, proof2 []curves.Scalar
|
||||
}
|
||||
|
||||
// Init creates an empty proof to a specific curve
|
||||
// which should be followed by UnmarshalBinary
|
||||
func (pok *PokSignatureProof) Init(curve *curves.PairingCurve) *PokSignatureProof {
|
||||
pok.aPrime = curve.Scalar.Point().(curves.PairingPoint).OtherGroup()
|
||||
pok.aBar = pok.aPrime
|
||||
pok.d = pok.aPrime
|
||||
pok.proof1 = []curves.Scalar{
|
||||
curve.Scalar.Zero(),
|
||||
curve.Scalar.Zero(),
|
||||
}
|
||||
pok.proof2 = make([]curves.Scalar, 0)
|
||||
return pok
|
||||
}
|
||||
|
||||
func (pok *PokSignatureProof) MarshalBinary() ([]byte, error) {
|
||||
data := append(pok.aPrime.ToAffineCompressed(), pok.aBar.ToAffineCompressed()...)
|
||||
data = append(data, pok.d.ToAffineCompressed()...)
|
||||
for _, p := range pok.proof1 {
|
||||
data = append(data, p.Bytes()...)
|
||||
}
|
||||
for _, p := range pok.proof2 {
|
||||
data = append(data, p.Bytes()...)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (pok *PokSignatureProof) UnmarshalBinary(in []byte) error {
|
||||
scSize := len(pok.proof1[0].Bytes())
|
||||
ptSize := len(pok.aPrime.ToAffineCompressed())
|
||||
minSize := scSize*4 + ptSize*3
|
||||
inSize := len(in)
|
||||
if inSize < minSize {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
if (inSize-ptSize)%scSize != 0 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
secretCnt := ((inSize - ptSize*3) / scSize) - 2
|
||||
offset := 0
|
||||
end := ptSize
|
||||
|
||||
aPrime, err := pok.aPrime.FromAffineCompressed(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = end
|
||||
end += ptSize
|
||||
aBar, err := pok.aBar.FromAffineCompressed(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = end
|
||||
end += ptSize
|
||||
d, err := pok.d.FromAffineCompressed(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = end
|
||||
end += scSize
|
||||
proof1i0, err := pok.proof1[0].SetBytes(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset = end
|
||||
end += scSize
|
||||
proof1i1, err := pok.proof1[1].SetBytes(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proof2 := make([]curves.Scalar, secretCnt)
|
||||
for i := 0; i < secretCnt; i++ {
|
||||
offset = end
|
||||
end += scSize
|
||||
proof2[i], err = pok.proof1[0].SetBytes(in[offset:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var ok bool
|
||||
pok.aPrime, ok = aPrime.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
pok.aBar, ok = aBar.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
pok.d, ok = d.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
pok.proof1[0] = proof1i0
|
||||
pok.proof1[1] = proof1i1
|
||||
pok.proof2 = proof2
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChallengeContribution converts the committed values to bytes
|
||||
// for the Fiat-Shamir challenge
|
||||
func (pok PokSignatureProof) GetChallengeContribution(
|
||||
generators *MessageGenerators,
|
||||
revealedMessages map[int]curves.Scalar,
|
||||
challenge common.Challenge,
|
||||
transcript *merlin.Transcript,
|
||||
) {
|
||||
transcript.AppendMessage([]byte("A'"), pok.aPrime.ToAffineCompressed())
|
||||
transcript.AppendMessage([]byte("Abar"), pok.aBar.ToAffineCompressed())
|
||||
transcript.AppendMessage([]byte("D"), pok.d.ToAffineCompressed())
|
||||
|
||||
proof1Points := []curves.Point{pok.aBar.Sub(pok.d), pok.aPrime, generators.h0}
|
||||
proof1Scalars := []curves.Scalar{challenge, pok.proof1[0], pok.proof1[1]}
|
||||
commitmentProof1 := pok.aPrime.SumOfProducts(proof1Points, proof1Scalars)
|
||||
transcript.AppendMessage([]byte("Proof1"), commitmentProof1.ToAffineCompressed())
|
||||
|
||||
rPoints := make([]curves.Point, 1, len(revealedMessages)+1)
|
||||
rScalars := make([]curves.Scalar, 1, len(revealedMessages)+1)
|
||||
|
||||
rPoints[0] = pok.aPrime.Generator()
|
||||
rScalars[0] = pok.proof1[0].One()
|
||||
|
||||
for idx, msg := range revealedMessages {
|
||||
rPoints = append(rPoints, generators.Get(idx+1))
|
||||
rScalars = append(rScalars, msg)
|
||||
}
|
||||
|
||||
r := pok.aPrime.SumOfProducts(rPoints, rScalars)
|
||||
|
||||
pts := 3 + generators.length - len(revealedMessages)
|
||||
proof2Points := make([]curves.Point, 3, pts)
|
||||
proof2Scalars := make([]curves.Scalar, 3, pts)
|
||||
|
||||
// R * c
|
||||
proof2Points[0] = r
|
||||
proof2Scalars[0] = challenge
|
||||
|
||||
// D * -r3Hat
|
||||
proof2Points[1] = pok.d.Neg()
|
||||
proof2Scalars[1] = pok.proof2[0]
|
||||
|
||||
// H0 * s'Hat
|
||||
proof2Points[2] = generators.h0
|
||||
proof2Scalars[2] = pok.proof2[1]
|
||||
|
||||
j := 2
|
||||
for i := 0; i < generators.length; i++ {
|
||||
if _, contains := revealedMessages[i]; contains {
|
||||
continue
|
||||
}
|
||||
proof2Points = append(proof2Points, generators.Get(i+1))
|
||||
proof2Scalars = append(proof2Scalars, pok.proof2[j])
|
||||
j++
|
||||
}
|
||||
commitmentProof2 := r.SumOfProducts(proof2Points, proof2Scalars)
|
||||
|
||||
transcript.AppendMessage([]byte("Proof2"), commitmentProof2.ToAffineCompressed())
|
||||
}
|
||||
|
||||
// VerifySigPok only validates the signature proof,
|
||||
// the selective disclosure proof is checked by
|
||||
// verifying
|
||||
// pok.challenge == computedChallenge
|
||||
func (pok PokSignatureProof) VerifySigPok(pk *PublicKey) bool {
|
||||
return !pk.value.IsIdentity() &&
|
||||
!pok.aPrime.IsIdentity() &&
|
||||
!pok.aBar.IsIdentity() &&
|
||||
pok.aPrime.MultiPairing(pok.aPrime, pk.value, pok.aBar, pk.value.Generator().Neg().(curves.PairingPoint)).
|
||||
IsOne()
|
||||
}
|
||||
|
||||
// Verify checks a signature proof of knowledge and selective disclosure proof
|
||||
func (pok PokSignatureProof) Verify(
|
||||
revealedMsgs map[int]curves.Scalar,
|
||||
pk *PublicKey,
|
||||
generators *MessageGenerators,
|
||||
nonce common.Nonce,
|
||||
challenge common.Challenge,
|
||||
transcript *merlin.Transcript,
|
||||
) bool {
|
||||
pok.GetChallengeContribution(generators, revealedMsgs, challenge, transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm := transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
vChallenge, err := pok.proof1[0].SetBytesWide(okm)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return pok.VerifySigPok(pk) && challenge.Cmp(vChallenge) == 0
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/gtank/merlin"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/signatures/common"
|
||||
)
|
||||
|
||||
func TestPokSignatureProofSomeMessagesRevealed(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
_, ok := pk.value.(*curves.PointBls12381G2)
|
||||
require.True(t, ok)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(2),
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
}
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sig)
|
||||
require.False(t, sig.a.IsIdentity())
|
||||
require.False(t, sig.e.IsZero())
|
||||
require.False(t, sig.s.IsZero())
|
||||
|
||||
proofMsgs := []common.ProofMessage{
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[0],
|
||||
},
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[1],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[2],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[3],
|
||||
},
|
||||
}
|
||||
|
||||
pok, err := NewPokSignature(sig, generators, proofMsgs, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pok)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
transcript := merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pok.GetChallengeContribution(transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm := transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
challenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
|
||||
pokSig, err := pok.GenerateProof(challenge)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pokSig)
|
||||
require.True(t, pokSig.VerifySigPok(pk))
|
||||
|
||||
revealedMsgs := map[int]curves.Scalar{
|
||||
2: msgs[2],
|
||||
3: msgs[3],
|
||||
}
|
||||
// Manual verify to show how when used in conjunction with other ZKPs
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pokSig.GetChallengeContribution(generators, revealedMsgs, challenge, transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm = transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
vChallenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, challenge.Cmp(vChallenge), 0)
|
||||
|
||||
// Use the all-inclusive method
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
require.True(t, pokSig.Verify(revealedMsgs, pk, generators, nonce, challenge, transcript))
|
||||
}
|
||||
|
||||
func TestPokSignatureProofAllMessagesRevealed(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
_, ok := pk.value.(*curves.PointBls12381G2)
|
||||
require.True(t, ok)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(2),
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
}
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sig)
|
||||
require.False(t, sig.a.IsIdentity())
|
||||
require.False(t, sig.e.IsZero())
|
||||
require.False(t, sig.s.IsZero())
|
||||
|
||||
proofMsgs := []common.ProofMessage{
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[0],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[1],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[2],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[3],
|
||||
},
|
||||
}
|
||||
|
||||
pok, err := NewPokSignature(sig, generators, proofMsgs, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pok)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
transcript := merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pok.GetChallengeContribution(transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm := transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
challenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
|
||||
pokSig, err := pok.GenerateProof(challenge)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pokSig)
|
||||
require.True(t, pokSig.VerifySigPok(pk))
|
||||
|
||||
revealedMsgs := map[int]curves.Scalar{
|
||||
0: msgs[0],
|
||||
1: msgs[1],
|
||||
2: msgs[2],
|
||||
3: msgs[3],
|
||||
}
|
||||
// Manual verify to show how when used in conjunction with other ZKPs
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pokSig.GetChallengeContribution(generators, revealedMsgs, challenge, transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm = transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
vChallenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, challenge.Cmp(vChallenge), 0)
|
||||
|
||||
// Use the all-inclusive method
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
require.True(t, pokSig.Verify(revealedMsgs, pk, generators, nonce, challenge, transcript))
|
||||
}
|
||||
|
||||
func TestPokSignatureProofAllMessagesHidden(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
_, ok := pk.value.(*curves.PointBls12381G2)
|
||||
require.True(t, ok)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(2),
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
}
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sig)
|
||||
require.False(t, sig.a.IsIdentity())
|
||||
require.False(t, sig.e.IsZero())
|
||||
require.False(t, sig.s.IsZero())
|
||||
|
||||
proofMsgs := []common.ProofMessage{
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[0],
|
||||
},
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[1],
|
||||
},
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[2],
|
||||
},
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[3],
|
||||
},
|
||||
}
|
||||
|
||||
pok, err := NewPokSignature(sig, generators, proofMsgs, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pok)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
transcript := merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pok.GetChallengeContribution(transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm := transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
challenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
|
||||
pokSig, err := pok.GenerateProof(challenge)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pokSig)
|
||||
require.True(t, pokSig.VerifySigPok(pk))
|
||||
|
||||
revealedMsgs := map[int]curves.Scalar{}
|
||||
|
||||
// Manual verify to show how when used in conjunction with other ZKPs
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
pokSig.GetChallengeContribution(generators, revealedMsgs, challenge, transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
okm = transcript.ExtractBytes([]byte("signature proof of knowledge"), 64)
|
||||
vChallenge, err := curve.Scalar.SetBytesWide(okm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, challenge.Cmp(vChallenge), 0)
|
||||
|
||||
// Use the all-inclusive method
|
||||
transcript = merlin.NewTranscript("TestPokSignatureProofWorks")
|
||||
require.True(t, pokSig.Verify(revealedMsgs, pk, generators, nonce, challenge, transcript))
|
||||
}
|
||||
|
||||
func TestPokSignatureProofMarshalBinary(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
_, ok := pk.value.(*curves.PointBls12381G2)
|
||||
require.True(t, ok)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(2),
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
}
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sig)
|
||||
require.False(t, sig.a.IsIdentity())
|
||||
require.False(t, sig.e.IsZero())
|
||||
require.False(t, sig.s.IsZero())
|
||||
|
||||
proofMsgs := []common.ProofMessage{
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[0],
|
||||
},
|
||||
&common.ProofSpecificMessage{
|
||||
Message: msgs[1],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[2],
|
||||
},
|
||||
&common.RevealedMessage{
|
||||
Message: msgs[3],
|
||||
},
|
||||
}
|
||||
|
||||
pok, err := NewPokSignature(sig, generators, proofMsgs, crand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pok)
|
||||
nonce := curve.Scalar.Random(crand.Reader)
|
||||
transcript := merlin.NewTranscript("TestPokSignatureProofMarshalBinary")
|
||||
pok.GetChallengeContribution(transcript)
|
||||
transcript.AppendMessage([]byte("nonce"), nonce.Bytes())
|
||||
challenge, err := curve.Scalar.SetBytesWide(
|
||||
transcript.ExtractBytes([]byte("signature proof of knowledge"), 64),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
pokSig, err := pok.GenerateProof(challenge)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pokSig)
|
||||
|
||||
data, err := pokSig.MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, data)
|
||||
pokSig2 := new(PokSignatureProof).Init(curve)
|
||||
err = pokSig2.UnmarshalBinary(data)
|
||||
require.NoError(t, err)
|
||||
require.True(t, pokSig.aPrime.Equal(pokSig2.aPrime))
|
||||
require.True(t, pokSig.aBar.Equal(pokSig2.aBar))
|
||||
require.True(t, pokSig.d.Equal(pokSig2.d))
|
||||
require.Equal(t, len(pokSig.proof1), len(pokSig2.proof1))
|
||||
require.Equal(t, len(pokSig.proof2), len(pokSig2.proof2))
|
||||
for i, p := range pokSig.proof1 {
|
||||
require.Equal(t, p.Cmp(pokSig2.proof1[i]), 0)
|
||||
}
|
||||
for i, p := range pokSig.proof2 {
|
||||
require.Equal(t, p.Cmp(pokSig2.proof2[i]), 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// PublicKey is a BBS+ verification key
|
||||
type PublicKey struct {
|
||||
value curves.PairingPoint
|
||||
}
|
||||
|
||||
func (pk *PublicKey) Init(curve *curves.PairingCurve) *PublicKey {
|
||||
pk.value = curve.NewG2IdentityPoint()
|
||||
return pk
|
||||
}
|
||||
|
||||
func (pk PublicKey) MarshalBinary() ([]byte, error) {
|
||||
return pk.value.ToAffineCompressed(), nil
|
||||
}
|
||||
|
||||
func (pk *PublicKey) UnmarshalBinary(in []byte) error {
|
||||
value, err := pk.value.FromAffineCompressed(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ok bool
|
||||
pk.value, ok = value.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify checks a signature where all messages are known to the verifier
|
||||
func (pk PublicKey) Verify(
|
||||
signature *Signature,
|
||||
generators *MessageGenerators,
|
||||
msgs []curves.Scalar,
|
||||
) error {
|
||||
if generators.length < len(msgs) {
|
||||
return fmt.Errorf("not enough message generators")
|
||||
}
|
||||
if len(msgs) < 1 {
|
||||
return fmt.Errorf("invalid messages")
|
||||
}
|
||||
// Identity Point will always return true which is not what we want
|
||||
if pk.value.IsIdentity() {
|
||||
return fmt.Errorf("invalid public key")
|
||||
}
|
||||
if signature.a.IsIdentity() {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
a, ok := pk.value.Generator().Mul(signature.e).Add(pk.value).(curves.PairingPoint)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a valid point")
|
||||
}
|
||||
b, ok := computeB(signature.s, msgs, generators).Neg().(curves.PairingPoint)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a valid point")
|
||||
}
|
||||
|
||||
res := a.MultiPairing(signature.a, a, b, pk.value.Generator().(curves.PairingPoint))
|
||||
if !res.IsOne() {
|
||||
return fmt.Errorf("invalid result")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// SecretKey is a BBS+ signing key
|
||||
type SecretKey struct {
|
||||
value curves.PairingScalar
|
||||
}
|
||||
|
||||
func NewSecretKey(curve *curves.PairingCurve) (*SecretKey, error) {
|
||||
// The salt used with generating secret keys
|
||||
// See section 2.3 from https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04
|
||||
const hkdfKeyGenSalt = "BLS-SIG-KEYGEN-SALT-"
|
||||
const Size = 33
|
||||
var ikm [Size]byte
|
||||
cnt, err := crand.Read(ikm[:32])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cnt != Size-1 {
|
||||
return nil, fmt.Errorf("unable to read sufficient random data")
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.3
|
||||
h := sha256.New()
|
||||
n, err := h.Write([]byte(hkdfKeyGenSalt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != len(hkdfKeyGenSalt) {
|
||||
return nil, fmt.Errorf("incorrect salt bytes written to be hashed")
|
||||
}
|
||||
salt := h.Sum(nil)
|
||||
|
||||
// Leaves key_info parameter as the default empty string
|
||||
// and just adds parameter I2OSP(L, 2)
|
||||
kdf := hkdf.New(sha256.New, ikm[:], salt, []byte{0, 48})
|
||||
var okm [64]byte
|
||||
read, err := kdf.Read(okm[:48])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if read != 48 {
|
||||
return nil, fmt.Errorf("failed to create secret key")
|
||||
}
|
||||
v, err := curve.Scalar.SetBytesWide(okm[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, ok := v.(curves.PairingScalar)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid scalar")
|
||||
}
|
||||
return &SecretKey{
|
||||
value: value.SetPoint(curve.PointG2),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewKeys(curve *curves.PairingCurve) (*PublicKey, *SecretKey, error) {
|
||||
sk, err := NewSecretKey(curve)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sk.PublicKey(), sk, nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) Init(curve *curves.PairingCurve) *SecretKey {
|
||||
sk.value = curve.NewScalar()
|
||||
return sk
|
||||
}
|
||||
|
||||
func (sk SecretKey) MarshalBinary() ([]byte, error) {
|
||||
return sk.value.Bytes(), nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) UnmarshalBinary(in []byte) error {
|
||||
value, err := sk.value.SetBytes(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ok bool
|
||||
sk.value, ok = value.(curves.PairingScalar)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign generates a new signature where all messages are known to the signer
|
||||
func (sk *SecretKey) Sign(generators *MessageGenerators, msgs []curves.Scalar) (*Signature, error) {
|
||||
if generators.length < len(msgs) {
|
||||
return nil, fmt.Errorf("not enough message generators")
|
||||
}
|
||||
if len(msgs) < 1 {
|
||||
return nil, fmt.Errorf("invalid messages")
|
||||
}
|
||||
if sk.value.IsZero() {
|
||||
return nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
|
||||
drbg := sha3.NewShake256()
|
||||
_, _ = drbg.Write(sk.value.Bytes())
|
||||
addDeterministicNonceData(generators, msgs, 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)
|
||||
b := computeB(s, msgs, generators)
|
||||
exp, err := e.Add(sk.value).Invert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Signature{
|
||||
a: b.Mul(exp).(curves.PairingPoint),
|
||||
e: e,
|
||||
s: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PublicKey returns the corresponding public key
|
||||
func (sk *SecretKey) PublicKey() *PublicKey {
|
||||
return &PublicKey{
|
||||
value: sk.value.Point().Generator().Mul(sk.value).(curves.PairingPoint),
|
||||
}
|
||||
}
|
||||
|
||||
// computes g1 + s * h0 + msgs[0] * h[0] + msgs[1] * h[1] ...
|
||||
func computeB(
|
||||
s curves.Scalar,
|
||||
msgs []curves.Scalar,
|
||||
generators *MessageGenerators,
|
||||
) curves.PairingPoint {
|
||||
nMsgs := len(msgs)
|
||||
points := make([]curves.Point, nMsgs+2)
|
||||
points[1] = generators.Get(0)
|
||||
points[0] = points[1].Generator()
|
||||
|
||||
scalars := make([]curves.Scalar, nMsgs+2)
|
||||
scalars[0] = msgs[0].One()
|
||||
scalars[1] = s
|
||||
for i, m := range msgs {
|
||||
points[i+2] = generators.Get(i + 1)
|
||||
scalars[i+2] = m
|
||||
}
|
||||
pt := points[0].SumOfProducts(points, scalars)
|
||||
return pt.(curves.PairingPoint)
|
||||
}
|
||||
|
||||
func addDeterministicNonceData(
|
||||
generators *MessageGenerators,
|
||||
msgs []curves.Scalar,
|
||||
drbg io.Writer,
|
||||
) {
|
||||
for i := 0; i <= generators.length; i++ {
|
||||
_, _ = drbg.Write(generators.Get(i).ToAffineUncompressed())
|
||||
}
|
||||
for _, m := range msgs {
|
||||
_, _ = drbg.Write(m.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func getNonZeroScalar(sc curves.Scalar, reader io.Reader) curves.Scalar {
|
||||
// Should yield non-zero values for `e` and `s`, very small likelihood of being zero
|
||||
e := sc.Random(reader)
|
||||
for e.IsZero() {
|
||||
e = sc.Random(reader)
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package bbs is an implementation of BBS+ signature of https://eprint.iacr.org/2016/663.pdf
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Signature is a BBS+ signature
|
||||
// as described in 4.3 in
|
||||
// <https://eprint.iacr.org/2016/663.pdf>
|
||||
type Signature struct {
|
||||
a curves.PairingPoint
|
||||
e, s curves.Scalar
|
||||
}
|
||||
|
||||
// Init creates an empty signature to a specific curve
|
||||
// which should be followed by UnmarshalBinary or Create
|
||||
func (sig *Signature) Init(curve *curves.PairingCurve) *Signature {
|
||||
sig.a = curve.NewG1IdentityPoint()
|
||||
sig.e = curve.NewScalar()
|
||||
sig.s = curve.NewScalar()
|
||||
return sig
|
||||
}
|
||||
|
||||
func (sig Signature) MarshalBinary() ([]byte, error) {
|
||||
out := append(sig.a.ToAffineCompressed(), sig.e.Bytes()...)
|
||||
out = append(out, sig.s.Bytes()...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (sig *Signature) UnmarshalBinary(data []byte) error {
|
||||
pointLength := len(sig.a.ToAffineCompressed())
|
||||
scalarLength := len(sig.s.Bytes())
|
||||
expectedLength := pointLength + scalarLength*2
|
||||
if len(data) != expectedLength {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
a, err := sig.a.FromAffineCompressed(data[:pointLength])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e, err := sig.e.SetBytes(data[pointLength:(pointLength + scalarLength)])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := sig.s.SetBytes(data[(pointLength + scalarLength):])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ok bool
|
||||
sig.a, ok = a.(curves.PairingPoint)
|
||||
if !ok {
|
||||
return errors.New("incorrect type conversion")
|
||||
}
|
||||
sig.e = e
|
||||
sig.s = s
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bbs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
func TestSignatureWorks(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
curve.Scalar.New(6),
|
||||
}
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
err = pk.Verify(sig, generators, msgs)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSignatureIncorrectMessages(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
curve.Scalar.New(6),
|
||||
}
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
msgs[0] = curve.Scalar.New(7)
|
||||
err = pk.Verify(sig, generators, msgs)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSignatureMarshalBinary(t *testing.T) {
|
||||
curve := curves.BLS12381(&curves.PointBls12381G2{})
|
||||
msgs := []curves.Scalar{
|
||||
curve.Scalar.New(3),
|
||||
curve.Scalar.New(4),
|
||||
curve.Scalar.New(5),
|
||||
curve.Scalar.New(6),
|
||||
}
|
||||
pk, sk, err := NewKeys(curve)
|
||||
require.NoError(t, err)
|
||||
generators, err := new(MessageGenerators).Init(pk, 4)
|
||||
require.NoError(t, err)
|
||||
|
||||
sig, err := sk.Sign(generators, msgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := sig.MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 112, len(data))
|
||||
sig2 := new(Signature).Init(curve)
|
||||
err = sig2.UnmarshalBinary(data)
|
||||
require.NoError(t, err)
|
||||
require.True(t, sig.a.Equal(sig2.a))
|
||||
require.Equal(t, sig.e.Cmp(sig2.e), 0)
|
||||
require.Equal(t, sig.s.Cmp(sig2.s), 0)
|
||||
}
|
||||
Reference in New Issue
Block a user