mirror of
https://github.com/sonr-io/crypto.git
synced 2026-01-12 04:09:13 +00:00
No commit suggestions generated
This commit is contained in:
79
signatures/bbs/blind_signature.go
Normal file
79
signatures/bbs/blind_signature.go
Normal file
@@ -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),
|
||||
}
|
||||
}
|
||||
271
signatures/bbs/blind_signature_context.go
Normal file
271
signatures/bbs/blind_signature_context.go
Normal file
@@ -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
|
||||
}
|
||||
98
signatures/bbs/blind_signature_context_test.go
Normal file
98
signatures/bbs/blind_signature_context_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
77
signatures/bbs/message_generators.go
Normal file
77
signatures/bbs/message_generators.go
Normal file
@@ -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
|
||||
}
|
||||
159
signatures/bbs/pok_signature.go
Normal file
159
signatures/bbs/pok_signature.go
Normal file
@@ -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
|
||||
}
|
||||
214
signatures/bbs/pok_signature_proof.go
Normal file
214
signatures/bbs/pok_signature_proof.go
Normal file
@@ -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
|
||||
}
|
||||
319
signatures/bbs/pok_signature_proof_test.go
Normal file
319
signatures/bbs/pok_signature_proof_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
77
signatures/bbs/public_key.go
Normal file
77
signatures/bbs/public_key.go
Normal file
@@ -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
|
||||
}
|
||||
185
signatures/bbs/secret_key.go
Normal file
185
signatures/bbs/secret_key.go
Normal file
@@ -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
|
||||
}
|
||||
67
signatures/bbs/signature.go
Normal file
67
signatures/bbs/signature.go
Normal file
@@ -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
|
||||
}
|
||||
81
signatures/bbs/signature_test.go
Normal file
81
signatures/bbs/signature_test.go
Normal file
@@ -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)
|
||||
}
|
||||
114
signatures/bls/README.md
Executable file
114
signatures/bls/README.md
Executable file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
aliases: [README]
|
||||
tags: []
|
||||
title: README
|
||||
linter-yaml-title-alias: README
|
||||
date created: Wednesday, April 17th 2024, 4:11:40 pm
|
||||
date modified: Thursday, April 18th 2024, 8:19:25 am
|
||||
---
|
||||
|
||||
## BLS Signatures
|
||||
|
||||
An implementation of the Boneh-Lynn-Shacham (BLS) signatures according to the [standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/?include_text=1)
|
||||
on top of the Barreto-Lynn-Scott (BLS) 12-381 curve. The [BLS12-381 curve](https://github.com/zkcrypto/pairing/tree/master/src/bls12_381#serialization) provides roughly
|
||||
128-bits of security and BLS is a digital signature with aggregation properties.
|
||||
|
||||
We have implemented all three signature schemes described in the [standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/?include_text=1)
|
||||
which are designed to handle rogue-key attacks differently. These three are:
|
||||
|
||||
- **Basic** handles rogue key attacks by requiring all signed messages in an aggregate signature be distinct
|
||||
- **Message Augmentation** handles rogue key attacks by prepending the public key to the message during signing which ensures that all messages are distinct for different public keys.
|
||||
- **Proof of Possession** handles rogue key attacks by validating public keys in a separate step called a proof of possession. This allows for faster aggregate verification.
|
||||
|
||||
Pairing-friendly curves have two generator-groups 𝔾<sub>1</sub>, 𝔾<sub>2</sub>.
|
||||
Data in 𝔾<sub>2</sub> is twice the size of 𝔾<sub>1</sub> and operations are slower.
|
||||
BLS signatures require signatures and public keys to be in opposite groups i.e. signatures in 𝔾<sub>1</sub> and public keys in 𝔾<sub>2</sub> or
|
||||
signatures in 𝔾<sub>2</sub> and public keys in 𝔾<sub>1</sub>. This means one of two things:
|
||||
|
||||
- **Short public keys, long signatures**: Signatures are longer and slower to create, verify, and aggregate but public keys are small and fast to aggregate. Used when signing and verification operations not computed as often or for minimizing storage or bandwidth for public keys.
|
||||
- **Short signatures, long public keys**: Signatures are short and fast to create, verify, and aggregate but public keys are bigger and slower to aggregate. Used when signing and verification operations are computed often or for minimizing storage or bandwidth for signatures.
|
||||
|
||||
This library supports both of these variants for all three signature schemes. The more widely deployed
|
||||
variant is short public keys, long signatures. We refer to this variant as `UsualBls`. The other variant,
|
||||
short signatures, long public keys, is named `TinyBls`.
|
||||
The naming convention follows Sig`SchemeType`[Vt] where **Vt** is short for variant. For example,
|
||||
|
||||
- **Usual Bls Basic -> SigBasic**: Provides all the functions for the Basic signature scheme with signatures in 𝔾<sub>2</sub> and public keys in 𝔾<sub>1</sub>
|
||||
- **Tiny Bls Basic -> SigBasicVt**: Provides all the functions for the Basic signature scheme with signatures in 𝔾<sub>1</sub> and public keys in 𝔾<sub>2</sub>
|
||||
|
||||
One final note, in cryptography, it is considered good practice to use domain separation values to limit attacks to specific contexts. The [standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/?include_text=1)
|
||||
recommends specific values for each scheme but this library also supports supplying custom domain separation values. For example, there are two functions for creating
|
||||
a BLS signing instance:
|
||||
|
||||
- **NewSigBasic()** creates a Basic BLS signature using the recommended domain separation value.
|
||||
- **NewSigBasicWithDst(dst)** creates a Basic BLS signature using the parameter `dst` as the domain separation value such as in the [Eth2.0 Spec](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/validator.md#attestation-aggregation)
|
||||
|
||||
Also implemented is Threshold BLS as described in section 3.2 of [B03](https://www.cc.gatech.edu/~aboldyre/papers/bold.pdf).
|
||||
|
||||
- ThresholdKeygen(parts, threshold int) -> ([]\*SecretKeyShare, error)
|
||||
- PartialSign(share *SecretKeyShare, msg []byte) -> *PartialSignature
|
||||
- CombineSigs(*PartialSignature…) -> *Signature
|
||||
|
||||
### Security Considerations
|
||||
|
||||
#### Validating Secret Keys
|
||||
|
||||
Low entropy secret keys are a problem since this means an attacker can more easily guess the value and now can impersonate the key holder.
|
||||
Secret keys are derived using trusted random number generators (TRNG). If the TRNG produces bad entropy
|
||||
the secret key will be weak. Secret keys are checked to be non-zero values. A zero value secret key
|
||||
not only fails the entropy test but also yields a public key that will validate any signature.
|
||||
|
||||
#### Validating Public Keys
|
||||
|
||||
Public keys are ensured to be valid. A valid public key means
|
||||
it represents a valid, non-identity elliptic curve point in the correct subgroup.
|
||||
Public keys that are the identity element mean any signature would pass a call to verify.
|
||||
|
||||
#### Validating Signatures
|
||||
|
||||
Generated signatures are ensured to be valid. A valid signature means
|
||||
it represents a valid, non-identity elliptic curve point in the correct subgroup.
|
||||
Signatures that are the identity element mean any signature would pass a call to verify.
|
||||
Verify checks signatures for non-identity elements in the correct subgroup before checking
|
||||
for a valid signature.
|
||||
|
||||
#### Mitigating Rogue Key Attacks
|
||||
|
||||
A rogue key attacks can only happen to multisignature or aggregated signature.
|
||||
A rogue key attack is where at least one party produces a valid but malicious public key such that the multisignature requires less than the threshold to verify a signature.
|
||||
There are two ways to mitigate rogue key attacks: Guarantee all signed messages are unique or validate each public key before use. This library offers both solutions.
|
||||
|
||||
### Comparison to other BLS Implementations and Integrations
|
||||
|
||||
This library has been tested to be compatible with the following implementations
|
||||
by randomly generating millions of keys and signatures and importing and verifying
|
||||
between the two libraries.
|
||||
|
||||
1. Ethereum's [py_ecc](https://github.com/ethereum/py_ecc)
|
||||
2. PhoreProject [Go BLS](https://github.com/phoreproject/bls)
|
||||
3. Herumi's [BLS](https://github.com/herumi/bls-eth-go-binary)
|
||||
4. Algorand's [Rust BLS Sig](https://crates.io/crates/bls_sigs_ref)
|
||||
5. Miracl's [Rust and Go BLS sig](https://github.com/miracl/core)
|
||||
|
||||
The most common and high risk failures that can occur with library implementations
|
||||
are low entropy secret keys, malformed public keys, and invalid signature generation.
|
||||
|
||||
Low entropy secret keys are a problem since this means an attacker can more easily guess
|
||||
the value and now can impersonate the key holder.
|
||||
|
||||
Malformed public keys and signatures result in invalid addresses and no ability to withdraw funds.
|
||||
|
||||
To check for these problems, We tested millions of keys/signatures (vs say thousands or hundreds) to prove that
|
||||
|
||||
1. The odds of producing an invalid key/signature is already theoretically low 2<sup>-255</sup>
|
||||
2. The results of running millions of checks shows that nothing bad happened in 10M attempts
|
||||
|
||||
In other words, keys and signatures generated from these libraries can be consumed by this library
|
||||
and visa-versa.
|
||||
|
||||
Some of the libraries implementations for hash to curve were either out of date
|
||||
or not compliant with the [IETF Spec](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/).
|
||||
This meant that signatures generated by this library would not validate with others and visa-versa.
|
||||
However, public keys were found to be compatible in all libraries.
|
||||
|
||||
This library is compliant with the latest version (10 as of this writing).
|
||||
208
signatures/bls/bls_sig/lib.go
Normal file
208
signatures/bls/bls_sig/lib.go
Normal file
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// Package bls_sig is an implementation of the BLS signature defined in https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
"github.com/sonr-io/sonr/crypto/sharing"
|
||||
)
|
||||
|
||||
// Secret key in Fr
|
||||
const SecretKeySize = 32
|
||||
|
||||
// Secret key share with identifier byte in Fr
|
||||
const SecretKeyShareSize = 33
|
||||
|
||||
// The salt used with generating secret keys
|
||||
// See section 2.3 from https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
const hkdfKeyGenSalt = "BLS-SIG-KEYGEN-SALT-"
|
||||
|
||||
// Represents a value mod r where r is the curve order or
|
||||
// order of the subgroups in G1 and G2
|
||||
type SecretKey struct {
|
||||
value *native.Field
|
||||
}
|
||||
|
||||
func allRowsUnique(data [][]byte) bool {
|
||||
seen := make(map[string]bool)
|
||||
for _, row := range data {
|
||||
m := string(row)
|
||||
if _, ok := seen[m]; ok {
|
||||
return false
|
||||
}
|
||||
seen[m] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func generateRandBytes(count int) ([]byte, error) {
|
||||
ikm := make([]byte, count)
|
||||
cnt, err := rand.Read(ikm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cnt != count {
|
||||
return nil, fmt.Errorf("unable to read sufficient random data")
|
||||
}
|
||||
return ikm, nil
|
||||
}
|
||||
|
||||
// Creates a new BLS secret key
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (sk SecretKey) Generate(ikm []byte) (*SecretKey, error) {
|
||||
if len(ikm) < 32 {
|
||||
return nil, fmt.Errorf("ikm is too short. Must be at least 32")
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
ikm = append(ikm, 0)
|
||||
// Leaves key_info parameter as the default empty string
|
||||
// and just adds parameter I2OSP(L, 2)
|
||||
var okm [native.WideFieldBytes]byte
|
||||
kdf := hkdf.New(sha256.New, ikm, salt, []byte{0, 48})
|
||||
read, err := kdf.Read(okm[:48])
|
||||
copy(okm[:48], internal.ReverseScalarBytes(okm[:48]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if read != 48 {
|
||||
return nil, fmt.Errorf("failed to create private key")
|
||||
}
|
||||
v := bls12381.Bls12381FqNew().SetBytesWide(&okm)
|
||||
return &SecretKey{value: v}, nil
|
||||
}
|
||||
|
||||
// Serialize a secret key to raw bytes
|
||||
func (sk SecretKey) MarshalBinary() ([]byte, error) {
|
||||
bytes := sk.value.Bytes()
|
||||
return internal.ReverseScalarBytes(bytes[:]), nil
|
||||
}
|
||||
|
||||
// Deserialize a secret key from raw bytes
|
||||
// Cannot be zero. Must be 32 bytes and cannot be all zeroes.
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-2.3
|
||||
func (sk *SecretKey) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SecretKeySize {
|
||||
return fmt.Errorf("secret key must be %d bytes", SecretKeySize)
|
||||
}
|
||||
zeros := make([]byte, len(data))
|
||||
if subtle.ConstantTimeCompare(data, zeros) == 1 {
|
||||
return fmt.Errorf("secret key cannot be zero")
|
||||
}
|
||||
var bb [native.FieldBytes]byte
|
||||
copy(bb[:], internal.ReverseScalarBytes(data))
|
||||
value, err := bls12381.Bls12381FqNew().SetBytes(&bb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sk.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SecretKeyShare is shamir share of a private key
|
||||
type SecretKeyShare struct {
|
||||
identifier byte
|
||||
value []byte
|
||||
}
|
||||
|
||||
// Serialize a secret key share to raw bytes
|
||||
func (sks SecretKeyShare) MarshalBinary() ([]byte, error) {
|
||||
var blob [SecretKeyShareSize]byte
|
||||
l := len(sks.value)
|
||||
copy(blob[:l], sks.value)
|
||||
blob[l] = sks.identifier
|
||||
return blob[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a secret key share from raw bytes
|
||||
func (sks *SecretKeyShare) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SecretKeyShareSize {
|
||||
return fmt.Errorf("secret key share must be %d bytes", SecretKeyShareSize)
|
||||
}
|
||||
|
||||
zeros := make([]byte, len(data))
|
||||
if subtle.ConstantTimeCompare(data, zeros) == 1 {
|
||||
return fmt.Errorf("secret key share cannot be zero")
|
||||
}
|
||||
l := len(data)
|
||||
sks.identifier = data[l-1]
|
||||
sks.value = make([]byte, SecretKeySize)
|
||||
copy(sks.value, data[:l])
|
||||
return nil
|
||||
}
|
||||
|
||||
// thresholdizeSecretKey splits a composite secret key such that
|
||||
// `threshold` partial signatures can be combined to form a composite signature
|
||||
func thresholdizeSecretKey(secretKey *SecretKey, threshold, total uint) ([]*SecretKeyShare, error) {
|
||||
// Verify our parameters are acceptable.
|
||||
if secretKey == nil {
|
||||
return nil, fmt.Errorf("secret key is nil")
|
||||
}
|
||||
if threshold > total {
|
||||
return nil, fmt.Errorf("threshold cannot be greater than the total")
|
||||
}
|
||||
if threshold == 0 {
|
||||
return nil, fmt.Errorf("threshold cannot be zero")
|
||||
}
|
||||
if total <= 1 {
|
||||
return nil, fmt.Errorf("total must be larger than 1")
|
||||
}
|
||||
if total > 255 || threshold > 255 {
|
||||
return nil, fmt.Errorf("cannot have more than 255 shares")
|
||||
}
|
||||
|
||||
curve := curves.BLS12381G1()
|
||||
sss, err := sharing.NewShamir(uint32(threshold), uint32(total), curve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret, ok := curve.NewScalar().(*curves.ScalarBls12381)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid curve")
|
||||
}
|
||||
secret.Value = secretKey.value
|
||||
shares, err := sss.Split(secret, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify we got the expected number of shares
|
||||
if uint(len(shares)) != total {
|
||||
return nil, fmt.Errorf("%v != %v shares", len(shares), total)
|
||||
}
|
||||
|
||||
// Package our shares
|
||||
secrets := make([]*SecretKeyShare, len(shares))
|
||||
for i, s := range shares {
|
||||
// users expect BigEndian
|
||||
sks := &SecretKeyShare{identifier: byte(s.Id), value: s.Value}
|
||||
secrets[i] = sks
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
}
|
||||
481
signatures/bls/bls_sig/lib_test.go
Normal file
481
signatures/bls/bls_sig/lib_test.go
Normal file
@@ -0,0 +1,481 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
func genSecretKey(t *testing.T) *SecretKey {
|
||||
ikm := make([]byte, 32)
|
||||
sk, err := new(SecretKey).Generate(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't generate secret key")
|
||||
}
|
||||
return sk
|
||||
}
|
||||
|
||||
func genRandSecretKey(ikm []byte, t *testing.T) *SecretKey {
|
||||
sk, err := new(SecretKey).Generate(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't generate secret key")
|
||||
}
|
||||
return sk
|
||||
}
|
||||
|
||||
func genPublicKeyVt(sk *SecretKey, t *testing.T) *PublicKeyVt {
|
||||
pk, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetPublicKeyVt to pass but failed: %v", err)
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func genPublicKey(sk *SecretKey, t *testing.T) *PublicKey {
|
||||
pk, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("GetPublicKey failed. Couldn't generate public key: %v", err)
|
||||
}
|
||||
return pk
|
||||
}
|
||||
|
||||
func genSignature(sk *SecretKey, message []byte, t *testing.T) *Signature {
|
||||
bls := NewSigPop()
|
||||
|
||||
sig, err := bls.Sign(sk, message)
|
||||
if err != nil {
|
||||
t.Errorf("createSignature couldn't sign message: %v", err)
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func genSignatureVt(sk *SecretKey, message []byte, t *testing.T) *SignatureVt {
|
||||
bls := NewSigPopVt()
|
||||
sig, err := bls.Sign(sk, message)
|
||||
if err != nil {
|
||||
t.Errorf("createSignatureVt couldn't sign message: %v", err)
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func readRand(ikm []byte, t *testing.T) {
|
||||
n, err := rand.Read(ikm)
|
||||
if err != nil || n < len(ikm) {
|
||||
t.Errorf("Not enough data was read or an error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
func assertSecretKeyGen(seed, expected []byte, t *testing.T) {
|
||||
sk, err := new(SecretKey).Generate(seed)
|
||||
if err != nil {
|
||||
t.Errorf("Expected Generate to succeed but failed")
|
||||
}
|
||||
actual, _ := sk.MarshalBinary()
|
||||
if len(actual) != len(expected) {
|
||||
t.Errorf("Length of Generate output is incorrect. Expected 32, found: %v\n", len(actual))
|
||||
}
|
||||
if !bytes.Equal(actual[:], expected) {
|
||||
t.Errorf("SecretKey was not as expected")
|
||||
}
|
||||
}
|
||||
|
||||
func marshalStruct(value encoding.BinaryMarshaler, t *testing.T) []byte {
|
||||
out, err := value.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("MarshalBinary failed: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestSecretKeyZeroBytes(t *testing.T) {
|
||||
seed := []byte{}
|
||||
_, err := new(SecretKey).Generate(seed)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Generate to fail but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalLeadingZeroes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []byte
|
||||
}{
|
||||
{
|
||||
"no leading zeroes",
|
||||
[]byte{
|
||||
74,
|
||||
53,
|
||||
59,
|
||||
227,
|
||||
218,
|
||||
192,
|
||||
145,
|
||||
160,
|
||||
167,
|
||||
230,
|
||||
64,
|
||||
98,
|
||||
3,
|
||||
114,
|
||||
245,
|
||||
225,
|
||||
226,
|
||||
228,
|
||||
64,
|
||||
23,
|
||||
23,
|
||||
193,
|
||||
231,
|
||||
156,
|
||||
172,
|
||||
111,
|
||||
251,
|
||||
168,
|
||||
246,
|
||||
144,
|
||||
86,
|
||||
4,
|
||||
},
|
||||
},
|
||||
{
|
||||
"one leading zero byte",
|
||||
[]byte{
|
||||
0o0,
|
||||
53,
|
||||
59,
|
||||
227,
|
||||
218,
|
||||
192,
|
||||
145,
|
||||
160,
|
||||
167,
|
||||
230,
|
||||
64,
|
||||
98,
|
||||
3,
|
||||
114,
|
||||
245,
|
||||
225,
|
||||
226,
|
||||
228,
|
||||
64,
|
||||
23,
|
||||
23,
|
||||
193,
|
||||
231,
|
||||
156,
|
||||
172,
|
||||
111,
|
||||
251,
|
||||
168,
|
||||
246,
|
||||
144,
|
||||
86,
|
||||
4,
|
||||
},
|
||||
},
|
||||
{
|
||||
"two leading zeroes",
|
||||
[]byte{
|
||||
0o0,
|
||||
0o0,
|
||||
59,
|
||||
227,
|
||||
218,
|
||||
192,
|
||||
145,
|
||||
160,
|
||||
167,
|
||||
230,
|
||||
64,
|
||||
98,
|
||||
3,
|
||||
114,
|
||||
245,
|
||||
225,
|
||||
226,
|
||||
228,
|
||||
64,
|
||||
23,
|
||||
23,
|
||||
193,
|
||||
231,
|
||||
156,
|
||||
172,
|
||||
111,
|
||||
251,
|
||||
168,
|
||||
246,
|
||||
144,
|
||||
86,
|
||||
4,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Run all the tests!
|
||||
ss := bls12381.Bls12381FqNew()
|
||||
for _, test := range tests {
|
||||
// Marshal
|
||||
var k big.Int
|
||||
k.SetBytes(test.in)
|
||||
ss.SetBigInt(&k)
|
||||
bytes, err := SecretKey{ss}.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Test that marshal produces a values of the exected len
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if len(bytes) != SecretKeySize {
|
||||
t.Errorf("expected len=%v got len=%v", SecretKeySize, len(bytes))
|
||||
}
|
||||
})
|
||||
|
||||
// Test that we can also unmarhsal correctly
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var actual SecretKey
|
||||
err := actual.UnmarshalBinary(bytes)
|
||||
// Test for error
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test for correctness
|
||||
if actual.value.Cmp(ss) != 0 {
|
||||
t.Errorf("unmarshaled doens't match original value")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretKey32Bytes(t *testing.T) {
|
||||
seed := make([]byte, 32)
|
||||
expected := []byte{
|
||||
77,
|
||||
18,
|
||||
154,
|
||||
25,
|
||||
223,
|
||||
134,
|
||||
160,
|
||||
245,
|
||||
52,
|
||||
91,
|
||||
173,
|
||||
76,
|
||||
198,
|
||||
242,
|
||||
73,
|
||||
236,
|
||||
42,
|
||||
129,
|
||||
156,
|
||||
204,
|
||||
51,
|
||||
134,
|
||||
137,
|
||||
91,
|
||||
235,
|
||||
79,
|
||||
125,
|
||||
152,
|
||||
179,
|
||||
219,
|
||||
98,
|
||||
53,
|
||||
}
|
||||
assertSecretKeyGen(seed, expected, t)
|
||||
}
|
||||
|
||||
func TestSecretKey128Bytes(t *testing.T) {
|
||||
seed := make([]byte, 128)
|
||||
expected := []byte{
|
||||
97,
|
||||
207,
|
||||
109,
|
||||
96,
|
||||
94,
|
||||
90,
|
||||
233,
|
||||
215,
|
||||
221,
|
||||
207,
|
||||
240,
|
||||
139,
|
||||
24,
|
||||
209,
|
||||
152,
|
||||
170,
|
||||
73,
|
||||
209,
|
||||
151,
|
||||
241,
|
||||
148,
|
||||
176,
|
||||
173,
|
||||
92,
|
||||
101,
|
||||
48,
|
||||
39,
|
||||
175,
|
||||
201,
|
||||
219,
|
||||
146,
|
||||
168,
|
||||
}
|
||||
assertSecretKeyGen(seed, expected, t)
|
||||
}
|
||||
|
||||
func TestRandomSecretKey(t *testing.T) {
|
||||
seed := make([]byte, 48)
|
||||
_, _ = rand.Read(seed)
|
||||
_, err := new(SecretKey).Generate(seed)
|
||||
if err != nil {
|
||||
t.Errorf("Expected Generate to succeed but failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretKeyToBytes(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
skBytes := marshalStruct(sk, t)
|
||||
sk1 := new(SecretKey)
|
||||
err := sk1.UnmarshalBinary(skBytes)
|
||||
if err != nil {
|
||||
t.Errorf("Expected UnmarshalBinary to pass but failed: %v", err)
|
||||
}
|
||||
out := sk1.value.Bytes()
|
||||
for i, b := range internal.ReverseScalarBytes(out[:]) {
|
||||
if skBytes[i] != b {
|
||||
t.Errorf(
|
||||
"Expected secret keys to be equal but are different at offset %d: %v != %v",
|
||||
i,
|
||||
skBytes[i],
|
||||
b,
|
||||
)
|
||||
}
|
||||
}
|
||||
sk2 := new(SecretKey)
|
||||
err = sk2.UnmarshalBinary(skBytes)
|
||||
if err != nil {
|
||||
t.Errorf("Expected FromBytes to succeed but failed.")
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(sk2, t), skBytes) {
|
||||
t.Errorf("Expected secret keys to be equal but are different")
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the thresholdize creates the expected number
|
||||
// of shares
|
||||
func TestThresholdizeSecretKeyCountsCorrect(t *testing.T) {
|
||||
sk := &SecretKey{value: bls12381.Bls12381FqNew().SetBigInt(big.NewInt(248631463258962596))}
|
||||
tests := []struct {
|
||||
key *SecretKey
|
||||
t, n uint
|
||||
expectedError bool
|
||||
}{
|
||||
// bad cases
|
||||
{sk, 1, 1, true}, // n == 1
|
||||
{sk, 1, 5, true}, // t == 1
|
||||
{nil, 3, 5, true}, // sk nil
|
||||
{sk, 101, 100, true}, // t> n
|
||||
{sk, 0, 10, true}, // t == 0
|
||||
{sk, 10, 256, true}, // n > 256
|
||||
|
||||
// good cases
|
||||
{sk, 10, 10, false}, // t == n
|
||||
{sk, 2, 10, false}, // boundary case for t
|
||||
{sk, 9, 10, false}, // boundary case for t
|
||||
{sk, 10, 255, false}, // boundary case for n
|
||||
{sk, 254, 255, false}, // boundary case for t,n
|
||||
{sk, 100, 200, false}, // arbitrary t,n values
|
||||
{sk, 10, 20, false}, // arbitrary t,n values
|
||||
{sk, 15, 200, false}, // arbitrary t,n values
|
||||
{sk, 254, 255, false}, // boundary case
|
||||
{sk, 255, 255, false}, // boundary case
|
||||
}
|
||||
|
||||
// Run all the tests!
|
||||
for i, test := range tests {
|
||||
shares, err := thresholdizeSecretKey(test.key, test.t, test.n)
|
||||
|
||||
// Check for errors
|
||||
if test.expectedError && err == nil {
|
||||
t.Errorf(
|
||||
"%d - expected an error but received nil. t=%v, n=%v, sk=%v",
|
||||
i,
|
||||
test.t,
|
||||
test.n,
|
||||
sk,
|
||||
)
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if !test.expectedError && err != nil {
|
||||
t.Errorf(
|
||||
"%d - received unexpected error %v. t=%v, n=%v, sk=%v",
|
||||
i,
|
||||
err,
|
||||
test.t,
|
||||
test.n,
|
||||
sk,
|
||||
)
|
||||
}
|
||||
|
||||
// Check the share count == n
|
||||
if !test.expectedError && test.n != uint(len(shares)) {
|
||||
t.Errorf("%d - expected len(shares) = %v != %v (n)", i, len(shares), test.n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretKeyShareUnmarshalBinary(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
sks, err := thresholdizeSecretKey(sk, 3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("Expected thresholdizeSecretKey to pass but failed.")
|
||||
}
|
||||
|
||||
for i, sh := range sks {
|
||||
b1, err := sh.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("%d - expected MarshalBinary to pass but failed. sh=%v", i, sh)
|
||||
}
|
||||
|
||||
// UnmarshalBinary b1 to new SecretKeyShare
|
||||
sh1 := new(SecretKeyShare)
|
||||
err = sh1.UnmarshalBinary(b1)
|
||||
if err != nil {
|
||||
t.Errorf("%d - expected UnmarshalBinary to pass but failed. sh=%v", i, sh)
|
||||
}
|
||||
|
||||
// zero bytes slice with length equal to SecretKeySize
|
||||
zeros := make([]byte, SecretKeySize)
|
||||
b2, err := sh1.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Errorf("%d - expected MarshalBinary to pass but failed. sh1=%v", i, sh1)
|
||||
}
|
||||
|
||||
// Check if []bytes from UnmarshalBinary != zeros && Initial bytes(b1) == Final bytes(b2)
|
||||
if bytes.Equal(zeros, b2) && !bytes.Equal(b1, b2) {
|
||||
t.Errorf(
|
||||
"%d - expected UnmarshalBinary to give non zeros value but failed. sh1=%v, sh=%v",
|
||||
i,
|
||||
sh1,
|
||||
sh,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
484
signatures/bls/bls_sig/tiny_bls.go
Executable file
484
signatures/bls/bls_sig/tiny_bls.go
Executable file
@@ -0,0 +1,484 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// Domain separation tag for basic signatures
|
||||
// according to section 4.2.1 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignatureBasicVtDst = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"
|
||||
// Domain separation tag for basic signatures
|
||||
// according to section 4.2.2 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignatureAugVtDst = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_"
|
||||
// Domain separation tag for proof of possession signatures
|
||||
// according to section 4.2.3 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignaturePopVtDst = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_"
|
||||
// Domain separation tag for proof of possession proofs
|
||||
// according to section 4.2.3 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsPopProofVtDst = "BLS_POP_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_"
|
||||
)
|
||||
|
||||
type BlsSchemeVt interface {
|
||||
Keygen() (*PublicKeyVt, *SecretKey, error)
|
||||
KeygenWithSeed(ikm []byte) (*PublicKeyVt, *SecretKey, error)
|
||||
Sign(sk *SecretKey, msg []byte) (*SignatureVt, error)
|
||||
Verify(pk *PublicKeyVt, msg []byte, sig *SignatureVt) bool
|
||||
AggregateVerify(pks []*PublicKeyVt, msgs [][]byte, sigs []*SignatureVt) bool
|
||||
}
|
||||
|
||||
// generateKeysVt creates 32 bytes of random data to be fed to
|
||||
// generateKeysWithSeedVt
|
||||
func generateKeysVt() (*PublicKeyVt, *SecretKey, error) {
|
||||
ikm, err := generateRandBytes(32)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return generateKeysWithSeedVt(ikm)
|
||||
}
|
||||
|
||||
// generateKeysWithSeedVt generates a BLS key pair given input key material (ikm)
|
||||
func generateKeysWithSeedVt(ikm []byte) (*PublicKeyVt, *SecretKey, error) {
|
||||
sk, err := new(SecretKey).Generate(ikm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pk, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// thresholdGenerateKeys will generate random secret key shares and the corresponding public key
|
||||
func thresholdGenerateKeysVt(threshold, total uint) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
pk, sk, err := generateKeysVt()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
shares, err := thresholdizeSecretKey(sk, threshold, total)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, shares, nil
|
||||
}
|
||||
|
||||
// thresholdGenerateKeysWithSeed will generate random secret key shares and the corresponding public key
|
||||
// using the corresponding seed `ikm`
|
||||
func thresholdGenerateKeysWithSeedVt(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
pk, sk, err := generateKeysWithSeedVt(ikm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
shares, err := thresholdizeSecretKey(sk, threshold, total)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, shares, nil
|
||||
}
|
||||
|
||||
// SigBasicVt is minimal-pubkey-size scheme that doesn't support FastAggregateVerification.
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.1
|
||||
type SigBasicVt struct {
|
||||
dst string
|
||||
}
|
||||
|
||||
// Creates a new BLS basic signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigBasicVt() *SigBasicVt {
|
||||
return &SigBasicVt{dst: blsSignatureBasicVtDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS basic signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigBasicVtWithDst(signDst string) *SigBasicVt {
|
||||
return &SigBasicVt{dst: signDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigBasicVt) Keygen() (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysVt()
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigBasicVt) KeygenWithSeed(ikm []byte) (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysWithSeedVt(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigBasicVt) ThresholdKeygen(
|
||||
threshold, total uint,
|
||||
) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysVt(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeygenWithSeed generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures from input key material (ikm)
|
||||
func (b SigBasicVt) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeedVt(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G1 from sk, a secret key, and a message
|
||||
func (b SigBasicVt) Sign(sk *SecretKey, msg []byte) (*SignatureVt, error) {
|
||||
return sk.createSignatureVt(msg, b.dst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigBasicVt) PartialSign(sks *SecretKeyShare, msg []byte) (*PartialSignatureVt, error) {
|
||||
return sks.partialSignVt(msg, b.dst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigBasicVt) CombineSignatures(sigs ...*PartialSignatureVt) (*SignatureVt, error) {
|
||||
return combineSigsVt(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
func (b SigBasicVt) Verify(pk *PublicKeyVt, msg []byte, sig *SignatureVt) (bool, error) {
|
||||
return pk.verifySignatureVt(msg, sig, b.dst)
|
||||
}
|
||||
|
||||
// The AggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// Each message must be different or this will return false.
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigBasicVt) AggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msgs [][]byte,
|
||||
sigs []*SignatureVt,
|
||||
) (bool, error) {
|
||||
if !allRowsUnique(msgs) {
|
||||
return false, fmt.Errorf("all messages must be distinct")
|
||||
}
|
||||
asig, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, msgs, b.dst)
|
||||
}
|
||||
|
||||
// SigAugVt is minimal-signature-size scheme that doesn't support FastAggregateVerification.
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.2
|
||||
type SigAugVt struct {
|
||||
dst string
|
||||
}
|
||||
|
||||
// Creates a new BLS message augmentation signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigAugVt() *SigAugVt {
|
||||
return &SigAugVt{dst: blsSignatureAugVtDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS message augmentation signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigAugVtWithDst(signDst string) *SigAugVt {
|
||||
return &SigAugVt{dst: signDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigAugVt) Keygen() (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysVt()
|
||||
}
|
||||
|
||||
// Creates a new BLS secret key
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigAugVt) KeygenWithSeed(ikm []byte) (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysWithSeedVt(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigAugVt) ThresholdKeygen(threshold, total uint) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysVt(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeygenWithSeed generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigAugVt) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeedVt(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G1 from sk, a secret key, and a message
|
||||
// See section 3.2.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02
|
||||
func (b SigAugVt) Sign(sk *SecretKey, msg []byte) (*SignatureVt, error) {
|
||||
pk, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MarshalBinary failed")
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return sk.createSignatureVt(bytes, b.dst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigAugVt) PartialSign(
|
||||
sks *SecretKeyShare,
|
||||
pk *PublicKeyVt,
|
||||
msg []byte,
|
||||
) (*PartialSignatureVt, error) {
|
||||
if len(msg) == 0 {
|
||||
return nil, fmt.Errorf("message cannot be empty or nil")
|
||||
}
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MarshalBinary failed")
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return sks.partialSignVt(bytes, b.dst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigAugVt) CombineSignatures(sigs ...*PartialSignatureVt) (*SignatureVt, error) {
|
||||
return combineSigsVt(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
// See section 3.2.2 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigAugVt) Verify(pk *PublicKeyVt, msg []byte, sig *SignatureVt) (bool, error) {
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return pk.verifySignatureVt(bytes, sig, b.dst)
|
||||
}
|
||||
|
||||
// The aggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// See section 3.2.3 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigAugVt) AggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msgs [][]byte,
|
||||
sigs []*SignatureVt,
|
||||
) (bool, error) {
|
||||
if len(pks) != len(msgs) {
|
||||
return false, fmt.Errorf(
|
||||
"the number of public keys does not match the number of messages: %v != %v",
|
||||
len(pks),
|
||||
len(msgs),
|
||||
)
|
||||
}
|
||||
data := make([][]byte, len(msgs))
|
||||
for i, msg := range msgs {
|
||||
bytes, err := pks[i].MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data[i] = append(bytes, msg...)
|
||||
}
|
||||
asig, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, data, b.dst)
|
||||
}
|
||||
|
||||
// SigEth2Vt supports signatures on Eth2.
|
||||
// Internally is an alias for SigPopVt
|
||||
type SigEth2Vt = SigPopVt
|
||||
|
||||
// NewSigEth2Vt Creates a new BLS ETH2 signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigEth2Vt() *SigEth2Vt {
|
||||
return NewSigPopVt()
|
||||
}
|
||||
|
||||
// SigPopVt is minimal-signature-size scheme that supports FastAggregateVerification
|
||||
// and requires using proofs of possession to mitigate rogue-key attacks
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.3
|
||||
type SigPopVt struct {
|
||||
sigDst string
|
||||
popDst string
|
||||
}
|
||||
|
||||
// Creates a new BLS proof of possession signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigPopVt() *SigPopVt {
|
||||
return &SigPopVt{sigDst: blsSignaturePopVtDst, popDst: blsPopProofVtDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS message proof of possession signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigPopVtWithDst(signDst, popDst string) (*SigPopVt, error) {
|
||||
if signDst == popDst {
|
||||
return nil, fmt.Errorf("domain separation tags cannot be equal")
|
||||
}
|
||||
return &SigPopVt{sigDst: signDst, popDst: popDst}, nil
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigPopVt) Keygen() (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysVt()
|
||||
}
|
||||
|
||||
// Creates a new BLS secret key
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigPopVt) KeygenWithSeed(ikm []byte) (*PublicKeyVt, *SecretKey, error) {
|
||||
return generateKeysWithSeedVt(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigPopVt) ThresholdKeygen(threshold, total uint) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysVt(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigPopVt) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKeyVt, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeedVt(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G1 from sk, a secret key, and a message
|
||||
// See section 2.6 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) Sign(sk *SecretKey, msg []byte) (*SignatureVt, error) {
|
||||
return sk.createSignatureVt(msg, b.sigDst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigPopVt) PartialSign(sks *SecretKeyShare, msg []byte) (*PartialSignatureVt, error) {
|
||||
return sks.partialSignVt(msg, b.sigDst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigPopVt) CombineSignatures(sigs ...*PartialSignatureVt) (*SignatureVt, error) {
|
||||
return combineSigsVt(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
// See section 2.7 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) Verify(pk *PublicKeyVt, msg []byte, sig *SignatureVt) (bool, error) {
|
||||
return pk.verifySignatureVt(msg, sig, b.sigDst)
|
||||
}
|
||||
|
||||
// The aggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// Each message must be different or this will return false.
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02
|
||||
func (b SigPopVt) AggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msgs [][]byte,
|
||||
sigs []*SignatureVt,
|
||||
) (bool, error) {
|
||||
if !allRowsUnique(msgs) {
|
||||
return false, fmt.Errorf("all messages must be distinct")
|
||||
}
|
||||
asig, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, msgs, b.sigDst)
|
||||
}
|
||||
|
||||
// Combine many signatures together to form a Multisignature.
|
||||
// Multisignatures can be created when multiple signers jointly
|
||||
// generate signatures over the same message.
|
||||
func (b SigPopVt) AggregateSignatures(sigs ...*SignatureVt) (*MultiSignatureVt, error) {
|
||||
g1, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MultiSignatureVt{value: g1.value}, nil
|
||||
}
|
||||
|
||||
// Combine many public keys together to form a Multipublickey.
|
||||
// Multipublickeys are used to verify multisignatures.
|
||||
func (b SigPopVt) AggregatePublicKeys(pks ...*PublicKeyVt) (*MultiPublicKeyVt, error) {
|
||||
g2, err := aggregatePublicKeysVt(pks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MultiPublicKeyVt{value: g2.value}, nil
|
||||
}
|
||||
|
||||
// Checks that a multisignature is valid for the message under the multi public key
|
||||
// Similar to FastAggregateVerify except the keys and signatures have already been
|
||||
// combined. See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02
|
||||
func (b SigPopVt) VerifyMultiSignature(
|
||||
pk *MultiPublicKeyVt,
|
||||
msg []byte,
|
||||
sig *MultiSignatureVt,
|
||||
) (bool, error) {
|
||||
s := &SignatureVt{value: sig.value}
|
||||
p := &PublicKeyVt{value: pk.value}
|
||||
return p.verifySignatureVt(msg, s, b.sigDst)
|
||||
}
|
||||
|
||||
// FastAggregateVerify verifies an aggregated signature over the same message under the given public keys.
|
||||
// See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) FastAggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msg []byte,
|
||||
asig *SignatureVt,
|
||||
) (bool, error) {
|
||||
apk, err := aggregatePublicKeysVt(pks...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return apk.verifySignatureVt(msg, asig, b.sigDst)
|
||||
}
|
||||
|
||||
// FastAggregateVerifyConstituent verifies a list of signature over the same message under the given public keys.
|
||||
// See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) FastAggregateVerifyConstituent(
|
||||
pks []*PublicKeyVt,
|
||||
msg []byte,
|
||||
sigs []*SignatureVt,
|
||||
) (bool, error) {
|
||||
asig, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return b.FastAggregateVerify(pks, msg, asig)
|
||||
}
|
||||
|
||||
// Create a proof of possession for the corresponding public key.
|
||||
// A proof of possession must be created for each public key to be used
|
||||
// in FastAggregateVerify or a Multipublickey to avoid rogue key attacks.
|
||||
// See section 3.3.2 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) PopProve(sk *SecretKey) (*ProofOfPossessionVt, error) {
|
||||
return sk.createProofOfPossessionVt(b.popDst)
|
||||
}
|
||||
|
||||
// verify a proof of possession for the corresponding public key is valid.
|
||||
// A proof of possession must be created for each public key to be used
|
||||
// in FastAggregateVerify or a Multipublickey to avoid rogue key attacks.
|
||||
// See section 3.3.3 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPopVt) PopVerify(pk *PublicKeyVt, pop1 *ProofOfPossessionVt) (bool, error) {
|
||||
return pop1.verify(pk, b.popDst)
|
||||
}
|
||||
516
signatures/bls/bls_sig/tiny_bls_sig.go
Normal file
516
signatures/bls/bls_sig/tiny_bls_sig.go
Normal file
@@ -0,0 +1,516 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Implement BLS signatures on the BLS12-381 curve
|
||||
// according to https://crypto.standford.edu/~dabo/pubs/papers/BLSmultisig.html
|
||||
// and https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
// this file implements signatures in G1 and public keys in G2.
|
||||
// Public Keys and Signatures can be aggregated but the consumer
|
||||
// must use proofs of possession to defend against rogue-key attacks.
|
||||
|
||||
const (
|
||||
// Public key size in G2
|
||||
PublicKeyVtSize = 96
|
||||
// Signature size in G1
|
||||
SignatureVtSize = 48
|
||||
// Proof of Possession in G1
|
||||
ProofOfPossessionVtSize = 48
|
||||
)
|
||||
|
||||
// Represents a public key in G2
|
||||
type PublicKeyVt struct {
|
||||
value bls12381.G2
|
||||
}
|
||||
|
||||
// Serialize a public key to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pk *PublicKeyVt) MarshalBinary() ([]byte, error) {
|
||||
out := pk.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a public key from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the public key
|
||||
// otherwise it will return an error
|
||||
func (pk *PublicKeyVt) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != PublicKeyVtSize {
|
||||
return fmt.Errorf("public key must be %d bytes", PublicKeySize)
|
||||
}
|
||||
var blob [PublicKeyVtSize]byte
|
||||
copy(blob[:], data)
|
||||
p2, err := new(bls12381.G2).FromCompressed(&blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p2.IsIdentity() == 1 {
|
||||
return fmt.Errorf("public keys cannot be zero")
|
||||
}
|
||||
pk.value = *p2
|
||||
return nil
|
||||
}
|
||||
|
||||
// Represents a BLS signature in G1
|
||||
type SignatureVt struct {
|
||||
value bls12381.G1
|
||||
}
|
||||
|
||||
// Serialize a signature to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (sig *SignatureVt) MarshalBinary() ([]byte, error) {
|
||||
out := sig.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
func (sig *SignatureVt) verify(pk *PublicKeyVt, message []byte, signDstVt string) (bool, error) {
|
||||
return pk.verifySignatureVt(message, sig, signDstVt)
|
||||
}
|
||||
|
||||
// The AggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message) pairs.
|
||||
// The Signature is the output of aggregateSignaturesVt
|
||||
// Each message must be different or this will return false.
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (sig *SignatureVt) aggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msgs [][]byte,
|
||||
signDstVt string,
|
||||
) (bool, error) {
|
||||
return sig.coreAggregateVerify(pks, msgs, signDstVt)
|
||||
}
|
||||
|
||||
func (sig *SignatureVt) coreAggregateVerify(
|
||||
pks []*PublicKeyVt,
|
||||
msgs [][]byte,
|
||||
signDstVt string,
|
||||
) (bool, error) {
|
||||
if len(pks) < 1 {
|
||||
return false, fmt.Errorf("at least one key is required")
|
||||
}
|
||||
if len(msgs) < 1 {
|
||||
return false, fmt.Errorf("at least one message is required")
|
||||
}
|
||||
if len(pks) != len(msgs) {
|
||||
return false, fmt.Errorf(
|
||||
"the number of public keys does not match the number of messages: %v != %v",
|
||||
len(pks),
|
||||
len(msgs),
|
||||
)
|
||||
}
|
||||
if sig.value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
|
||||
engine := new(bls12381.Engine)
|
||||
dst := []byte(signDstVt)
|
||||
// e(H(m_1), pk_1)*...*e(H(m_N), pk_N) == e(s, g2)
|
||||
// However, we use only one miller loop
|
||||
// by doing the equivalent of
|
||||
// e(H(m_1), pk_1)*...*e(H(m_N), pk_N) * e(s^-1, g2) == 1
|
||||
for i, pk := range pks {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key at %d is nil", i)
|
||||
}
|
||||
if pk.value.IsIdentity() == 1 || pk.value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("public key at %d is not in the correct subgroup", i)
|
||||
}
|
||||
p1 := new(bls12381.G1).Hash(native.EllipticPointHasherSha256(), msgs[i], dst)
|
||||
engine.AddPair(p1, &pk.value)
|
||||
}
|
||||
engine.AddPairInvG2(&sig.value, new(bls12381.G2).Generator())
|
||||
return engine.Check(), nil
|
||||
}
|
||||
|
||||
// Deserialize a signature from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (sig *SignatureVt) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SignatureVtSize {
|
||||
return fmt.Errorf("signature must be %d bytes", SignatureSize)
|
||||
}
|
||||
var blob [SignatureVtSize]byte
|
||||
copy(blob[:], data)
|
||||
p1, err := new(bls12381.G1).FromCompressed(&blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p1.IsIdentity() == 1 {
|
||||
return fmt.Errorf("signatures cannot be zero")
|
||||
}
|
||||
sig.value = *p1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the corresponding public key from a secret key
|
||||
// Verifies the public key is in the correct subgroup
|
||||
func (sk *SecretKey) GetPublicKeyVt() (*PublicKeyVt, error) {
|
||||
result := new(bls12381.G2).Mul(new(bls12381.G2).Generator(), sk.value)
|
||||
if result.InCorrectSubgroup() == 0 || result.IsIdentity() == 1 {
|
||||
return nil, fmt.Errorf("point is not in correct subgroup")
|
||||
}
|
||||
return &PublicKeyVt{value: *result}, nil
|
||||
}
|
||||
|
||||
// Compute a signature from a secret key and message
|
||||
// This signature is deterministic which protects against
|
||||
// attacks arising from signing with bad randomness like
|
||||
// the nonce reuse attack on ECDSA. `message` is
|
||||
// hashed to a point in G1 as described in to
|
||||
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/?include_text=1
|
||||
// See Section 2.6 in https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
// nil message is not permitted but empty slice is allowed
|
||||
func (sk *SecretKey) createSignatureVt(message []byte, dstVt string) (*SignatureVt, error) {
|
||||
if message == nil {
|
||||
return nil, fmt.Errorf("message cannot be nil")
|
||||
}
|
||||
if sk.value.IsZero() == 1 {
|
||||
return nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
p1 := new(bls12381.G1).Hash(native.EllipticPointHasherSha256(), message, []byte(dstVt))
|
||||
result := new(bls12381.G1).Mul(p1, sk.value)
|
||||
if result.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("point is not in correct subgroup")
|
||||
}
|
||||
return &SignatureVt{value: *result}, nil
|
||||
}
|
||||
|
||||
// Verify a signature is valid for the message under this public key.
|
||||
// See Section 2.7 in https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (pk PublicKeyVt) verifySignatureVt(
|
||||
message []byte,
|
||||
signature *SignatureVt,
|
||||
dstVt string,
|
||||
) (bool, error) {
|
||||
if signature == nil || message == nil || pk.value.IsIdentity() == 1 {
|
||||
return false, fmt.Errorf("signature and message and public key cannot be nil or zero")
|
||||
}
|
||||
if signature.value.IsIdentity() == 1 || signature.value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
engine := new(bls12381.Engine)
|
||||
|
||||
p1 := new(bls12381.G1).Hash(native.EllipticPointHasherSha256(), message, []byte(dstVt))
|
||||
// e(H(m), pk) == e(s, g2)
|
||||
// However, we can reduce the number of miller loops
|
||||
// by doing the equivalent of
|
||||
// e(H(m)^-1, pk) * e(s, g2) == 1
|
||||
engine.AddPairInvG1(p1, &pk.value)
|
||||
engine.AddPair(&signature.value, new(bls12381.G2).Generator())
|
||||
return engine.Check(), nil
|
||||
}
|
||||
|
||||
// Combine public keys into one aggregated key
|
||||
func aggregatePublicKeysVt(pks ...*PublicKeyVt) (*PublicKeyVt, error) {
|
||||
if len(pks) < 1 {
|
||||
return nil, fmt.Errorf("at least one public key is required")
|
||||
}
|
||||
result := new(bls12381.G2).Identity()
|
||||
for i, k := range pks {
|
||||
if k == nil {
|
||||
return nil, fmt.Errorf("key at %d is nil, keys cannot be nil", i)
|
||||
}
|
||||
if k.value.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("key at %d is not in the correct subgroup", i)
|
||||
}
|
||||
result.Add(result, &k.value)
|
||||
}
|
||||
return &PublicKeyVt{value: *result}, nil
|
||||
}
|
||||
|
||||
// Combine signatures into one aggregated signature
|
||||
func aggregateSignaturesVt(sigs ...*SignatureVt) (*SignatureVt, error) {
|
||||
if len(sigs) < 1 {
|
||||
return nil, fmt.Errorf("at least one signature is required")
|
||||
}
|
||||
result := new(bls12381.G1).Identity()
|
||||
for i, s := range sigs {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("signature at %d is nil, signature cannot be nil", i)
|
||||
}
|
||||
if s.value.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("signature at %d is not in the correct subgroup", i)
|
||||
}
|
||||
result.Add(result, &s.value)
|
||||
}
|
||||
return &SignatureVt{value: *result}, nil
|
||||
}
|
||||
|
||||
// A proof of possession scheme uses a separate public key validation
|
||||
// step, called a proof of possession, to defend against rogue key
|
||||
// attacks. This enables an optimization to aggregate signature
|
||||
// verification for the case that all signatures are on the same
|
||||
// message.
|
||||
type ProofOfPossessionVt struct {
|
||||
value bls12381.G1
|
||||
}
|
||||
|
||||
// Generates a proof-of-possession (PoP) for this secret key. The PoP signature should be verified before
|
||||
// before accepting any aggregate signatures related to the corresponding pubkey.
|
||||
func (sk *SecretKey) createProofOfPossessionVt(popDstVt string) (*ProofOfPossessionVt, error) {
|
||||
pk, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := sk.createSignatureVt(msg, popDstVt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProofOfPossessionVt{value: sig.value}, nil
|
||||
}
|
||||
|
||||
// Serialize a proof of possession to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pop *ProofOfPossessionVt) MarshalBinary() ([]byte, error) {
|
||||
out := pop.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a proof of possession from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (pop *ProofOfPossessionVt) UnmarshalBinary(data []byte) error {
|
||||
p1 := new(SignatureVt)
|
||||
err := p1.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pop.value = p1.value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verifies that PoP is valid for this pubkey. In order to prevent rogue key attacks, a PoP must be validated
|
||||
// for each pubkey in an aggregated signature.
|
||||
func (pop *ProofOfPossessionVt) verify(pk *PublicKeyVt, popDstVt string) (bool, error) {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key cannot be nil")
|
||||
}
|
||||
msg, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pk.verifySignatureVt(msg, &SignatureVt{value: pop.value}, popDstVt)
|
||||
}
|
||||
|
||||
// Represents an MultiSignature in G1. A multisignature is used when multiple signatures
|
||||
// are calculated over the same message vs an aggregate signature where each message signed
|
||||
// is a unique.
|
||||
type MultiSignatureVt struct {
|
||||
value bls12381.G1
|
||||
}
|
||||
|
||||
// Serialize a multi-signature to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (sig *MultiSignatureVt) MarshalBinary() ([]byte, error) {
|
||||
out := sig.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Check a multisignature is valid for a multipublickey and a message
|
||||
func (sig *MultiSignatureVt) verify(
|
||||
pk *MultiPublicKeyVt,
|
||||
message []byte,
|
||||
signDstVt string,
|
||||
) (bool, error) {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key cannot be nil")
|
||||
}
|
||||
p := &PublicKeyVt{value: pk.value}
|
||||
return p.verifySignatureVt(message, &SignatureVt{value: sig.value}, signDstVt)
|
||||
}
|
||||
|
||||
// Deserialize a signature from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (sig *MultiSignatureVt) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SignatureVtSize {
|
||||
return fmt.Errorf("multi signature must be %v bytes", SignatureSize)
|
||||
}
|
||||
s1 := new(SignatureVt)
|
||||
err := s1.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig.value = s1.value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Represents accumulated multiple Public Keys in G2 for verifying a multisignature
|
||||
type MultiPublicKeyVt struct {
|
||||
value bls12381.G2
|
||||
}
|
||||
|
||||
// Serialize a public key to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pk *MultiPublicKeyVt) MarshalBinary() ([]byte, error) {
|
||||
out := pk.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a public key from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the public key
|
||||
// otherwise it will return an error
|
||||
func (pk *MultiPublicKeyVt) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != PublicKeyVtSize {
|
||||
return fmt.Errorf("multi public key must be %v bytes", PublicKeySize)
|
||||
}
|
||||
p2 := new(PublicKeyVt)
|
||||
err := p2.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = p2.value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check a multisignature is valid for a multipublickey and a message
|
||||
func (pk *MultiPublicKeyVt) verify(
|
||||
message []byte,
|
||||
sig *MultiSignatureVt,
|
||||
signDstVt string,
|
||||
) (bool, error) {
|
||||
return sig.verify(pk, message, signDstVt)
|
||||
}
|
||||
|
||||
// PartialSignatureVt represents threshold Gap Diffie-Hellman BLS signature
|
||||
// that can be combined with other partials to yield a completed BLS signature
|
||||
// See section 3.2 in <https://www.cc.gatech.edu/~aboldyre/papers/bold.pdf>
|
||||
type PartialSignatureVt struct {
|
||||
identifier byte
|
||||
signature bls12381.G1
|
||||
}
|
||||
|
||||
// partialSignVt creates a partial signature that can be combined with other partial signatures
|
||||
// to yield a complete signature
|
||||
func (sks *SecretKeyShare) partialSignVt(
|
||||
message []byte,
|
||||
signDst string,
|
||||
) (*PartialSignatureVt, error) {
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("message cannot be empty or nil")
|
||||
}
|
||||
p1 := new(bls12381.G1).Hash(native.EllipticPointHasherSha256(), message, []byte(signDst))
|
||||
|
||||
var blob [SecretKeySize]byte
|
||||
copy(blob[:], internal.ReverseScalarBytes(sks.value))
|
||||
s, err := bls12381.Bls12381FqNew().SetBytes(&blob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(bls12381.G1).Mul(p1, s)
|
||||
if result.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("point is not on correct subgroup")
|
||||
}
|
||||
return &PartialSignatureVt{identifier: sks.identifier, signature: *result}, nil
|
||||
}
|
||||
|
||||
// combineSigsVt gathers partial signatures and yields a complete signature
|
||||
func combineSigsVt(partials []*PartialSignatureVt) (*SignatureVt, error) {
|
||||
if len(partials) < 2 {
|
||||
return nil, fmt.Errorf("must have at least 2 partial signatures")
|
||||
}
|
||||
if len(partials) > 255 {
|
||||
return nil, fmt.Errorf("unsupported to combine more than 255 signatures")
|
||||
}
|
||||
|
||||
xVars, yVars, err := splitXYVt(partials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sTmp := new(bls12381.G1).Identity()
|
||||
sig := new(bls12381.G1).Identity()
|
||||
|
||||
// Lagrange interpolation
|
||||
basis := bls12381.Bls12381FqNew()
|
||||
for i, xi := range xVars {
|
||||
basis.SetOne()
|
||||
|
||||
for j, xj := range xVars {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
|
||||
num := bls12381.Bls12381FqNew().Neg(xj) // x - x_m
|
||||
den := bls12381.Bls12381FqNew().Sub(xi, xj) // x_j - x_m
|
||||
_, wasInverted := den.Invert(den)
|
||||
// wasInverted == false if den == 0
|
||||
if !wasInverted {
|
||||
return nil, fmt.Errorf("signatures cannot be recombined")
|
||||
}
|
||||
basis.Mul(basis, num.Mul(num, den))
|
||||
}
|
||||
sTmp.Mul(yVars[i], basis)
|
||||
sig.Add(sig, sTmp)
|
||||
}
|
||||
if sig.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
|
||||
return &SignatureVt{value: *sig}, nil
|
||||
}
|
||||
|
||||
// Ensure no duplicates x values and convert x values to field elements
|
||||
func splitXYVt(partials []*PartialSignatureVt) ([]*native.Field, []*bls12381.G1, error) {
|
||||
x := make([]*native.Field, len(partials))
|
||||
y := make([]*bls12381.G1, len(partials))
|
||||
|
||||
dup := make(map[byte]bool)
|
||||
|
||||
for i, sp := range partials {
|
||||
if sp == nil {
|
||||
return nil, nil, fmt.Errorf("partial signature cannot be nil")
|
||||
}
|
||||
if _, exists := dup[sp.identifier]; exists {
|
||||
return nil, nil, fmt.Errorf("duplicate signature included")
|
||||
}
|
||||
if sp.signature.InCorrectSubgroup() == 0 {
|
||||
return nil, nil, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
dup[sp.identifier] = true
|
||||
x[i] = bls12381.Bls12381FqNew().SetUint64(uint64(sp.identifier))
|
||||
y[i] = &sp.signature
|
||||
}
|
||||
return x, y, nil
|
||||
}
|
||||
389
signatures/bls/bls_sig/tiny_bls_sig_aug_test.go
Normal file
389
signatures/bls/bls_sig/tiny_bls_sig_aug_test.go
Normal file
@@ -0,0 +1,389 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
func generateAugSignatureG1(sk *SecretKey, msg []byte, t *testing.T) *SignatureVt {
|
||||
bls := NewSigAugVt()
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Sign failed")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func generateAugAggregateDataG1(t *testing.T) ([]*PublicKeyVt, []*SignatureVt, [][]byte) {
|
||||
msgs := make([][]byte, numAggregateG1)
|
||||
pks := make([]*PublicKeyVt, numAggregateG1)
|
||||
sigs := make([]*SignatureVt, numAggregateG1)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAugVt()
|
||||
|
||||
for i := 0; i < numAggregateG1; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
msg := make([]byte, 20)
|
||||
readRand(msg, t)
|
||||
sig := generateAugSignatureG1(sk, msg, t)
|
||||
msgs[i] = msg
|
||||
sigs[i] = sig
|
||||
pks[i] = pk
|
||||
}
|
||||
return pks, sigs, msgs
|
||||
}
|
||||
|
||||
func TestAugKeyGenG1Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAugVt()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugKeyGenG1Fail(t *testing.T) {
|
||||
ikm := make([]byte, 10)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAugVt()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Aug KeyGen succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugCustomDstG1(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAugVtWithDst("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_TEST")
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Custom Dst KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Custom Dst Sign failed")
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Aug Custom Dst Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Aug Custom Dst Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugSigningG1(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAugVt()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig := generateAugSignatureG1(sk, ikm, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Aug Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Aug Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG1Works(t *testing.T) {
|
||||
pks, sigs, msgs := generateAugAggregateDataG1(t)
|
||||
bls := NewSigAugVt()
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug AggregateVerify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG1BadPks(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
pks, sigs, msgs := generateAugAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug AggregateVerify failed")
|
||||
}
|
||||
|
||||
pks[0] = pks[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug AggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
pkValue := new(bls12381.G2).Identity()
|
||||
pks[0] = &PublicKeyVt{value: *pkValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug AggregateVerify succeeded with zero byte public key it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
pkValue.Generator()
|
||||
pks[0] = &PublicKeyVt{value: *pkValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Aug aggregateVerify succeeded with the base generator public key it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG1BadSigs(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
pks, sigs, msgs := generateAugAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed")
|
||||
}
|
||||
|
||||
sigs[0] = sigs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero signature to make sure it doesn't crash
|
||||
sigValue := new(bls12381.G1).Identity()
|
||||
sigs[0] = &SignatureVt{value: *sigValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded with zero byte signature it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
sigValue.Generator()
|
||||
sigs[0] = &SignatureVt{value: *sigValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Aug aggregateVerify succeeded with the base generator signature it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG1BadMsgs(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
pks, sigs, msgs := generateAugAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed")
|
||||
}
|
||||
|
||||
// Test len(pks) != len(msgs)
|
||||
if res, _ := bls.AggregateVerify(pks, msgs[0:8], sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
msgs[0] = msgs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG1DupMsg(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
|
||||
// Only two messages but repeated
|
||||
messages := make([][]byte, numAggregateG1)
|
||||
messages[0] = []byte("Yes")
|
||||
messages[1] = []byte("No")
|
||||
for i := 2; i < numAggregateG1; i++ {
|
||||
messages[i] = messages[i%2]
|
||||
}
|
||||
|
||||
pks := make([]*PublicKeyVt, numAggregateG1)
|
||||
sigs := make([]*SignatureVt, numAggregateG1)
|
||||
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < numAggregateG1; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
sig := generateAugSignatureG1(sk, messages[i], t)
|
||||
pks[i] = pk
|
||||
sigs[i] = sig
|
||||
}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed for duplicate messages")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsAugG1KeyGen(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
_, _, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugVtThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(1, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugVtThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugPartialSignVt(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAugVt()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], pk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], pk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestAugVtPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAugVt()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignatureVt, total)
|
||||
sigs2 := make([]*PartialSignatureVt, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], pk1, msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], pk2, msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyMessageAugPartialSignVt(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
pk, sks, err := bls.ThresholdKeygen(5, 6)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
|
||||
// Sign an empty message
|
||||
_, err = bls.PartialSign(sks[0], pk, []byte{})
|
||||
if err == nil {
|
||||
t.Errorf("Expected partial sign to fail on empty message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilMessageAugPartialSignVt(t *testing.T) {
|
||||
bls := NewSigAugVt()
|
||||
pk, sks, err := bls.ThresholdKeygen(5, 6)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
|
||||
// Sign nil message
|
||||
_, err = bls.PartialSign(sks[0], pk, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected partial signing to fail on nil message")
|
||||
}
|
||||
}
|
||||
385
signatures/bls/bls_sig/tiny_bls_sig_basic_test.go
Normal file
385
signatures/bls/bls_sig/tiny_bls_sig_basic_test.go
Normal file
@@ -0,0 +1,385 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
func generateBasicSignatureG1(sk *SecretKey, msg []byte, t *testing.T) *SignatureVt {
|
||||
bls := NewSigBasicVt()
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Sign failed")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func generateBasicAggregateDataG1(t *testing.T) ([]*PublicKeyVt, []*SignatureVt, [][]byte) {
|
||||
msgs := make([][]byte, numAggregateG1)
|
||||
pks := make([]*PublicKeyVt, numAggregateG1)
|
||||
sigs := make([]*SignatureVt, numAggregateG1)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasicVt()
|
||||
|
||||
for i := 0; i < numAggregateG1; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
msg := make([]byte, 20)
|
||||
readRand(msg, t)
|
||||
sig := generateBasicSignatureG1(sk, msg, t)
|
||||
msgs[i] = msg
|
||||
sigs[i] = sig
|
||||
pks[i] = pk
|
||||
}
|
||||
return pks, sigs, msgs
|
||||
}
|
||||
|
||||
func TestBasicKeyGenG1Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasicVt()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicKeyGenG1Fail(t *testing.T) {
|
||||
ikm := make([]byte, 10)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasicVt()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Basic KeyGen succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicCustomDstG1(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasicVtWithDst("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_TEST")
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Custom Dst KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Custom Dst Sign failed")
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Basic Custon Dst Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Basic Custom Dst Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningG1(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasicVt()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig := generateBasicSignatureG1(sk, ikm, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Basic Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Basic Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningEmptyMessage(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign an empty message
|
||||
_, err = bls.Sign(sk, []byte{})
|
||||
if err != nil {
|
||||
t.Errorf("Expected signing message to succeed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningNilMessage(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign nil message
|
||||
_, err = bls.Sign(sk, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected signing empty message to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG1Works(t *testing.T) {
|
||||
pks, sigs, msgs := generateBasicAggregateDataG1(t)
|
||||
bls := NewSigBasicVt()
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic AggregateVerify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG1BadPks(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic aggregateVerify failed")
|
||||
}
|
||||
|
||||
pks[0] = pks[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
pkValue := new(bls12381.G2).Identity()
|
||||
pks[0] = &PublicKeyVt{value: *pkValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded with zero byte public key it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
pkValue.Generator()
|
||||
pks[0] = &PublicKeyVt{value: *pkValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Basic aggregateVerify succeeded with the base generator public key it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG1BadSigs(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic aggregateVerify failed")
|
||||
}
|
||||
|
||||
sigs[0] = sigs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
g1 := new(bls12381.G1).Identity()
|
||||
sigValue := g1.Identity()
|
||||
sigs[0] = &SignatureVt{value: *sigValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded with zero byte signature it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
sigValue = g1.Generator()
|
||||
sigs[0] = &SignatureVt{value: *sigValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Basic aggregateVerify succeeded with the base generator signature it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG1BadMsgs(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG1(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic aggregateVerify failed")
|
||||
}
|
||||
|
||||
msgs[0] = msgs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicVtThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(0, 1)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicVtThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicPartialSignVt(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasicVt()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures succeeded when it should've failed")
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestBasicVtPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasicVt()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignatureVt, total)
|
||||
sigs2 := make([]*PartialSignatureVt, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityPublicKeyVt(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
msg := []byte{0, 0, 0, 0}
|
||||
sig, _ := bls.Sign(sk, msg)
|
||||
pk := PublicKeyVt{value: *new(bls12381.G2).Identity()}
|
||||
if res, _ := bls.Verify(&pk, msg, sig); res {
|
||||
t.Errorf("Verify succeeded when the public key is the identity.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestThresholdSignTooHighAndLowVt(t *testing.T) {
|
||||
bls := NewSigBasicVt()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
|
||||
ps, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
|
||||
_, err = bls.CombineSignatures(ps)
|
||||
if err == nil {
|
||||
t.Errorf("CombinSignatures succeeded when it should've failed")
|
||||
}
|
||||
|
||||
pss := make([]*PartialSignatureVt, 256)
|
||||
|
||||
_, err = bls.CombineSignatures(pss...)
|
||||
if err == nil {
|
||||
t.Errorf("CombinSignatures succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
962
signatures/bls/bls_sig/tiny_bls_sig_pop_test.go
Normal file
962
signatures/bls/bls_sig/tiny_bls_sig_pop_test.go
Normal file
@@ -0,0 +1,962 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
const numAggregateG1 = 10
|
||||
|
||||
func TestGetPublicKeyG2(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
actual := marshalStruct(pk, t)
|
||||
expected := []byte{
|
||||
175,
|
||||
76,
|
||||
33,
|
||||
103,
|
||||
184,
|
||||
172,
|
||||
12,
|
||||
111,
|
||||
24,
|
||||
87,
|
||||
84,
|
||||
61,
|
||||
243,
|
||||
82,
|
||||
99,
|
||||
76,
|
||||
131,
|
||||
95,
|
||||
171,
|
||||
237,
|
||||
145,
|
||||
143,
|
||||
7,
|
||||
93,
|
||||
205,
|
||||
148,
|
||||
104,
|
||||
29,
|
||||
153,
|
||||
103,
|
||||
187,
|
||||
206,
|
||||
112,
|
||||
223,
|
||||
252,
|
||||
198,
|
||||
102,
|
||||
41,
|
||||
38,
|
||||
244,
|
||||
228,
|
||||
223,
|
||||
102,
|
||||
16,
|
||||
216,
|
||||
152,
|
||||
231,
|
||||
250,
|
||||
7,
|
||||
111,
|
||||
90,
|
||||
98,
|
||||
194,
|
||||
244,
|
||||
101,
|
||||
251,
|
||||
69,
|
||||
130,
|
||||
11,
|
||||
209,
|
||||
41,
|
||||
210,
|
||||
133,
|
||||
105,
|
||||
217,
|
||||
179,
|
||||
190,
|
||||
1,
|
||||
6,
|
||||
155,
|
||||
135,
|
||||
2,
|
||||
168,
|
||||
249,
|
||||
253,
|
||||
41,
|
||||
59,
|
||||
87,
|
||||
8,
|
||||
49,
|
||||
231,
|
||||
198,
|
||||
142,
|
||||
30,
|
||||
186,
|
||||
44,
|
||||
175,
|
||||
17,
|
||||
198,
|
||||
63,
|
||||
210,
|
||||
176,
|
||||
237,
|
||||
171,
|
||||
11,
|
||||
127,
|
||||
}
|
||||
if !bytes.Equal(actual, expected) {
|
||||
t.Errorf("Expected GetPublicKeyVt to pass but failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func testSignG1(message []byte, t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignatureVt(sk, message, t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
|
||||
bls := NewSigPopVt()
|
||||
|
||||
if res, _ := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("createSignatureVt failed when it should've passed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignG1EmptyNilMessage(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
bls := NewSigPopVt()
|
||||
sig, _ := bls.Sign(sk, nil)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, nil, sig); res {
|
||||
t.Errorf("createSignature succeeded when it should've failed")
|
||||
}
|
||||
|
||||
message := []byte{}
|
||||
sig = genSignatureVt(sk, message, t)
|
||||
|
||||
if res, err := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("create and verify failed on empty message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignG1OneByteMessage(t *testing.T) {
|
||||
message := []byte{1}
|
||||
testSignG1(message, t)
|
||||
}
|
||||
|
||||
func TestSignG1LargeMessage(t *testing.T) {
|
||||
message := make([]byte, 1048576)
|
||||
testSignG1(message, t)
|
||||
}
|
||||
|
||||
func TestSignG1RandomMessage(t *testing.T) {
|
||||
message := make([]byte, 65537)
|
||||
readRand(message, t)
|
||||
testSignG1(message, t)
|
||||
}
|
||||
|
||||
func TestSignG1BadMessage(t *testing.T) {
|
||||
message := make([]byte, 1024)
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignatureVt(sk, message, t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
message = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
|
||||
|
||||
bls := NewSigPopVt()
|
||||
|
||||
if res, _ := bls.Verify(pk, message, sig); res {
|
||||
t.Errorf("Expected signature to not verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadConversionsG1(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
|
||||
sig := genSignatureVt(sk, message, t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
|
||||
bls := NewSigPopVt()
|
||||
|
||||
if res, _ := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("Signature should be valid")
|
||||
}
|
||||
if res, _ := sig.verify(pk, message, blsSignaturePopVtDst); !res {
|
||||
t.Errorf("Signature should be valid")
|
||||
}
|
||||
|
||||
// Convert public key to signature in G2
|
||||
sig2 := new(Signature)
|
||||
err := sig2.UnmarshalBinary(marshalStruct(pk, t))
|
||||
if err != nil {
|
||||
t.Errorf("Should be able to convert to signature in G2")
|
||||
}
|
||||
pk2 := new(PublicKey)
|
||||
err = pk2.UnmarshalBinary(marshalStruct(sig, t))
|
||||
if err != nil {
|
||||
t.Errorf("Should be able to convert to public key in G1")
|
||||
}
|
||||
|
||||
res, _ := pk2.verifySignature(message, sig2, blsSignaturePopVtDst)
|
||||
if res {
|
||||
t.Errorf("The signature shouldn't verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregatePublicKeysG2(t *testing.T) {
|
||||
pks := []*PublicKeyVt{}
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < 20; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
pks = append(pks, pk)
|
||||
}
|
||||
apk1, err := aggregatePublicKeysVt(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
rng := rand.New(rand.NewSource(1234567890))
|
||||
rng.Shuffle(len(pks), func(i, j int) { pks[i], pks[j] = pks[j], pks[i] })
|
||||
apk2, err := aggregatePublicKeysVt(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(apk1, t), marshalStruct(apk2, t)) {
|
||||
t.Errorf("Aggregated public keys should be equal")
|
||||
}
|
||||
rand.Shuffle(len(pks), func(i, j int) { pks[i], pks[j] = pks[j], pks[i] })
|
||||
apk1, err = aggregatePublicKeysVt(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(apk1, t), marshalStruct(apk2, t)) {
|
||||
t.Errorf("Aggregated public keys should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateSignaturesG1(t *testing.T) {
|
||||
sigs := []*SignatureVt{}
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < 20; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
sig := genSignatureVt(sk, ikm, t)
|
||||
sigs = append(sigs, sig)
|
||||
}
|
||||
asig1, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
rng2 := rand.New(rand.NewSource(1234567890))
|
||||
rng2.Shuffle(len(sigs), func(i, j int) { sigs[i], sigs[j] = sigs[j], sigs[i] })
|
||||
asig2, err := aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(asig1, t), marshalStruct(asig2, t)) {
|
||||
t.Errorf("Aggregated signatures should be equal")
|
||||
}
|
||||
rand.Shuffle(len(sigs), func(i, j int) { sigs[i], sigs[j] = sigs[j], sigs[i] })
|
||||
asig1, err = aggregateSignaturesVt(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(asig1, t), marshalStruct(asig2, t)) {
|
||||
t.Errorf("Aggregated signatures should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func initAggregatedTestValuesG1(messages [][]byte, t *testing.T) ([]*PublicKeyVt, []*SignatureVt) {
|
||||
pks := []*PublicKeyVt{}
|
||||
sigs := []*SignatureVt{}
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < numAggregateG1; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
sig := genSignatureVt(sk, messages[i%len(messages)], t)
|
||||
sigs = append(sigs, sig)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
pks = append(pks, pk)
|
||||
}
|
||||
return pks, sigs
|
||||
}
|
||||
|
||||
func TestAggregatedFunctionalityG1(t *testing.T) {
|
||||
message := make([]byte, 20)
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
|
||||
bls := NewSigPopVt()
|
||||
asig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); !res {
|
||||
t.Errorf("Should verify aggregated signatures with same message")
|
||||
}
|
||||
if res, _ := asig.verify(apk, message, blsSignaturePopVtDst); !res {
|
||||
t.Errorf("MultiSignature.verify failed.")
|
||||
}
|
||||
if res, _ := apk.verify(message, asig, blsSignaturePopVtDst); !res {
|
||||
t.Errorf("MultiPublicKey.verify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadAggregatedFunctionalityG1(t *testing.T) {
|
||||
message := make([]byte, 20)
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
|
||||
bls := NewSigPopVt()
|
||||
|
||||
apk, err := bls.AggregatePublicKeys(pks[2:]...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
asig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); res {
|
||||
t.Errorf(
|
||||
"Should not verify aggregated signatures with same message when some public keys are missing",
|
||||
)
|
||||
}
|
||||
|
||||
apk, err = bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
asig, err = bls.AggregateSignatures(sigs[2:]...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); res {
|
||||
t.Errorf(
|
||||
"Should not verify aggregated signatures with same message when some signatures are missing",
|
||||
)
|
||||
}
|
||||
|
||||
asig, err = bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
badmsg := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, badmsg, asig); res {
|
||||
t.Errorf("Should not verify aggregated signature with bad message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG1Pass(t *testing.T) {
|
||||
messages := make([][]byte, numAggregateG1)
|
||||
for i := 0; i < numAggregateG1; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); !res {
|
||||
t.Errorf("Expected aggregateVerify to pass but failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG1MsgSigCntMismatch(t *testing.T) {
|
||||
messages := make([][]byte, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); res {
|
||||
t.Errorf("Expected AggregateVerifyG1 to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG1FailDupMsg(t *testing.T) {
|
||||
messages := make([][]byte, 10)
|
||||
for i := 0; i < 9; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
// Duplicate message
|
||||
messages[9] = messages[0]
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); res {
|
||||
t.Errorf("Expected aggregateVerify to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG1FailIncorrectMsg(t *testing.T) {
|
||||
messages := make([][]byte, 10)
|
||||
for i := 0; i < 9; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
// Duplicate message
|
||||
messages[9] = messages[0]
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
if res, _ := bls.AggregateVerify(pks[2:], messages[2:], sigs); res {
|
||||
t.Errorf("Expected aggregateVerify to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG1OneMsg(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = make([]byte, 20)
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignatureVt(sk, messages[0], t)
|
||||
pk := genPublicKeyVt(sk, t)
|
||||
bls := NewSigPopVt()
|
||||
// Should be the same as verifySignatureVt
|
||||
if res, _ := bls.AggregateVerify([]*PublicKeyVt{pk}, messages, []*SignatureVt{sig}); !res {
|
||||
t.Errorf("Expected AggregateVerifyG1OneMsg to pass but failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyG1Mutability(t *testing.T) {
|
||||
// verify should not change any inputs
|
||||
ikm := make([]byte, 32)
|
||||
ikm_copy := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
copy(ikm_copy, ikm)
|
||||
bls := NewSigPopVt()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPopVt.KeygenWithSeed modifies ikm")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Expected KeygenWithSeed to succeed but failed.")
|
||||
}
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPopVt.Sign modifies message")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("SigPopVt.KeygenWithSeed to succeed but failed.")
|
||||
}
|
||||
sigCopy := marshalStruct(sig, t)
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Expected verify to succeed but failed.")
|
||||
}
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPopVt.verify modifies message")
|
||||
}
|
||||
if !bytes.Equal(sigCopy, marshalStruct(sig, t)) {
|
||||
t.Errorf("SigPopVt.verify modifies signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyG2FromBadBytes(t *testing.T) {
|
||||
pk := make([]byte, 32)
|
||||
err := new(PublicKeyVt).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG2FromBytes to fail but passed")
|
||||
}
|
||||
// All zeros
|
||||
pk = make([]byte, PublicKeyVtSize)
|
||||
// See https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// 1 << 7 == compressed
|
||||
// 1 << 6 == infinity or zero
|
||||
pk[0] = 0xc0
|
||||
err = new(PublicKeyVt).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG2FromBytes to fail but passed")
|
||||
}
|
||||
sk := genSecretKey(t)
|
||||
pk1, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetPublicKeyVt to pass but failed.")
|
||||
}
|
||||
out := marshalStruct(pk1, t)
|
||||
out[3] += 1
|
||||
err = new(PublicKeyVt).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG2FromBytes to fail but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureG1FromBadBytes(t *testing.T) {
|
||||
sig := make([]byte, 32)
|
||||
err := new(SignatureVt).UnmarshalBinary(sig)
|
||||
if err == nil {
|
||||
t.Errorf("Expected SignatureG1FromBytes to fail but passed")
|
||||
}
|
||||
// All zeros
|
||||
sig = make([]byte, SignatureVtSize)
|
||||
// See https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// 1 << 7 == compressed
|
||||
// 1 << 6 == infinity or zero
|
||||
sig[0] = 0xc0
|
||||
err = new(SignatureVt).UnmarshalBinary(sig)
|
||||
if err == nil {
|
||||
t.Errorf("Expected SignatureG1FromBytes to fail but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecretKeyG1(t *testing.T) {
|
||||
sk := &SecretKey{value: bls12381.Bls12381FqNew()}
|
||||
pk, err := sk.GetPublicKeyVt()
|
||||
if err == nil {
|
||||
t.Errorf("Expected GetPublicKeyVt to fail with 0 byte secret key but passed: %v", pk)
|
||||
}
|
||||
_ = sk.UnmarshalBinary(sk.value.Params.BiModulus.Bytes())
|
||||
pk, err = sk.GetPublicKeyVt()
|
||||
if err == nil {
|
||||
t.Errorf("Expected GetPublicKeyVt to fail with secret key with Q but passed: %v", pk)
|
||||
}
|
||||
|
||||
err = sk.UnmarshalBinary([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
|
||||
if err == nil {
|
||||
t.Errorf("Expected SecretKeyFromBytes to fail with not enough bytes but passed: %v", pk)
|
||||
}
|
||||
|
||||
err = sk.UnmarshalBinary(make([]byte, 32))
|
||||
if err == nil {
|
||||
t.Errorf("Expected SecretKeyFromBytes to fail but passed: %v", pk)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG1Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigPopVt()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Key gen failed but should've succeeded")
|
||||
}
|
||||
pop, err := bls.PopProve(sk)
|
||||
if err != nil {
|
||||
t.Errorf("PopProve failed but should've succeeded")
|
||||
}
|
||||
if res, _ := bls.PopVerify(pk, pop); !res {
|
||||
t.Errorf("PopVerify failed but should've succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG1FromBadKey(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
value := new(big.Int)
|
||||
value.SetBytes(ikm)
|
||||
sk := SecretKey{value: bls12381.Bls12381FqNew().SetBigInt(value)}
|
||||
_, err := sk.createProofOfPossessionVt(blsSignaturePopVtDst)
|
||||
if err == nil {
|
||||
t.Errorf("createProofOfPossessionVt should've failed but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG1BytesWorks(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pop, err := sk.createProofOfPossessionVt(blsSignaturePopVtDst)
|
||||
if err != nil {
|
||||
t.Errorf("CreateProofOfPossesionG1 failed but shouldn've succeeded.")
|
||||
}
|
||||
out := marshalStruct(pop, t)
|
||||
if len(out) != ProofOfPossessionVtSize {
|
||||
t.Errorf(
|
||||
"ProofOfPossessionBytes incorrect size: expected %v, got %v",
|
||||
ProofOfPossessionVtSize,
|
||||
len(out),
|
||||
)
|
||||
}
|
||||
pop2 := new(ProofOfPossessionVt)
|
||||
err = pop2.UnmarshalBinary(out)
|
||||
if err != nil {
|
||||
t.Errorf("ProofOfPossessionVt.UnmarshalBinary failed: %v", err)
|
||||
}
|
||||
out2 := marshalStruct(pop2, t)
|
||||
if !bytes.Equal(out, out2) {
|
||||
t.Errorf("ProofOfPossessionVt.UnmarshalBinary failed, not equal when deserialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG1BadBytes(t *testing.T) {
|
||||
zeros := make([]byte, ProofOfPossessionVtSize)
|
||||
temp := new(ProofOfPossessionVt)
|
||||
err := temp.UnmarshalBinary(zeros)
|
||||
if err == nil {
|
||||
t.Errorf("ProofOfPossessionVt.UnmarshalBinary shouldn've failed but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG1Fails(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pop, err := sk.createProofOfPossessionVt(blsSignaturePopVtDst)
|
||||
if err != nil {
|
||||
t.Errorf("Expected createProofOfPossessionVt to succeed but failed.")
|
||||
}
|
||||
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
sk = genRandSecretKey(ikm, t)
|
||||
bad, err := sk.GetPublicKeyVt()
|
||||
if err != nil {
|
||||
t.Errorf("Expected PublicKeyG2FromBytes to succeed but failed: %v", err)
|
||||
}
|
||||
if res, _ := pop.verify(bad, blsSignaturePopVtDst); res {
|
||||
t.Errorf("Expected ProofOfPossession verify to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSigG1Bytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
_, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
msig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
msigBytes := marshalStruct(msig, t)
|
||||
if len(msigBytes) != SignatureVtSize {
|
||||
t.Errorf(
|
||||
"Invalid multi-sig length. Expected %d bytes, found %d",
|
||||
SignatureVtSize,
|
||||
len(msigBytes),
|
||||
)
|
||||
}
|
||||
msig2 := new(MultiSignatureVt)
|
||||
err = msig2.UnmarshalBinary(msigBytes)
|
||||
if err != nil {
|
||||
t.Errorf("MultiSignatureG1FromBytes failed with %v", err)
|
||||
}
|
||||
msigBytes2 := marshalStruct(msig2, t)
|
||||
|
||||
if !bytes.Equal(msigBytes, msigBytes2) {
|
||||
t.Errorf("Bytes methods not equal.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSigG1BadBytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
_, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
msig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
msigBytes := marshalStruct(msig, t)
|
||||
if len(msigBytes) != SignatureVtSize {
|
||||
t.Errorf(
|
||||
"Invalid multi-sig length. Expected %d bytes, found %d",
|
||||
SignatureSize,
|
||||
len(msigBytes),
|
||||
)
|
||||
}
|
||||
msigBytes[0] = 0
|
||||
temp := new(MultiSignatureVt)
|
||||
err = temp.UnmarshalBinary(msigBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiSignatureG1FromBytes should've failed but succeeded")
|
||||
}
|
||||
msigBytes = make([]byte, SignatureVtSize)
|
||||
err = temp.UnmarshalBinary(msigBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiSignatureG1FromBytes should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPubkeyG2Bytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
pks, _ := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apkBytes := marshalStruct(apk, t)
|
||||
if len(apkBytes) != PublicKeyVtSize {
|
||||
t.Errorf("MultiPublicKeyVt has an incorrect size")
|
||||
}
|
||||
apk2 := new(MultiPublicKeyVt)
|
||||
err = apk2.UnmarshalBinary(apkBytes)
|
||||
if err != nil {
|
||||
t.Errorf("MultiPublicKeyVt.UnmarshalBinary failed with %v", err)
|
||||
}
|
||||
apk2Bytes := marshalStruct(apk2, t)
|
||||
if !bytes.Equal(apkBytes, apk2Bytes) {
|
||||
t.Errorf("Bytes methods not equal.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPubkeyG2BadBytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
pks, _ := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apkBytes := marshalStruct(apk, t)
|
||||
if len(apkBytes) != PublicKeyVtSize {
|
||||
t.Errorf("MultiPublicKeyVt has an incorrect size")
|
||||
}
|
||||
apkBytes[0] = 0
|
||||
temp := new(MultiPublicKeyVt)
|
||||
err = temp.UnmarshalBinary(apkBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiPublicKeyVt.UnmarshalBinary should've failed but succeeded")
|
||||
}
|
||||
apkBytes = make([]byte, PublicKeyVtSize)
|
||||
err = temp.UnmarshalBinary(apkBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiPublicKeyVt.UnmarshalBinary should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyConstituentG1Works(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, message, sigs); !res {
|
||||
t.Errorf("FastAggregateVerify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyG1Works(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
asig, _ := aggregateSignaturesVt(sigs...)
|
||||
bls := NewSigPopVt()
|
||||
|
||||
if res, _ := bls.FastAggregateVerify(pks, message, asig); !res {
|
||||
t.Errorf("FastAggregateVerify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyG1Fails(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG1(messages, t)
|
||||
bls := NewSigPopVt()
|
||||
message[0] = 1
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, message, sigs); res {
|
||||
t.Errorf("FastAggregateVerify verified when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomPopDstG1Works(t *testing.T) {
|
||||
bls, _ := NewSigPopVtWithDst("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_TEST",
|
||||
"BLS_POP_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_TEST")
|
||||
msg := make([]byte, 20)
|
||||
ikm := make([]byte, 32)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't create custom dst keys: %v", err)
|
||||
}
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't sign with custom dst: %v", err)
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("verify fails with custom dst")
|
||||
}
|
||||
|
||||
pks := make([]*PublicKeyVt, 10)
|
||||
sigs := make([]*SignatureVt, 10)
|
||||
pks[0] = pk
|
||||
sigs[0] = sig
|
||||
for i := 1; i < 10; i++ {
|
||||
readRand(ikm, t)
|
||||
pkt, skt, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't create custom dst keys: %v", err)
|
||||
}
|
||||
sigt, err := bls.Sign(skt, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't sign with custom dst: %v", err)
|
||||
}
|
||||
pks[i] = pkt
|
||||
sigs[i] = sigt
|
||||
}
|
||||
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, msg, sigs); !res {
|
||||
t.Errorf("FastAggregateVerify failed with custom dst")
|
||||
}
|
||||
|
||||
pop, err := bls.PopProve(sk)
|
||||
if err != nil {
|
||||
t.Errorf("PopProve failed with custom dst")
|
||||
}
|
||||
|
||||
if res, _ := bls.PopVerify(pk, pop); !res {
|
||||
t.Errorf("PopVerify failed with custom dst")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsPopG1KeyGenWithSeed(t *testing.T) {
|
||||
ikm := []byte("Not enough bytes")
|
||||
bls := NewSigPopVt()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Expected KeygenWithSeed to fail but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsPopG1KeyGen(t *testing.T) {
|
||||
bls := NewSigPopVt()
|
||||
_, _, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopVtThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigPopVt()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(1, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopVtThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigPopVt()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopPartialSignVt(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigPopVt()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures succeeded when it should've failed")
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestPopVtPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigPopVt()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignatureVt, total)
|
||||
sigs2 := make([]*PartialSignatureVt, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
478
signatures/bls/bls_sig/usual_bls.go
Executable file
478
signatures/bls/bls_sig/usual_bls.go
Executable file
@@ -0,0 +1,478 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// Domain separation tag for basic signatures
|
||||
// according to section 4.2.1 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignatureBasicDst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"
|
||||
// Domain separation tag for basic signatures
|
||||
// according to section 4.2.2 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignatureAugDst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_"
|
||||
// Domain separation tag for proof of possession signatures
|
||||
// according to section 4.2.3 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsSignaturePopDst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
|
||||
// Domain separation tag for proof of possession proofs
|
||||
// according to section 4.2.3 in
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
blsPopProofDst = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
|
||||
)
|
||||
|
||||
type BlsScheme interface {
|
||||
Keygen() (*PublicKey, *SecretKey, error)
|
||||
KeygenWithSeed(ikm []byte) (*PublicKey, *SecretKey, error)
|
||||
Sign(sk *SecretKey, msg []byte) (*Signature, error)
|
||||
Verify(pk *PublicKey, msg []byte, sig *Signature) bool
|
||||
AggregateVerify(pks []*PublicKey, msgs [][]byte, sigs []*Signature) bool
|
||||
}
|
||||
|
||||
// generateKeys creates 32 bytes of random data to be fed to
|
||||
// generateKeysWithSeed
|
||||
func generateKeys() (*PublicKey, *SecretKey, error) {
|
||||
ikm, err := generateRandBytes(32)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return generateKeysWithSeed(ikm)
|
||||
}
|
||||
|
||||
// generateKeysWithSeed generates a BLS key pair given input key material (ikm)
|
||||
func generateKeysWithSeed(ikm []byte) (*PublicKey, *SecretKey, error) {
|
||||
sk, err := new(SecretKey).Generate(ikm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pk, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// thresholdGenerateKeys will generate random secret key shares and the corresponding public key
|
||||
func thresholdGenerateKeys(threshold, total uint) (*PublicKey, []*SecretKeyShare, error) {
|
||||
pk, sk, err := generateKeys()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
shares, err := thresholdizeSecretKey(sk, threshold, total)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, shares, nil
|
||||
}
|
||||
|
||||
// thresholdGenerateKeysWithSeed will generate random secret key shares and the corresponding public key
|
||||
// using the corresponding seed `ikm`
|
||||
func thresholdGenerateKeysWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKey, []*SecretKeyShare, error) {
|
||||
pk, sk, err := generateKeysWithSeed(ikm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
shares, err := thresholdizeSecretKey(sk, threshold, total)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return pk, shares, nil
|
||||
}
|
||||
|
||||
// SigBasic is minimal-pubkey-size scheme that doesn't support FastAggregateVerificiation.
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.1
|
||||
type SigBasic struct {
|
||||
dst string
|
||||
}
|
||||
|
||||
// Creates a new BLS basic signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigBasic() *SigBasic {
|
||||
return &SigBasic{dst: blsSignatureBasicDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS basic signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigBasicWithDst(signDst string) *SigBasic {
|
||||
return &SigBasic{dst: signDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigBasic) Keygen() (*PublicKey, *SecretKey, error) {
|
||||
return generateKeys()
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigBasic) KeygenWithSeed(ikm []byte) (*PublicKey, *SecretKey, error) {
|
||||
return generateKeysWithSeed(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigBasic) ThresholdKeygen(threshold, total uint) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeys(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigBasic) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeed(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G2 from sk, a secret key, and a message
|
||||
func (b SigBasic) Sign(sk *SecretKey, msg []byte) (*Signature, error) {
|
||||
return sk.createSignature(msg, b.dst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigBasic) PartialSign(sks *SecretKeyShare, msg []byte) (*PartialSignature, error) {
|
||||
return sks.partialSign(msg, b.dst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigBasic) CombineSignatures(sigs ...*PartialSignature) (*Signature, error) {
|
||||
return combineSigs(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
func (b SigBasic) Verify(pk *PublicKey, msg []byte, sig *Signature) (bool, error) {
|
||||
return pk.verifySignature(msg, sig, b.dst)
|
||||
}
|
||||
|
||||
// The AggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// Each message must be different or this will return false.
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigBasic) AggregateVerify(
|
||||
pks []*PublicKey,
|
||||
msgs [][]byte,
|
||||
sigs []*Signature,
|
||||
) (bool, error) {
|
||||
if !allRowsUnique(msgs) {
|
||||
return false, fmt.Errorf("all messages must be distinct")
|
||||
}
|
||||
asig, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, msgs, b.dst)
|
||||
}
|
||||
|
||||
// SigAug is minimal-pubkey-size scheme that doesn't support FastAggregateVerificiation.
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.2
|
||||
type SigAug struct {
|
||||
dst string
|
||||
}
|
||||
|
||||
// Creates a new BLS message augmentation signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigAug() *SigAug {
|
||||
return &SigAug{dst: blsSignatureAugDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS message augmentation signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigAugWithDst(signDst string) *SigAug {
|
||||
return &SigAug{dst: signDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigAug) Keygen() (*PublicKey, *SecretKey, error) {
|
||||
return generateKeys()
|
||||
}
|
||||
|
||||
// Creates a new BLS secret key
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigAug) KeygenWithSeed(ikm []byte) (*PublicKey, *SecretKey, error) {
|
||||
return generateKeysWithSeed(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigAug) ThresholdKeygen(threshold, total uint) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeys(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigAug) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeed(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G1 from sk, a secret key, and a message
|
||||
// See section 3.2.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigAug) Sign(sk *SecretKey, msg []byte) (*Signature, error) {
|
||||
if len(msg) == 0 {
|
||||
return nil, fmt.Errorf("message cannot be empty or nil")
|
||||
}
|
||||
|
||||
pk, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MarshalBinary failed")
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return sk.createSignature(bytes, b.dst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigAug) PartialSign(
|
||||
sks *SecretKeyShare,
|
||||
pk *PublicKey,
|
||||
msg []byte,
|
||||
) (*PartialSignature, error) {
|
||||
if len(msg) == 0 {
|
||||
return nil, fmt.Errorf("message cannot be empty or nil")
|
||||
}
|
||||
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MarshalBinary failed")
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return sks.partialSign(bytes, b.dst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigAug) CombineSignatures(sigs ...*PartialSignature) (*Signature, error) {
|
||||
return combineSigs(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
// See section 3.2.2 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigAug) Verify(pk *PublicKey, msg []byte, sig *Signature) (bool, error) {
|
||||
bytes, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bytes = append(bytes, msg...)
|
||||
return pk.verifySignature(bytes, sig, b.dst)
|
||||
}
|
||||
|
||||
// The AggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// See section 3.2.3 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigAug) AggregateVerify(pks []*PublicKey, msgs [][]byte, sigs []*Signature) (bool, error) {
|
||||
if len(pks) != len(msgs) {
|
||||
return false, fmt.Errorf(
|
||||
"the number of public keys does not match the number of messages: %v != %v",
|
||||
len(pks),
|
||||
len(msgs),
|
||||
)
|
||||
}
|
||||
data := make([][]byte, len(msgs))
|
||||
for i, msg := range msgs {
|
||||
bytes, err := pks[i].MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data[i] = append(bytes, msg...)
|
||||
}
|
||||
asig, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, data, b.dst)
|
||||
}
|
||||
|
||||
// SigEth2 supports signatures on Eth2.
|
||||
// Internally is an alias for SigPop
|
||||
type SigEth2 = SigPop
|
||||
|
||||
// NewSigEth2 Creates a new BLS ETH2 signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigEth2() *SigEth2 {
|
||||
return NewSigPop()
|
||||
}
|
||||
|
||||
// SigPop is minimal-pubkey-size scheme that supports FastAggregateVerification
|
||||
// and requires using proofs of possession to mitigate rogue-key attacks
|
||||
// see: https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03#section-4.2.3
|
||||
type SigPop struct {
|
||||
sigDst string
|
||||
popDst string
|
||||
}
|
||||
|
||||
// Creates a new BLS proof of possession signature scheme with the standard domain separation tag used for signatures.
|
||||
func NewSigPop() *SigPop {
|
||||
return &SigPop{sigDst: blsSignaturePopDst, popDst: blsPopProofDst}
|
||||
}
|
||||
|
||||
// Creates a new BLS message proof of possession signature scheme with a custom domain separation tag used for signatures.
|
||||
func NewSigPopWithDst(signDst, popDst string) (*SigPop, error) {
|
||||
if signDst == popDst {
|
||||
return nil, fmt.Errorf("domain separation tags cannot be equal")
|
||||
}
|
||||
return &SigPop{sigDst: signDst, popDst: popDst}, nil
|
||||
}
|
||||
|
||||
// Creates a new BLS key pair
|
||||
func (b SigPop) Keygen() (*PublicKey, *SecretKey, error) {
|
||||
return generateKeys()
|
||||
}
|
||||
|
||||
// Creates a new BLS secret key
|
||||
// Input key material (ikm) MUST be at least 32 bytes long,
|
||||
// but it MAY be longer.
|
||||
func (b SigPop) KeygenWithSeed(ikm []byte) (*PublicKey, *SecretKey, error) {
|
||||
return generateKeysWithSeed(ikm)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigPop) ThresholdKeygen(threshold, total uint) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeys(threshold, total)
|
||||
}
|
||||
|
||||
// ThresholdKeyGen generates a public key and `total` secret key shares such that
|
||||
// `threshold` of them can be combined in signatures
|
||||
func (b SigPop) ThresholdKeygenWithSeed(
|
||||
ikm []byte,
|
||||
threshold, total uint,
|
||||
) (*PublicKey, []*SecretKeyShare, error) {
|
||||
return thresholdGenerateKeysWithSeed(ikm, threshold, total)
|
||||
}
|
||||
|
||||
// Computes a signature in G2 from sk, a secret key, and a message
|
||||
// See section 2.6 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) Sign(sk *SecretKey, msg []byte) (*Signature, error) {
|
||||
return sk.createSignature(msg, b.sigDst)
|
||||
}
|
||||
|
||||
// Compute a partial signature in G2 that can be combined with other partial signature
|
||||
func (b SigPop) PartialSign(sks *SecretKeyShare, msg []byte) (*PartialSignature, error) {
|
||||
return sks.partialSign(msg, b.sigDst)
|
||||
}
|
||||
|
||||
// CombineSignatures takes partial signatures to yield a completed signature
|
||||
func (b SigPop) CombineSignatures(sigs ...*PartialSignature) (*Signature, error) {
|
||||
return combineSigs(sigs)
|
||||
}
|
||||
|
||||
// Checks that a signature is valid for the message under the public key pk
|
||||
// See section 2.7 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) Verify(pk *PublicKey, msg []byte, sig *Signature) (bool, error) {
|
||||
return pk.verifySignature(msg, sig, b.sigDst)
|
||||
}
|
||||
|
||||
// The aggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message, signature) pairs.
|
||||
// Each message must be different or this will return false.
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) AggregateVerify(pks []*PublicKey, msgs [][]byte, sigs []*Signature) (bool, error) {
|
||||
if !allRowsUnique(msgs) {
|
||||
return false, fmt.Errorf("all messages must be distinct")
|
||||
}
|
||||
asig, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return asig.aggregateVerify(pks, msgs, b.sigDst)
|
||||
}
|
||||
|
||||
// Combine many signatures together to form a Multisignature.
|
||||
// Multisignatures can be created when multiple signers jointly
|
||||
// generate signatures over the same message.
|
||||
func (b SigPop) AggregateSignatures(sigs ...*Signature) (*MultiSignature, error) {
|
||||
g1, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MultiSignature{value: g1.Value}, nil
|
||||
}
|
||||
|
||||
// Combine many public keys together to form a Multipublickey.
|
||||
// Multipublickeys are used to verify multisignatures.
|
||||
func (b SigPop) AggregatePublicKeys(pks ...*PublicKey) (*MultiPublicKey, error) {
|
||||
g2, err := aggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MultiPublicKey{value: g2.value}, nil
|
||||
}
|
||||
|
||||
// Checks that a multisignature is valid for the message under the multi public key
|
||||
// Similar to FastAggregateVerify except the keys and signatures have already been
|
||||
// combined. See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) VerifyMultiSignature(
|
||||
pk *MultiPublicKey,
|
||||
msg []byte,
|
||||
sig *MultiSignature,
|
||||
) (bool, error) {
|
||||
s := &Signature{Value: sig.value}
|
||||
p := &PublicKey{value: pk.value}
|
||||
return p.verifySignature(msg, s, b.sigDst)
|
||||
}
|
||||
|
||||
// FastAggregateVerify verifies an aggregated signature against the specified message and set of public keys.
|
||||
// See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) FastAggregateVerify(pks []*PublicKey, msg []byte, asig *Signature) (bool, error) {
|
||||
apk, err := aggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return apk.verifySignature(msg, asig, b.sigDst)
|
||||
}
|
||||
|
||||
// FastAggregateVerifyConstituent aggregates all constituent signatures and the verifies
|
||||
// them against the specified message and public keys
|
||||
// See section 3.3.4 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) FastAggregateVerifyConstituent(
|
||||
pks []*PublicKey,
|
||||
msg []byte,
|
||||
sigs []*Signature,
|
||||
) (bool, error) {
|
||||
// Aggregate the constituent signatures
|
||||
asig, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// And verify
|
||||
return b.FastAggregateVerify(pks, msg, asig)
|
||||
}
|
||||
|
||||
// Create a proof of possession for the corresponding public key.
|
||||
// A proof of possession must be created for each public key to be used
|
||||
// in FastAggregateVerify or a Multipublickey to avoid rogue key attacks.
|
||||
// See section 3.3.2 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) PopProve(sk *SecretKey) (*ProofOfPossession, error) {
|
||||
return sk.createProofOfPossession(b.popDst)
|
||||
}
|
||||
|
||||
// verify a proof of possession for the corresponding public key is valid.
|
||||
// A proof of possession must be created for each public key to be used
|
||||
// in FastAggregateVerify or a Multipublickey to avoid rogue key attacks.
|
||||
// See section 3.3.3 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (b SigPop) PopVerify(pk *PublicKey, pop2 *ProofOfPossession) (bool, error) {
|
||||
return pop2.verify(pk, b.popDst)
|
||||
}
|
||||
507
signatures/bls/bls_sig/usual_bls_sig.go
Normal file
507
signatures/bls/bls_sig/usual_bls_sig.go
Normal file
@@ -0,0 +1,507 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
// Implement BLS signatures on the BLS12-381 curve
|
||||
// according to https://crypto.standford.edu/~dabo/pubs/papers/BLSmultisig.html
|
||||
// and https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
// this file implements signatures in G2 and public keys in G1.
|
||||
// Public Keys and Signatures can be aggregated but the consumer
|
||||
// must use proofs of possession to defend against rogue-key attacks.
|
||||
|
||||
const (
|
||||
// Public key size in G1
|
||||
PublicKeySize = 48
|
||||
// Signature size in G2
|
||||
SignatureSize = 96
|
||||
// Proof of Possession in G2
|
||||
ProofOfPossessionSize = 96
|
||||
)
|
||||
|
||||
// Represents a public key in G1
|
||||
type PublicKey struct {
|
||||
value bls12381.G1
|
||||
}
|
||||
|
||||
// Serialize a public key to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pk PublicKey) MarshalBinary() ([]byte, error) {
|
||||
out := pk.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a public key from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the public key
|
||||
// otherwise it will return an error
|
||||
func (pk *PublicKey) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != PublicKeySize {
|
||||
return fmt.Errorf("public key must be %d bytes", PublicKeySize)
|
||||
}
|
||||
var blob [PublicKeySize]byte
|
||||
copy(blob[:], data)
|
||||
p1, err := new(bls12381.G1).FromCompressed(&blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p1.IsIdentity() == 1 {
|
||||
return fmt.Errorf("public keys cannot be zero")
|
||||
}
|
||||
pk.value = *p1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Represents a BLS signature in G2
|
||||
type Signature struct {
|
||||
Value bls12381.G2
|
||||
}
|
||||
|
||||
// Serialize a signature to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (sig Signature) MarshalBinary() ([]byte, error) {
|
||||
out := sig.Value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
func (sig Signature) verify(pk *PublicKey, message []byte, signDst string) (bool, error) {
|
||||
return pk.verifySignature(message, &sig, signDst)
|
||||
}
|
||||
|
||||
// The AggregateVerify algorithm checks an aggregated signature over
|
||||
// several (PK, message) pairs.
|
||||
// The Signature is the output of aggregateSignatures
|
||||
// Each message must be different or this will return false
|
||||
// See section 3.1.1 from
|
||||
// https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (sig Signature) aggregateVerify(
|
||||
pks []*PublicKey,
|
||||
msgs [][]byte,
|
||||
signDst string,
|
||||
) (bool, error) {
|
||||
return sig.coreAggregateVerify(pks, msgs, signDst)
|
||||
}
|
||||
|
||||
func (sig Signature) coreAggregateVerify(
|
||||
pks []*PublicKey,
|
||||
msgs [][]byte,
|
||||
signDst string,
|
||||
) (bool, error) {
|
||||
if len(pks) < 1 {
|
||||
return false, fmt.Errorf("at least one key is required")
|
||||
}
|
||||
if len(msgs) < 1 {
|
||||
return false, fmt.Errorf("at least one message is required")
|
||||
}
|
||||
if len(pks) != len(msgs) {
|
||||
return false, fmt.Errorf(
|
||||
"the number of public keys does not match the number of messages: %v != %v",
|
||||
len(pks),
|
||||
len(msgs),
|
||||
)
|
||||
}
|
||||
if sig.Value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
|
||||
dst := []byte(signDst)
|
||||
engine := new(bls12381.Engine)
|
||||
// e(pk_1, H(m_1))*...*e(pk_N, H(m_N)) == e(g1, s)
|
||||
// However, we use only one miller loop
|
||||
// by doing the equivalent of
|
||||
// e(pk_1, H(m_1))*...*e(pk_N, H(m_N)) * e(g1^-1, s) == 1
|
||||
for i, pk := range pks {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key at %d is nil", i)
|
||||
}
|
||||
if pk.value.IsIdentity() == 1 || pk.value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("public key at %d is not in the correct subgroup", i)
|
||||
}
|
||||
p2 := new(bls12381.G2).Hash(native.EllipticPointHasherSha256(), msgs[i], dst)
|
||||
engine.AddPair(&pk.value, p2)
|
||||
}
|
||||
engine.AddPairInvG1(new(bls12381.G1).Generator(), &sig.Value)
|
||||
return engine.Check(), nil
|
||||
}
|
||||
|
||||
// Deserialize a signature from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (sig *Signature) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SignatureSize {
|
||||
return fmt.Errorf("signature must be %d bytes", SignatureSize)
|
||||
}
|
||||
var blob [SignatureSize]byte
|
||||
copy(blob[:], data)
|
||||
p2, err := new(bls12381.G2).FromCompressed(&blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p2.IsIdentity() == 1 {
|
||||
return fmt.Errorf("signatures cannot be zero")
|
||||
}
|
||||
sig.Value = *p2
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the corresponding public key from a secret key
|
||||
// Verifies the public key is in the correct subgroup
|
||||
func (sk SecretKey) GetPublicKey() (*PublicKey, error) {
|
||||
result := new(bls12381.G1).Generator()
|
||||
result.Mul(result, sk.value)
|
||||
if result.InCorrectSubgroup() == 0 || result.IsIdentity() == 1 {
|
||||
return nil, fmt.Errorf("point is not in correct subgroup")
|
||||
}
|
||||
return &PublicKey{value: *result}, nil
|
||||
}
|
||||
|
||||
// Compute a signature from a secret key and message
|
||||
// This signature is deterministic which protects against
|
||||
// attacks arising from signing with bad randomness like
|
||||
// the nonce reuse attack on ECDSA. `message` is
|
||||
// hashed to a point in G2 as described in to
|
||||
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/?include_text=1
|
||||
// See Section 2.6 in https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
// nil message is not permitted but empty slice is allowed
|
||||
func (sk SecretKey) createSignature(message []byte, dst string) (*Signature, error) {
|
||||
if message == nil {
|
||||
return nil, fmt.Errorf("message cannot be nil")
|
||||
}
|
||||
if sk.value.IsZero() == 1 {
|
||||
return nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
p2 := new(bls12381.G2).Hash(native.EllipticPointHasherSha256(), message, []byte(dst))
|
||||
result := new(bls12381.G2).Mul(p2, sk.value)
|
||||
if result.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("point is not on correct subgroup")
|
||||
}
|
||||
return &Signature{Value: *result}, nil
|
||||
}
|
||||
|
||||
// Verify a signature is valid for the message under this public key.
|
||||
// See Section 2.7 in https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03
|
||||
func (pk PublicKey) verifySignature(
|
||||
message []byte,
|
||||
signature *Signature,
|
||||
dst string,
|
||||
) (bool, error) {
|
||||
if signature == nil || message == nil || pk.value.IsIdentity() == 1 {
|
||||
return false, fmt.Errorf("signature and message and public key cannot be nil or zero")
|
||||
}
|
||||
if signature.Value.IsIdentity() == 1 || signature.Value.InCorrectSubgroup() == 0 {
|
||||
return false, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
engine := new(bls12381.Engine)
|
||||
|
||||
p2 := new(bls12381.G2).Hash(native.EllipticPointHasherSha256(), message, []byte(dst))
|
||||
// e(pk, H(m)) == e(g1, s)
|
||||
// However, we can reduce the number of miller loops
|
||||
// by doing the equivalent of
|
||||
// e(pk^-1, H(m)) * e(g1, s) == 1
|
||||
engine.AddPair(&pk.value, p2)
|
||||
engine.AddPairInvG1(new(bls12381.G1).Generator(), &signature.Value)
|
||||
return engine.Check(), nil
|
||||
}
|
||||
|
||||
// Combine public keys into one aggregated key
|
||||
func aggregatePublicKeys(pks ...*PublicKey) (*PublicKey, error) {
|
||||
if len(pks) < 1 {
|
||||
return nil, fmt.Errorf("at least one public key is required")
|
||||
}
|
||||
result := new(bls12381.G1).Identity()
|
||||
for i, k := range pks {
|
||||
if k == nil {
|
||||
return nil, fmt.Errorf("key at %d is nil, keys cannot be nil", i)
|
||||
}
|
||||
if k.value.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("key at %d is not in the correct subgroup", i)
|
||||
}
|
||||
result.Add(result, &k.value)
|
||||
}
|
||||
return &PublicKey{value: *result}, nil
|
||||
}
|
||||
|
||||
// Combine signatures into one aggregated signature
|
||||
func aggregateSignatures(sigs ...*Signature) (*Signature, error) {
|
||||
if len(sigs) < 1 {
|
||||
return nil, fmt.Errorf("at least one signature is required")
|
||||
}
|
||||
result := new(bls12381.G2).Identity()
|
||||
for i, s := range sigs {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("signature at %d is nil, signature cannot be nil", i)
|
||||
}
|
||||
if s.Value.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("signature at %d is not in the correct subgroup", i)
|
||||
}
|
||||
result.Add(result, &s.Value)
|
||||
}
|
||||
return &Signature{Value: *result}, nil
|
||||
}
|
||||
|
||||
// A proof of possession scheme uses a separate public key validation
|
||||
// step, called a proof of possession, to defend against rogue key
|
||||
// attacks. This enables an optimization to aggregate signature
|
||||
// verification for the case that all signatures are on the same
|
||||
// message.
|
||||
type ProofOfPossession struct {
|
||||
value bls12381.G2
|
||||
}
|
||||
|
||||
// Generates a proof-of-possession (PoP) for this secret key. The PoP signature should be verified before
|
||||
// before accepting any aggregate signatures related to the corresponding pubkey.
|
||||
func (sk SecretKey) createProofOfPossession(popDst string) (*ProofOfPossession, error) {
|
||||
pk, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := sk.createSignature(msg, popDst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ProofOfPossession{value: sig.Value}, nil
|
||||
}
|
||||
|
||||
// Serialize a proof of possession to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pop ProofOfPossession) MarshalBinary() ([]byte, error) {
|
||||
out := pop.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a proof of possession from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (pop *ProofOfPossession) UnmarshalBinary(data []byte) error {
|
||||
p2 := new(Signature)
|
||||
err := p2.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pop.value = p2.Value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verifies that PoP is valid for this pubkey. In order to prevent rogue key attacks, a PoP must be validated
|
||||
// for each pubkey in an aggregated signature.
|
||||
func (pop ProofOfPossession) verify(pk *PublicKey, popDst string) (bool, error) {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key cannot be nil")
|
||||
}
|
||||
msg, err := pk.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return pk.verifySignature(msg, &Signature{Value: pop.value}, popDst)
|
||||
}
|
||||
|
||||
// Represents an MultiSignature in G2. A multisignature is used when multiple signatures
|
||||
// are calculated over the same message vs an aggregate signature where each message signed
|
||||
// is a unique.
|
||||
type MultiSignature struct {
|
||||
value bls12381.G2
|
||||
}
|
||||
|
||||
// Serialize a multi-signature to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (sig MultiSignature) MarshalBinary() ([]byte, error) {
|
||||
out := sig.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Check a multisignature is valid for a multipublickey and a message
|
||||
func (sig MultiSignature) verify(pk *MultiPublicKey, message []byte, signDst string) (bool, error) {
|
||||
if pk == nil {
|
||||
return false, fmt.Errorf("public key cannot be nil")
|
||||
}
|
||||
p := PublicKey{value: pk.value}
|
||||
return p.verifySignature(message, &Signature{Value: sig.value}, signDst)
|
||||
}
|
||||
|
||||
// Deserialize a signature from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the Signature
|
||||
// otherwise it will return an error
|
||||
func (sig *MultiSignature) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != SignatureSize {
|
||||
return fmt.Errorf("multi signature must be %v bytes", SignatureSize)
|
||||
}
|
||||
s2 := new(Signature)
|
||||
err := s2.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig.value = s2.Value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Represents accumulated multiple Public Keys in G1 for verifying a multisignature
|
||||
type MultiPublicKey struct {
|
||||
value bls12381.G1
|
||||
}
|
||||
|
||||
// Serialize a public key to a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
func (pk MultiPublicKey) MarshalBinary() ([]byte, error) {
|
||||
out := pk.value.ToCompressed()
|
||||
return out[:], nil
|
||||
}
|
||||
|
||||
// Deserialize a public key from a byte array in compressed form.
|
||||
// See
|
||||
// https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// https://docs.rs/bls12_381/0.1.1/bls12_381/notes/serialization/index.html
|
||||
// If successful, it will assign the public key
|
||||
// otherwise it will return an error
|
||||
func (pk *MultiPublicKey) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != PublicKeySize {
|
||||
return fmt.Errorf("multi public key must be %v bytes", PublicKeySize)
|
||||
}
|
||||
p1 := new(PublicKey)
|
||||
err := p1.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = p1.value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check a multisignature is valid for a multipublickey and a message
|
||||
func (pk MultiPublicKey) verify(message []byte, sig *MultiSignature, signDst string) (bool, error) {
|
||||
return sig.verify(&pk, message, signDst)
|
||||
}
|
||||
|
||||
// PartialSignature represents threshold Gap Diffie-Hellman BLS signature
|
||||
// that can be combined with other partials to yield a completed BLS signature
|
||||
// See section 3.2 in <https://www.cc.gatech.edu/~aboldyre/papers/bold.pdf>
|
||||
type PartialSignature struct {
|
||||
Identifier byte
|
||||
Signature bls12381.G2
|
||||
}
|
||||
|
||||
// partialSign creates a partial signature that can be combined with other partial signatures
|
||||
// to yield a complete signature
|
||||
func (sks SecretKeyShare) partialSign(message []byte, signDst string) (*PartialSignature, error) {
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("message cannot be empty or nil")
|
||||
}
|
||||
p2 := new(bls12381.G2).Hash(native.EllipticPointHasherSha256(), message, []byte(signDst))
|
||||
|
||||
var blob [SecretKeySize]byte
|
||||
copy(blob[:], internal.ReverseScalarBytes(sks.value))
|
||||
s, err := bls12381.Bls12381FqNew().SetBytes(&blob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(bls12381.G2).Mul(p2, s)
|
||||
if result.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("point is not on correct subgroup")
|
||||
}
|
||||
return &PartialSignature{Identifier: sks.identifier, Signature: *result}, nil
|
||||
}
|
||||
|
||||
// combineSigs gathers partial signatures and yields a complete signature
|
||||
func combineSigs(partials []*PartialSignature) (*Signature, error) {
|
||||
if len(partials) < 2 {
|
||||
return nil, fmt.Errorf("must have at least 2 partial signatures")
|
||||
}
|
||||
if len(partials) > 255 {
|
||||
return nil, fmt.Errorf("unsupported to combine more than 255 signatures")
|
||||
}
|
||||
|
||||
// Don't know the actual values so put the minimum
|
||||
xVars, yVars, err := splitXY(partials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sTmp := new(bls12381.G2).Identity()
|
||||
sig := new(bls12381.G2).Identity()
|
||||
|
||||
// Lagrange interpolation
|
||||
basis := bls12381.Bls12381FqNew().SetOne()
|
||||
for i, xi := range xVars {
|
||||
basis.SetOne()
|
||||
|
||||
for j, xj := range xVars {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
|
||||
num := bls12381.Bls12381FqNew().Neg(xj) // - x_m
|
||||
den := bls12381.Bls12381FqNew().Sub(xi, xj) // x_j - x_m
|
||||
_, wasInverted := den.Invert(den)
|
||||
// wasInverted == false if den == 0
|
||||
if !wasInverted {
|
||||
return nil, fmt.Errorf("signatures cannot be recombined")
|
||||
}
|
||||
basis.Mul(basis, num.Mul(num, den))
|
||||
}
|
||||
sTmp.Mul(yVars[i], basis)
|
||||
sig.Add(sig, sTmp)
|
||||
}
|
||||
if sig.InCorrectSubgroup() == 0 {
|
||||
return nil, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
|
||||
return &Signature{Value: *sig}, nil
|
||||
}
|
||||
|
||||
// Ensure no duplicates x values and convert x values to field elements
|
||||
func splitXY(partials []*PartialSignature) ([]*native.Field, []*bls12381.G2, error) {
|
||||
x := make([]*native.Field, len(partials))
|
||||
y := make([]*bls12381.G2, len(partials))
|
||||
|
||||
dup := make(map[byte]bool)
|
||||
|
||||
for i, sp := range partials {
|
||||
if sp == nil {
|
||||
return nil, nil, fmt.Errorf("partial signature cannot be nil")
|
||||
}
|
||||
if _, exists := dup[sp.Identifier]; exists {
|
||||
return nil, nil, fmt.Errorf("duplicate signature included")
|
||||
}
|
||||
if sp.Signature.InCorrectSubgroup() == 0 {
|
||||
return nil, nil, fmt.Errorf("signature is not in the correct subgroup")
|
||||
}
|
||||
dup[sp.Identifier] = true
|
||||
x[i] = bls12381.Bls12381FqNew().SetUint64(uint64(sp.Identifier))
|
||||
y[i] = &sp.Signature
|
||||
}
|
||||
return x, y, nil
|
||||
}
|
||||
417
signatures/bls/bls_sig/usual_bls_sig_aug_test.go
Normal file
417
signatures/bls/bls_sig/usual_bls_sig_aug_test.go
Normal file
@@ -0,0 +1,417 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
func generateAugSignatureG2(sk *SecretKey, msg []byte, t *testing.T) *Signature {
|
||||
bls := NewSigAug()
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Sign failed")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func generateAugAggregateDataG2(t *testing.T) ([]*PublicKey, []*Signature, [][]byte) {
|
||||
msgs := make([][]byte, numAggregateG2)
|
||||
pks := make([]*PublicKey, numAggregateG2)
|
||||
sigs := make([]*Signature, numAggregateG2)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAug()
|
||||
|
||||
for i := 0; i < numAggregateG2; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
msg := make([]byte, 20)
|
||||
readRand(msg, t)
|
||||
sig := generateAugSignatureG2(sk, msg, t)
|
||||
msgs[i] = msg
|
||||
sigs[i] = sig
|
||||
pks[i] = pk
|
||||
}
|
||||
return pks, sigs, msgs
|
||||
}
|
||||
|
||||
func TestAugKeyGenG2Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAug()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugKeyGenG2Fail(t *testing.T) {
|
||||
ikm := make([]byte, 10)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAug()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Aug KeyGen succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugCustomDstG2(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAugWithDst("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_TEST")
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Custom Dst KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug Custom Dst Sign failed")
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Aug Custon Dst Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] = 0
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Aug Custom Dst Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugSigningG2(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigAug()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig := generateAugSignatureG2(sk, ikm, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Aug Verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Aug Verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugSignEmptyMessage(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign a nil message
|
||||
_, err = bls.Sign(sk, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected sign of nil message to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugSignNilMessage(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign an empty message
|
||||
_, err = bls.Sign(sk, []byte{})
|
||||
if err == nil {
|
||||
t.Errorf("Expected sign of empty message to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG2Works(t *testing.T) {
|
||||
pks, sigs, msgs := generateAugAggregateDataG2(t)
|
||||
bls := NewSigAug()
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug AggregateVerify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG2BadPks(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
pks, sigs, msgs := generateAugAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug AggregateVerify failed")
|
||||
}
|
||||
|
||||
pks[0] = pks[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug AggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
pkValue := new(bls12381.G1).Identity()
|
||||
pks[0] = &PublicKey{value: *pkValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug AggregateVerify succeeded with zero byte public key it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
pkValue.Generator()
|
||||
pks[0] = &PublicKey{value: *pkValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Aug aggregateVerify succeeded with the base generator public key it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG2BadSigs(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
pks, sigs, msgs := generateAugAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed")
|
||||
}
|
||||
|
||||
sigs[0] = sigs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
sigValue := new(bls12381.G2).Identity()
|
||||
sigs[0] = &Signature{Value: *sigValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded with zero byte signature it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
sigValue.Generator()
|
||||
sigs[0] = &Signature{Value: *sigValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Aug aggregateVerify succeeded with the base generator signature it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG2BadMsgs(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
pks, sigs, msgs := generateAugAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed")
|
||||
}
|
||||
|
||||
// Test len(pks) != len(msgs)
|
||||
if res, _ := bls.AggregateVerify(pks, msgs[0:8], sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
msgs[0] = msgs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Aug aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugAggregateVerifyG2DupMsg(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
|
||||
// Only two messages but repeated
|
||||
messages := make([][]byte, numAggregateG2)
|
||||
messages[0] = []byte("Yes")
|
||||
messages[1] = []byte("No")
|
||||
for i := 2; i < numAggregateG2; i++ {
|
||||
messages[i] = messages[i%2]
|
||||
}
|
||||
|
||||
pks := make([]*PublicKey, numAggregateG2)
|
||||
sigs := make([]*Signature, numAggregateG2)
|
||||
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < numAggregateG2; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Aug KeyGen failed")
|
||||
}
|
||||
|
||||
sig := generateAugSignatureG2(sk, messages[i], t)
|
||||
pks[i] = pk
|
||||
sigs[i] = sig
|
||||
}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); !res {
|
||||
t.Errorf("Aug aggregateVerify failed for duplicate messages")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsAugG2KeyGen(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
_, _, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(1, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugPartialSign(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAug()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], pk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], pk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures succeeded when it should've failed")
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestAugPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigAug()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignature, total)
|
||||
sigs2 := make([]*PartialSignature, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], pk1, msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], pk2, msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugPartialSignEmptyMessage(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
pk, sks, err := bls.ThresholdKeygen(2, 2)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
|
||||
// Test signing an empty message
|
||||
_, err = bls.PartialSign(sks[0], pk, []byte{})
|
||||
if err == nil {
|
||||
t.Errorf("Expected partial sign of empty message to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugPartialSignNilMessage(t *testing.T) {
|
||||
bls := NewSigAug()
|
||||
pk, sks, err := bls.ThresholdKeygen(7, 7)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
|
||||
// Test signing a nil message
|
||||
_, err = bls.PartialSign(sks[0], pk, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected partial sign of nil message to fail")
|
||||
}
|
||||
}
|
||||
417
signatures/bls/bls_sig/usual_bls_sig_basic_test.go
Normal file
417
signatures/bls/bls_sig/usual_bls_sig_basic_test.go
Normal file
@@ -0,0 +1,417 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
func generateBasicSignatureG2(sk *SecretKey, msg []byte, t *testing.T) *Signature {
|
||||
bls := NewSigBasic()
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Sign failed")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
func generateBasicAggregateDataG2(t *testing.T) ([]*PublicKey, []*Signature, [][]byte) {
|
||||
msgs := make([][]byte, numAggregateG2)
|
||||
pks := make([]*PublicKey, numAggregateG2)
|
||||
sigs := make([]*Signature, numAggregateG2)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasic()
|
||||
|
||||
for i := 0; i < numAggregateG2; i++ {
|
||||
readRand(ikm, t)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
msg := make([]byte, 20)
|
||||
readRand(msg, t)
|
||||
sig := generateBasicSignatureG2(sk, msg, t)
|
||||
msgs[i] = msg
|
||||
sigs[i] = sig
|
||||
pks[i] = pk
|
||||
}
|
||||
return pks, sigs, msgs
|
||||
}
|
||||
|
||||
func TestBasicKeyGenG2Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasic()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicKeyGenG2Fail(t *testing.T) {
|
||||
ikm := make([]byte, 10)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasic()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Basic KeyGen succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicCustomDstG2(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasicWithDst("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_TEST")
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Custom Dst KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic Custom Dst Sign failed")
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Basic Custon Dst verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Basic Custom Dst verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningG2(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigBasic()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
readRand(ikm, t)
|
||||
sig := generateBasicSignatureG2(sk, ikm, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Basic verify failed")
|
||||
}
|
||||
|
||||
ikm[0] += 1
|
||||
if res, _ := bls.Verify(pk, ikm, sig); res {
|
||||
t.Errorf("Basic verify succeeded when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningG2EmptyMessage(t *testing.T) {
|
||||
// So basic
|
||||
bls := NewSigBasic()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign an empty message
|
||||
_, err = bls.Sign(sk, []byte{})
|
||||
if err != nil {
|
||||
t.Errorf("Expected signing empty message to succeed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicSigningG2NilMessage(t *testing.T) {
|
||||
// So basic
|
||||
bls := NewSigBasic()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Basic KeyGen failed")
|
||||
}
|
||||
|
||||
// Sign an empty message
|
||||
_, err = bls.Sign(sk, nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected signing nil message to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG2Works(t *testing.T) {
|
||||
pks, sigs, msgs := generateBasicAggregateDataG2(t)
|
||||
bls := NewSigBasic()
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic AggregateVerify failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG2BadPks(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic AggregateVerify failed")
|
||||
}
|
||||
|
||||
pks[0] = pks[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic AggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
pkValue := new(bls12381.G1).Identity()
|
||||
pks[0] = &PublicKey{value: *pkValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic AggregateVerify succeeded with zero byte public key it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
pkValue.Generator()
|
||||
pks[0] = &PublicKey{value: *pkValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Basic aggregateVerify succeeded with the base generator public key it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG2BadSigs(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic aggregateVerify failed")
|
||||
}
|
||||
|
||||
sigs[0] = sigs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
|
||||
// Try a zero key to make sure it doesn't crash
|
||||
sigValue := new(bls12381.G2).Identity()
|
||||
sigs[0] = &Signature{Value: *sigValue}
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded with zero byte signature it should've failed")
|
||||
}
|
||||
|
||||
// Try with base generator
|
||||
sigValue.Generator()
|
||||
sigs[0] = &Signature{Value: *sigValue}
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf(
|
||||
"Basic aggregateVerify succeeded with the base generator signature it should've failed",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAggregateVerifyG2BadMsgs(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
pks, sigs, msgs := generateBasicAggregateDataG2(t)
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); !res {
|
||||
t.Errorf("Basic aggregateVerify failed")
|
||||
}
|
||||
|
||||
msgs[0] = msgs[1]
|
||||
|
||||
if res, _ := bls.AggregateVerify(pks, msgs, sigs); res {
|
||||
t.Errorf("Basic aggregateVerify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(1, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicPartialSign(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasic()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures succeeded when it should've failed")
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that duplicate partial signatures cannot be used to create a complete one
|
||||
func TestBasicPartialDuplicateShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasic()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignature, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining duplicates from group 1
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[0], sigs1[1], sigs1[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded")
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf("CombineSignatures worked with duplicate partial signatures")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestBasicPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigBasic()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignature, total)
|
||||
sigs2 := make([]*PartialSignature, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityPublicKey(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
_, sk, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
msg := []byte{0, 0, 0, 0}
|
||||
sig, _ := bls.Sign(sk, msg)
|
||||
pk := PublicKey{value: *new(bls12381.G1).Identity()}
|
||||
if res, _ := bls.Verify(&pk, msg, sig); res {
|
||||
t.Errorf("Verify succeeded when the public key is the identity.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestThresholdSignTooHighAndLow(t *testing.T) {
|
||||
bls := NewSigBasic()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
|
||||
ps, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
|
||||
_, err = bls.CombineSignatures(ps)
|
||||
if err == nil {
|
||||
t.Errorf("CombinSignatures succeeded when it should've failed")
|
||||
}
|
||||
|
||||
pss := make([]*PartialSignature, 256)
|
||||
|
||||
_, err = bls.CombineSignatures(pss...)
|
||||
if err == nil {
|
||||
t.Errorf("CombinSignatures succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
936
signatures/bls/bls_sig/usual_bls_sig_pop_test.go
Normal file
936
signatures/bls/bls_sig/usual_bls_sig_pop_test.go
Normal file
@@ -0,0 +1,936 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bls_sig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/bls12381"
|
||||
)
|
||||
|
||||
const numAggregateG2 = 10
|
||||
|
||||
func TestGetPublicKeyG1(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pk := genPublicKey(sk, t)
|
||||
actual := marshalStruct(pk, t)
|
||||
expected := []byte{
|
||||
166,
|
||||
149,
|
||||
173,
|
||||
50,
|
||||
93,
|
||||
252,
|
||||
126,
|
||||
17,
|
||||
145,
|
||||
251,
|
||||
201,
|
||||
241,
|
||||
134,
|
||||
245,
|
||||
142,
|
||||
255,
|
||||
66,
|
||||
166,
|
||||
52,
|
||||
2,
|
||||
151,
|
||||
49,
|
||||
177,
|
||||
131,
|
||||
128,
|
||||
255,
|
||||
137,
|
||||
191,
|
||||
66,
|
||||
196,
|
||||
100,
|
||||
164,
|
||||
44,
|
||||
184,
|
||||
202,
|
||||
85,
|
||||
178,
|
||||
0,
|
||||
240,
|
||||
81,
|
||||
245,
|
||||
127,
|
||||
30,
|
||||
24,
|
||||
147,
|
||||
198,
|
||||
135,
|
||||
89,
|
||||
}
|
||||
if !bytes.Equal(actual, expected) {
|
||||
t.Errorf("Expected GetPublicKey to pass but failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func testSignG2(message []byte, t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignature(sk, message, t)
|
||||
pk := genPublicKey(sk, t)
|
||||
|
||||
bls := NewSigPop()
|
||||
|
||||
if res, err := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("createSignature failed when it should've passed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignG2EmptyMessage(t *testing.T) {
|
||||
bls := NewSigPop()
|
||||
sk := genSecretKey(t)
|
||||
sig, _ := bls.Sign(sk, nil)
|
||||
pk := genPublicKey(sk, t)
|
||||
|
||||
if res, _ := bls.Verify(pk, nil, sig); res {
|
||||
t.Errorf("createSignature succeeded when it should've failed")
|
||||
}
|
||||
|
||||
message := []byte{}
|
||||
sig = genSignature(sk, message, t)
|
||||
|
||||
if res, err := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("create and verify failed on empty message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignG2OneByteMessage(t *testing.T) {
|
||||
message := []byte{1}
|
||||
testSignG2(message, t)
|
||||
}
|
||||
|
||||
func TestSignG2LargeMessage(t *testing.T) {
|
||||
message := make([]byte, 1048576)
|
||||
testSignG2(message, t)
|
||||
}
|
||||
|
||||
func TestSignG2RandomMessage(t *testing.T) {
|
||||
message := make([]byte, 65537)
|
||||
readRand(message, t)
|
||||
testSignG2(message, t)
|
||||
}
|
||||
|
||||
func TestSignG2BadMessage(t *testing.T) {
|
||||
message := make([]byte, 1024)
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignature(sk, message, t)
|
||||
pk := genPublicKey(sk, t)
|
||||
message = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
|
||||
|
||||
bls := NewSigPop()
|
||||
|
||||
if res, _ := bls.Verify(pk, message, sig); res {
|
||||
t.Errorf("Expected signature to not verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadConversionsG2(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
|
||||
sig := genSignature(sk, message, t)
|
||||
pk := genPublicKey(sk, t)
|
||||
|
||||
bls := NewSigPop()
|
||||
|
||||
if res, _ := bls.Verify(pk, message, sig); !res {
|
||||
t.Errorf("Signature should be valid")
|
||||
}
|
||||
if res, _ := sig.verify(pk, message, blsSignaturePopDst); !res {
|
||||
t.Errorf("Signature should be valid")
|
||||
}
|
||||
|
||||
// Convert public key to signature in G2
|
||||
sig2 := new(SignatureVt)
|
||||
err := sig2.UnmarshalBinary(marshalStruct(pk, t))
|
||||
if err != nil {
|
||||
t.Errorf("Should be able to convert to signature in G2")
|
||||
}
|
||||
pk2 := new(PublicKeyVt)
|
||||
err = pk2.UnmarshalBinary(marshalStruct(sig, t))
|
||||
if err != nil {
|
||||
t.Errorf("Should be able to convert to public key in G1")
|
||||
}
|
||||
|
||||
if res, _ := pk2.verifySignatureVt(message, sig2, blsSignaturePopDst); res {
|
||||
t.Errorf("The signature shouldn't verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregatePublicKeysG1(t *testing.T) {
|
||||
pks := []*PublicKey{}
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < 20; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
pk := genPublicKey(sk, t)
|
||||
pks = append(pks, pk)
|
||||
}
|
||||
apk1, err := aggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
rng := rand.New(rand.NewSource(1234567890))
|
||||
rng.Shuffle(len(pks), func(i, j int) { pks[i], pks[j] = pks[j], pks[i] })
|
||||
apk2, err := aggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(apk1, t), marshalStruct(apk2, t)) {
|
||||
t.Errorf("Aggregated public keys should be equal")
|
||||
}
|
||||
rand.Shuffle(len(pks), func(i, j int) { pks[i], pks[j] = pks[j], pks[i] })
|
||||
apk1, err = aggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(apk1, t), marshalStruct(apk2, t)) {
|
||||
t.Errorf("Aggregated public keys should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateSignaturesG2(t *testing.T) {
|
||||
var sigs []*Signature
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < 20; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
sig := genSignature(sk, ikm, t)
|
||||
sigs = append(sigs, sig)
|
||||
}
|
||||
asig1, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
rng2 := rand.New(rand.NewSource(1234567890))
|
||||
rng2.Shuffle(len(sigs), func(i, j int) { sigs[i], sigs[j] = sigs[j], sigs[i] })
|
||||
asig2, err := aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(asig1, t), marshalStruct(asig2, t)) {
|
||||
t.Errorf("Aggregated signatures should be equal")
|
||||
}
|
||||
rand.Shuffle(len(sigs), func(i, j int) { sigs[i], sigs[j] = sigs[j], sigs[i] })
|
||||
asig1, err = aggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if !bytes.Equal(marshalStruct(asig1, t), marshalStruct(asig2, t)) {
|
||||
t.Errorf("Aggregated signatures should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func initAggregatedTestValuesG2(messages [][]byte, t *testing.T) ([]*PublicKey, []*Signature) {
|
||||
pks := []*PublicKey{}
|
||||
sigs := []*Signature{}
|
||||
ikm := make([]byte, 32)
|
||||
for i := 0; i < numAggregateG2; i++ {
|
||||
readRand(ikm, t)
|
||||
sk := genRandSecretKey(ikm, t)
|
||||
sig := genSignature(sk, messages[i%len(messages)], t)
|
||||
sigs = append(sigs, sig)
|
||||
pk := genPublicKey(sk, t)
|
||||
pks = append(pks, pk)
|
||||
}
|
||||
return pks, sigs
|
||||
}
|
||||
|
||||
func TestAggregatedFunctionalityG2(t *testing.T) {
|
||||
message := make([]byte, 20)
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
|
||||
bls := NewSigPop()
|
||||
asig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); !res {
|
||||
t.Errorf("Should verify aggregated signatures with same message")
|
||||
}
|
||||
if res, _ := asig.verify(apk, message, blsSignaturePopDst); !res {
|
||||
t.Errorf("MultiSignature.verify failed.")
|
||||
}
|
||||
if res, _ := apk.verify(message, asig, blsSignaturePopDst); !res {
|
||||
t.Errorf("MultiPublicKey.verify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadAggregatedFunctionalityG2(t *testing.T) {
|
||||
message := make([]byte, 20)
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
|
||||
bls := NewSigPop()
|
||||
|
||||
apk, err := bls.AggregatePublicKeys(pks[2:]...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
asig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); res {
|
||||
t.Errorf(
|
||||
"Should not verify aggregated signatures with same message when some public keys are missing",
|
||||
)
|
||||
}
|
||||
|
||||
apk, err = bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
asig, err = bls.AggregateSignatures(sigs[2:]...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, message, asig); res {
|
||||
t.Errorf(
|
||||
"Should not verify aggregated signatures with same message when some signatures are missing",
|
||||
)
|
||||
}
|
||||
|
||||
asig, err = bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
badmsg := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
|
||||
if res, _ := bls.VerifyMultiSignature(apk, badmsg, asig); res {
|
||||
t.Errorf("Should not verify aggregated signature with bad message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG2Pass(t *testing.T) {
|
||||
messages := make([][]byte, numAggregateG2)
|
||||
for i := 0; i < numAggregateG2; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); !res {
|
||||
t.Errorf("Expected aggregateVerify to pass but failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG2MsgSigCntMismatch(t *testing.T) {
|
||||
messages := make([][]byte, 8)
|
||||
for i := 0; i < 8; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); res {
|
||||
t.Errorf("Expected AggregateVerifyG2 to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG2FailDupMsg(t *testing.T) {
|
||||
messages := make([][]byte, 10)
|
||||
for i := 0; i < 9; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
// Duplicate message
|
||||
messages[9] = messages[0]
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.AggregateVerify(pks, messages, sigs); res {
|
||||
t.Errorf("Expected aggregateVerify to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG2FailIncorrectMsg(t *testing.T) {
|
||||
messages := make([][]byte, 10)
|
||||
for i := 0; i < 9; i++ {
|
||||
message := make([]byte, 20)
|
||||
readRand(message, t)
|
||||
messages[i] = message
|
||||
}
|
||||
// Duplicate message
|
||||
messages[9] = messages[0]
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.AggregateVerify(pks[2:], messages[2:], sigs); res {
|
||||
t.Errorf("Expected aggregateVerify to fail with duplicate message but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVerifyG2OneMsg(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
messages[0] = make([]byte, 20)
|
||||
sk := genSecretKey(t)
|
||||
sig := genSignature(sk, messages[0], t)
|
||||
pk := genPublicKey(sk, t)
|
||||
bls := NewSigPop()
|
||||
// Should be the same as verifySignature
|
||||
if res, _ := bls.AggregateVerify([]*PublicKey{pk}, messages, []*Signature{sig}); !res {
|
||||
t.Errorf("Expected AggregateVerifyG2OneMsg to pass but failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyG2Mutability(t *testing.T) {
|
||||
// verify should not change any inputs
|
||||
ikm := make([]byte, 32)
|
||||
ikm_copy := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
copy(ikm_copy, ikm)
|
||||
bls := NewSigPop()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPop.KeygenWithSeed modifies ikm")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Expected KeygenWithSeed to succeed but failed.")
|
||||
}
|
||||
sig, err := bls.Sign(sk, ikm)
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPop.Sign modifies message")
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("SigPop.KeygenWithSeed to succeed but failed.")
|
||||
}
|
||||
sigCopy := marshalStruct(sig, t)
|
||||
if res, _ := bls.Verify(pk, ikm, sig); !res {
|
||||
t.Errorf("Expected verify to succeed but failed.")
|
||||
}
|
||||
if !bytes.Equal(ikm, ikm_copy) {
|
||||
t.Errorf("SigPop.verify modifies message")
|
||||
}
|
||||
if !bytes.Equal(sigCopy, marshalStruct(sig, t)) {
|
||||
t.Errorf("SigPop.verify modifies signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicKeyG1FromBadBytes(t *testing.T) {
|
||||
pk := make([]byte, 32)
|
||||
err := new(PublicKey).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG1FromBytes to fail but passed")
|
||||
}
|
||||
// All zeros
|
||||
pk = make([]byte, PublicKeySize)
|
||||
// See https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// 1 << 7 == compressed
|
||||
// 1 << 6 == infinity or zero
|
||||
pk[0] = 0xc0
|
||||
err = new(PublicKey).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG1FromBytes to fail but passed")
|
||||
}
|
||||
sk := genSecretKey(t)
|
||||
pk1, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetPublicKey to pass but failed.")
|
||||
}
|
||||
out := marshalStruct(pk1, t)
|
||||
out[3] += 1
|
||||
err = new(PublicKey).UnmarshalBinary(pk)
|
||||
if err == nil {
|
||||
t.Errorf("Expected PublicKeyG1FromBytes to fail but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureG2FromBadBytes(t *testing.T) {
|
||||
sig := make([]byte, 32)
|
||||
err := new(Signature).UnmarshalBinary(sig)
|
||||
if err == nil {
|
||||
t.Errorf("Expected SignatureG2FromBytes to fail but passed")
|
||||
}
|
||||
// All zeros
|
||||
sig = make([]byte, SignatureSize)
|
||||
// See https://github.com/zcash/librustzcash/blob/master/pairing/src/bls12_381/README.md#serialization
|
||||
// 1 << 7 == compressed
|
||||
// 1 << 6 == infinity or zero
|
||||
sig[0] = 0xc0
|
||||
err = new(Signature).UnmarshalBinary(sig)
|
||||
if err == nil {
|
||||
t.Errorf("Expected SignatureG2FromBytes to fail but passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSecretKeyG2(t *testing.T) {
|
||||
sk := &SecretKey{value: bls12381.Bls12381FqNew()}
|
||||
pk, err := sk.GetPublicKey()
|
||||
if err == nil {
|
||||
t.Errorf("Expected GetPublicKey to fail with 0 byte secret key but passed: %v", pk)
|
||||
}
|
||||
_ = sk.UnmarshalBinary(sk.value.Params.BiModulus.Bytes())
|
||||
pk, err = sk.GetPublicKey()
|
||||
if err == nil {
|
||||
t.Errorf("Expected GetPublicKey to fail with secret key with Q but passed: %v", pk)
|
||||
}
|
||||
|
||||
err = sk.UnmarshalBinary([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
|
||||
if err == nil {
|
||||
t.Errorf("Expected SecretKeyFromBytes to fail with not enough bytes but passed: %v", pk)
|
||||
}
|
||||
|
||||
err = sk.UnmarshalBinary(make([]byte, 32))
|
||||
if err == nil {
|
||||
t.Errorf("Expected SecretKeyFromBytes to fail with all zeros but passed: %v", pk)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG2Works(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
bls := NewSigPop()
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Key gen failed but should've succeeded")
|
||||
}
|
||||
pop, err := bls.PopProve(sk)
|
||||
if err != nil {
|
||||
t.Errorf("PopProve failed but should've succeeded")
|
||||
}
|
||||
if res, _ := bls.PopVerify(pk, pop); !res {
|
||||
t.Errorf("PopVerify failed but should've succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG2FromBadKey(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
value := new(big.Int)
|
||||
value.SetBytes(ikm)
|
||||
sk := SecretKey{value: bls12381.Bls12381FqNew().SetBigInt(value)}
|
||||
_, err := sk.createProofOfPossession(blsSignaturePopDst)
|
||||
if err == nil {
|
||||
t.Errorf("createProofOfPossession should've failed but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG2BytesWorks(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pop, err := sk.createProofOfPossession(blsSignaturePopDst)
|
||||
if err != nil {
|
||||
t.Errorf("CreateProofOfPossesionG2 failed but shouldn've succeeded.")
|
||||
}
|
||||
out := marshalStruct(pop, t)
|
||||
if len(out) != ProofOfPossessionSize {
|
||||
t.Errorf(
|
||||
"ProofOfPossessionBytes incorrect size: expected %v, got %v",
|
||||
ProofOfPossessionSize,
|
||||
len(out),
|
||||
)
|
||||
}
|
||||
pop2 := new(ProofOfPossession)
|
||||
err = pop2.UnmarshalBinary(out)
|
||||
if err != nil {
|
||||
t.Errorf("ProofOfPossession.UnmarshalBinary failed: %v", err)
|
||||
}
|
||||
out2 := marshalStruct(pop2, t)
|
||||
if !bytes.Equal(out, out2) {
|
||||
t.Errorf("ProofOfPossession.UnmarshalBinary failed, not equal when deserialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG2BadBytes(t *testing.T) {
|
||||
zeros := make([]byte, ProofOfPossessionSize)
|
||||
temp := new(ProofOfPossession)
|
||||
err := temp.UnmarshalBinary(zeros)
|
||||
if err == nil {
|
||||
t.Errorf("ProofOfPossession.UnmarshalBinary shouldn've failed but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProofOfPossessionG2Fails(t *testing.T) {
|
||||
sk := genSecretKey(t)
|
||||
pop, err := sk.createProofOfPossession(blsSignaturePopDst)
|
||||
if err != nil {
|
||||
t.Errorf("Expected createProofOfPossession to succeed but failed.")
|
||||
}
|
||||
|
||||
ikm := make([]byte, 32)
|
||||
readRand(ikm, t)
|
||||
sk = genRandSecretKey(ikm, t)
|
||||
bad, err := sk.GetPublicKey()
|
||||
if err != nil {
|
||||
t.Errorf("Expected PublicKeyG1FromBytes to succeed but failed: %v", err)
|
||||
}
|
||||
if res, _ := pop.verify(bad, blsSignaturePopDst); res {
|
||||
t.Errorf("Expected ProofOfPossession verify to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSigG2Bytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
_, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
msig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
msigBytes := marshalStruct(msig, t)
|
||||
if len(msigBytes) != SignatureSize {
|
||||
t.Errorf(
|
||||
"Invalid multi-sig length. Expected %d bytes, found %d",
|
||||
SignatureSize,
|
||||
len(msigBytes),
|
||||
)
|
||||
}
|
||||
msig2 := new(MultiSignature)
|
||||
err = msig2.UnmarshalBinary(msigBytes)
|
||||
if err != nil {
|
||||
t.Errorf("MultiSignatureG2FromBytes failed with %v", err)
|
||||
}
|
||||
msigBytes2 := marshalStruct(msig2, t)
|
||||
|
||||
if !bytes.Equal(msigBytes, msigBytes2) {
|
||||
t.Errorf("Bytes methods not equal.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSigG2BadBytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
_, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
msig, err := bls.AggregateSignatures(sigs...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
msigBytes := marshalStruct(msig, t)
|
||||
if len(msigBytes) != SignatureSize {
|
||||
t.Errorf(
|
||||
"Invalid multi-sig length. Expected %d bytes, found %d",
|
||||
SignatureSize,
|
||||
len(msigBytes),
|
||||
)
|
||||
}
|
||||
msigBytes[0] = 0
|
||||
temp := new(MultiSignature)
|
||||
err = temp.UnmarshalBinary(msigBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiSignatureG2FromBytes should've failed but succeeded")
|
||||
}
|
||||
msigBytes = make([]byte, SignatureSize)
|
||||
err = temp.UnmarshalBinary(msigBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiSignatureG2FromBytes should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPubkeyG1Bytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
pks, _ := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apkBytes := marshalStruct(apk, t)
|
||||
if len(apkBytes) != PublicKeySize {
|
||||
t.Errorf("MultiPublicKey has an incorrect size")
|
||||
}
|
||||
apk2 := new(MultiPublicKey)
|
||||
err = apk2.UnmarshalBinary(apkBytes)
|
||||
if err != nil {
|
||||
t.Errorf("MultiPublicKey.UnmarshalBinary failed with %v", err)
|
||||
}
|
||||
apk2Bytes := marshalStruct(apk2, t)
|
||||
if !bytes.Equal(apkBytes, apk2Bytes) {
|
||||
t.Errorf("Bytes methods not equal.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPubkeyG1BadBytes(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 20)
|
||||
messages[0] = message
|
||||
pks, _ := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
apk, err := bls.AggregatePublicKeys(pks...)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
apkBytes := marshalStruct(apk, t)
|
||||
if len(apkBytes) != PublicKeySize {
|
||||
t.Errorf("MultiPublicKey has an incorrect size")
|
||||
}
|
||||
apkBytes[0] = 0
|
||||
temp := new(MultiPublicKey)
|
||||
err = temp.UnmarshalBinary(apkBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiPublicKey.UnmarshalBinary should've failed but succeeded")
|
||||
}
|
||||
apkBytes = make([]byte, PublicKeySize)
|
||||
err = temp.UnmarshalBinary(apkBytes)
|
||||
if err == nil {
|
||||
t.Errorf("MultiPublicKey.UnmarshalBinary should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyG2Works(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
asigs, _ := aggregateSignatures(sigs...)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.FastAggregateVerify(pks, message, asigs); !res {
|
||||
t.Errorf("FastAggregateVerify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyConstituentG2Works(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, message, sigs); !res {
|
||||
t.Errorf("FastAggregateVerify failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastAggregateVerifyG2Fails(t *testing.T) {
|
||||
messages := make([][]byte, 1)
|
||||
message := make([]byte, 1)
|
||||
messages[0] = message
|
||||
pks, sigs := initAggregatedTestValuesG2(messages, t)
|
||||
bls := NewSigPop()
|
||||
message[0] = 1
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, message, sigs); res {
|
||||
t.Errorf("FastAggregateVerify verified when it should've failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomPopDstG2Works(t *testing.T) {
|
||||
bls, _ := NewSigPopWithDst("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_TEST",
|
||||
"BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_TEST")
|
||||
msg := make([]byte, 20)
|
||||
ikm := make([]byte, 32)
|
||||
pk, sk, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't create custom dst keys: %v", err)
|
||||
}
|
||||
sig, err := bls.Sign(sk, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't sign with custom dst: %v", err)
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("verify fails with custom dst")
|
||||
}
|
||||
|
||||
pks := make([]*PublicKey, 10)
|
||||
sigs := make([]*Signature, 10)
|
||||
pks[0] = pk
|
||||
sigs[0] = sig
|
||||
for i := 1; i < 10; i++ {
|
||||
readRand(ikm, t)
|
||||
pkt, skt, err := bls.KeygenWithSeed(ikm)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't create custom dst keys: %v", err)
|
||||
}
|
||||
sigt, err := bls.Sign(skt, msg)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't sign with custom dst: %v", err)
|
||||
}
|
||||
pks[i] = pkt
|
||||
sigs[i] = sigt
|
||||
}
|
||||
|
||||
if res, _ := bls.FastAggregateVerifyConstituent(pks, msg, sigs); !res {
|
||||
t.Errorf("FastAggregateVerify failed with custom dst")
|
||||
}
|
||||
|
||||
pop, err := bls.PopProve(sk)
|
||||
if err != nil {
|
||||
t.Errorf("PopProve failed with custom dst")
|
||||
}
|
||||
|
||||
if res, _ := bls.PopVerify(pk, pop); !res {
|
||||
t.Errorf("PopVerify failed with custom dst")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsPopG2KeyGenWithSeed(t *testing.T) {
|
||||
ikm := []byte("Not enough bytes")
|
||||
bls := NewSigPop()
|
||||
_, _, err := bls.KeygenWithSeed(ikm)
|
||||
if err == nil {
|
||||
t.Errorf("Expected KeygenWithSeed to fail but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlsPopG2KeyGen(t *testing.T) {
|
||||
bls := NewSigPop()
|
||||
_, _, err := bls.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopThresholdKeygenBadInputs(t *testing.T) {
|
||||
bls := NewSigPop()
|
||||
_, _, err := bls.ThresholdKeygen(0, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(1, 0)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
_, _, err = bls.ThresholdKeygen(3, 2)
|
||||
if err == nil {
|
||||
t.Errorf("ThresholdKeygen should've failed but succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopThresholdKeygen(t *testing.T) {
|
||||
bls := NewSigPop()
|
||||
_, sks, err := bls.ThresholdKeygen(3, 5)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
if len(sks) != 5 {
|
||||
t.Errorf("ThresholdKeygen did not produce enough shares")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopPartialSign(t *testing.T) {
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigPop()
|
||||
pk, sks, err := bls.ThresholdKeygenWithSeed(ikm, 2, 4)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed")
|
||||
}
|
||||
msg := make([]byte, 10)
|
||||
sig1, err := bls.PartialSign(sks[0], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig2, err := bls.PartialSign(sks[1], msg)
|
||||
if err != nil {
|
||||
t.Errorf("partialSign failed: %v", err)
|
||||
}
|
||||
sig, err := bls.CombineSignatures(sig1, sig2)
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
|
||||
if res, _ := bls.Verify(pk, msg, sig); !res {
|
||||
t.Errorf("Combined signature does not verify")
|
||||
}
|
||||
|
||||
sig, err = bls.CombineSignatures(sig1)
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures succeeded when it should've failed")
|
||||
}
|
||||
if res, _ := bls.Verify(pk, msg, sig); res {
|
||||
t.Errorf("Combined signature verify succeeded when it should've failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that mixed partial signatures from distinct origins create invalid composite signatures
|
||||
func TestPopPartialMixupShares(t *testing.T) {
|
||||
total := uint(5)
|
||||
ikm := make([]byte, 32)
|
||||
bls := NewSigPop()
|
||||
pk1, sks1, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
for i := range ikm {
|
||||
ikm[i] = 1
|
||||
}
|
||||
pk2, sks2, err := bls.ThresholdKeygenWithSeed(ikm, 3, total)
|
||||
if err != nil {
|
||||
t.Errorf("ThresholdKeygen failed: %v", err)
|
||||
}
|
||||
|
||||
// Generate partial signatures for both sets of keys
|
||||
msg := make([]byte, 10)
|
||||
sigs1 := make([]*PartialSignature, total)
|
||||
sigs2 := make([]*PartialSignature, total)
|
||||
for i := range sks1 {
|
||||
sigs1[i], err = bls.PartialSign(sks1[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
sigs2[i], err = bls.PartialSign(sks2[i], msg)
|
||||
if err != nil {
|
||||
t.Errorf("PartialSign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try combining 2 from group 1 and 2 from group 2
|
||||
sig, err := bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[2], sigs2[3])
|
||||
if err != nil {
|
||||
t.Errorf("CombineSignatures failed: %v", err)
|
||||
}
|
||||
// Signature shouldn't validate
|
||||
if res, _ := bls.Verify(pk1, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
if res, _ := bls.Verify(pk2, msg, sig); res {
|
||||
t.Errorf(
|
||||
"CombineSignatures worked with different shares of two secret keys for the same message",
|
||||
)
|
||||
}
|
||||
// Should error out due to duplicate identifiers
|
||||
_, err = bls.CombineSignatures(sigs1[0], sigs1[1], sigs2[0], sigs2[1])
|
||||
if err == nil {
|
||||
t.Errorf("CombineSignatures expected to fail but succeeded.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSigEth2KeyGen(t *testing.T) {
|
||||
eth2 := NewSigEth2()
|
||||
_, _, err := eth2.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSigEth2SignRoundTrip(t *testing.T) {
|
||||
eth2 := NewSigEth2()
|
||||
pop := NewSigPop()
|
||||
eth2Pk, eth2Sk, err := eth2.Keygen()
|
||||
if err != nil {
|
||||
t.Errorf("Keygen failed: %v", err)
|
||||
}
|
||||
|
||||
sig, err := pop.Sign(eth2Sk, []byte{0, 0})
|
||||
if err != nil {
|
||||
t.Errorf("Sign failed: %v", err)
|
||||
}
|
||||
if ok, err := eth2.Verify(eth2Pk, []byte{0, 0}, sig); err != nil || !ok {
|
||||
t.Errorf("Verify failed: %v", err)
|
||||
}
|
||||
}
|
||||
19
signatures/bls/rust/Cargo.toml
Executable file
19
signatures/bls/rust/Cargo.toml
Executable file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "miracl"
|
||||
version = "0.1.0"
|
||||
authors = ["Mike Lodder <mike.lodder@coinbase.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bls_sigs_ref = "0.3"
|
||||
hex = "0.4"
|
||||
miracl_core = { version = "2.3", features = [
|
||||
"bls12381",
|
||||
], default-features = false }
|
||||
pairing-plus = "0.19"
|
||||
rand = "0.8"
|
||||
serious = { version = "0.1", git = "https://github.com/mikelodder7/malutils" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.8"
|
||||
structopt = "0.3"
|
||||
13
signatures/bls/rust/README.md
Executable file
13
signatures/bls/rust/README.md
Executable file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
aliases: [README]
|
||||
tags: []
|
||||
title: README
|
||||
linter-yaml-title-alias: README
|
||||
date created: Wednesday, April 17th 2024, 4:11:40 pm
|
||||
date modified: Thursday, April 18th 2024, 8:19:25 am
|
||||
---
|
||||
|
||||
## Note
|
||||
|
||||
This rust package is added for the sole purpose of comparing the result of our Go package with other
|
||||
implementations, including the rust package.
|
||||
388
signatures/bls/rust/src/main.rs
Executable file
388
signatures/bls/rust/src/main.rs
Executable file
@@ -0,0 +1,388 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use bls_sigs_ref::BLSSignaturePop;
|
||||
use miracl_core::bls12381::{big::BIG, ecp::ECP, ecp2::ECP2, rom::MODULUS};
|
||||
use pairing_plus::{
|
||||
bls12_381::{Fr, G1, G2},
|
||||
hash_to_field::BaseFromRO,
|
||||
serdes::SerDes,
|
||||
CurveProjective,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use serious::Encoding;
|
||||
use sha2::digest::generic_array::GenericArray;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, Cursor, Read},
|
||||
path::PathBuf,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
|
||||
fn main() {
|
||||
let args = CliArgs::from_args();
|
||||
|
||||
match args {
|
||||
CliArgs::Generate { number } => generate(number),
|
||||
CliArgs::PublicKey { keys } => pubkey(keys),
|
||||
CliArgs::Sign { number, data } => sign(number, data),
|
||||
CliArgs::Verify { keys } => verify(keys),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate(number: usize) {
|
||||
let mut rng = thread_rng();
|
||||
print!("[");
|
||||
let mut sep = "";
|
||||
for _ in 0..number {
|
||||
let mut buf = [0u8; 48];
|
||||
rng.fill_bytes(&mut buf);
|
||||
// let mut sk = buf;
|
||||
let fr = Fr::from_okm(GenericArray::from_slice(&buf));
|
||||
let mut pk = G1::one();
|
||||
pk.mul_assign(fr);
|
||||
|
||||
pk.serialize(&mut buf.as_mut(), true).unwrap();
|
||||
print!("{}\"{}\"", sep, hex::encode(buf));
|
||||
sep = ",";
|
||||
|
||||
// Miracl expects 48 bytes for secret key even though the top 16 bytes are zeros
|
||||
// sk = [0u8; 48];
|
||||
// fr.serialize(&mut sk[16..].as_mut(), true).unwrap();
|
||||
// let s = BIG::frombytes(&sk[..]);
|
||||
// let x = _compress_g1(g1mul(&ECP::generator(), &s));
|
||||
//
|
||||
// println!("Miracl = {}", hex::encode(x));
|
||||
}
|
||||
print!("]");
|
||||
}
|
||||
|
||||
/// Compress to BLS12-381 standard vs ANSI X9.62
|
||||
fn _compress_g1(pk: ECP) -> [u8; 48] {
|
||||
let mut x = [0u8; 48];
|
||||
pk.getx().tobytes(&mut x);
|
||||
if pk.is_infinity() {
|
||||
// Set the second-most significant bit to indicate this point
|
||||
// is at infinity.
|
||||
x[0] |= 1 << 6;
|
||||
} else {
|
||||
let m = BIG { w: MODULUS };
|
||||
let mut negy = BIG::new();
|
||||
negy.add(&BIG::modneg(&pk.gety(), &m));
|
||||
negy.rmod(&m);
|
||||
negy.norm();
|
||||
// Set the third most significant bit if the correct y-coordinate
|
||||
// is lexicographically largest.
|
||||
if BIG::comp(&pk.gety(), &negy) == 1 {
|
||||
x[0] |= 1 << 5;
|
||||
}
|
||||
}
|
||||
// Set highest bit to distinguish this as a compressed element.
|
||||
x[0] |= 1 << 7;
|
||||
x
|
||||
}
|
||||
|
||||
fn _compress_g2(sig: ECP2) -> [u8; 96] {
|
||||
let mut x = [0u8; 96];
|
||||
sig.getx().geta().tobytes(&mut x[..48]);
|
||||
sig.getx().getb().tobytes(&mut x[48..]);
|
||||
if sig.is_infinity() {
|
||||
// Set the second-most significant bit to indicate this point
|
||||
// is at infinity.
|
||||
x[0] |= 1 << 6;
|
||||
} else {
|
||||
let mut negy = sig.clone();
|
||||
negy.neg();
|
||||
|
||||
// Set the third most significant bit if the correct y-coordinate
|
||||
// is lexicographically largest.
|
||||
negy.sub(&sig);
|
||||
|
||||
if negy.gety().sign() > 0 {
|
||||
x[0] |= 1 << 5;
|
||||
}
|
||||
}
|
||||
// Set highest bit to distinguish this as a compressed element.
|
||||
x[0] |= 1 << 7;
|
||||
x
|
||||
}
|
||||
|
||||
fn pubkey(keys: String) {
|
||||
let res = read_input(&keys).unwrap();
|
||||
|
||||
for pubkey in serde_json::from_slice::<Vec<String>>(&res).unwrap() {
|
||||
let res = hex::decode(&pubkey);
|
||||
if res.is_err() {
|
||||
println!("Invalid hex format {}", res.unwrap_err());
|
||||
continue;
|
||||
}
|
||||
let mut key = res.unwrap();
|
||||
let mut cur = Cursor::new(key.as_slice());
|
||||
print!("ZCash {} - ", pubkey);
|
||||
let res = G1::deserialize(&mut cur, true);
|
||||
|
||||
if let Err(e) = res {
|
||||
println!("fail - {}", e);
|
||||
} else {
|
||||
println!("pass");
|
||||
}
|
||||
|
||||
let res = _uncompress_g1(key.as_mut_slice());
|
||||
|
||||
print!("Miracl {} - ", pubkey);
|
||||
if let Err(e) = res {
|
||||
println!("fail - {}", e);
|
||||
} else {
|
||||
println!("pass");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _uncompress_g1(d: &[u8]) -> Result<ECP, String> {
|
||||
if d.len() != 48 {
|
||||
return Err("Invalid length".to_string());
|
||||
}
|
||||
|
||||
if d[0] & 0x80 != 0x80 {
|
||||
return Err("Expected compressed point".to_string());
|
||||
}
|
||||
|
||||
// Expect point at infinity
|
||||
if d[0] & 0x40 == 0x40 {
|
||||
return if !d.iter().skip(1).all(|b| *b == 0) {
|
||||
Err("Expected point at infinity but found another point".to_string())
|
||||
} else {
|
||||
Ok(ECP::new())
|
||||
};
|
||||
}
|
||||
|
||||
let s = d[0] & 0x20;
|
||||
// Unset top bits
|
||||
let mut dd = [0u8; 48];
|
||||
dd.copy_from_slice(d);
|
||||
dd[0] &= 0x1F;
|
||||
let x = BIG::frombytes(&dd);
|
||||
Ok(ECP::new_bigint(&x, s as isize))
|
||||
}
|
||||
|
||||
fn _uncompress_g2(d: &[u8]) -> Result<ECP2, String> {
|
||||
if d.len() != 96 {
|
||||
return Err("Invalid length".to_string());
|
||||
}
|
||||
|
||||
if d[0] & 0x80 != 0x80 {
|
||||
return Err("Expected compressed point".to_string());
|
||||
}
|
||||
|
||||
// Expect point at infinity
|
||||
if d[0] & 0x40 == 0x40 {
|
||||
return if !d.iter().skip(1).all(|b| *b == 0) {
|
||||
Err("Expected point at infinity but found another point".to_string())
|
||||
} else {
|
||||
Ok(ECP2::new())
|
||||
};
|
||||
}
|
||||
|
||||
let s = d[0] & 0x20;
|
||||
let mut dd = [0u8; 97];
|
||||
dd[1..].copy_from_slice(d);
|
||||
dd[1] &= 0x1F;
|
||||
// Unset top bits
|
||||
dd[0] = if s > 0 { 0x3 } else { 0x2 };
|
||||
Ok(ECP2::frombytes(&dd))
|
||||
}
|
||||
|
||||
fn sign(number: usize, data: String) {
|
||||
let mut rng = thread_rng();
|
||||
let bytes = data.as_bytes();
|
||||
let mut sep = "";
|
||||
print!("[");
|
||||
for _ in 0..number {
|
||||
let mut buf = [0u8; 48];
|
||||
rng.fill_bytes(&mut buf);
|
||||
let fr = Fr::from_okm(GenericArray::from_slice(&buf));
|
||||
let mut pk = G1::one();
|
||||
pk.mul_assign(fr);
|
||||
|
||||
let mut pubkey = [0u8; 48];
|
||||
pk.serialize(&mut pubkey.as_mut(), true).unwrap();
|
||||
|
||||
let signature = G2::sign(fr, bytes);
|
||||
let mut sig = [0u8; 96];
|
||||
signature.serialize(&mut sig.as_mut(), true).unwrap();
|
||||
|
||||
print!("{}{{", sep);
|
||||
print!(r#""data":"{}","#, data);
|
||||
print!(
|
||||
r#""public_key":"{}","#,
|
||||
Encoding::encode(pubkey, Encoding::LowHex).into_string()
|
||||
);
|
||||
print!(
|
||||
r#""signature":"{}""#,
|
||||
Encoding::encode(sig, Encoding::LowHex).into_string()
|
||||
);
|
||||
print!("}}");
|
||||
sep = ",";
|
||||
}
|
||||
print!("]");
|
||||
}
|
||||
|
||||
fn verify(keys: String) {
|
||||
let res = read_input(&keys).unwrap();
|
||||
|
||||
for req in serde_json::from_slice::<Vec<VerifyRequest>>(&res).unwrap() {
|
||||
let pubkey = Encoding::decode(&req.public_key, Encoding::LowHex).unwrap();
|
||||
let sig = Encoding::decode(&req.signature, Encoding::LowHex).unwrap();
|
||||
let mut cur = Cursor::new(pubkey.as_slice());
|
||||
let verkey = G1::deserialize(&mut cur, true).unwrap();
|
||||
cur = Cursor::new(sig.as_slice());
|
||||
let signature = G2::deserialize(&mut cur, true).unwrap();
|
||||
|
||||
print!("ZCash {} - ", req.public_key);
|
||||
if G2::verify(verkey, signature, req.data.as_bytes()) {
|
||||
println!("pass");
|
||||
} else {
|
||||
println!("fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
enum CliArgs {
|
||||
Generate {
|
||||
#[structopt(short, long)]
|
||||
number: usize,
|
||||
},
|
||||
PublicKey {
|
||||
#[structopt(name = "KEYS")]
|
||||
keys: String,
|
||||
},
|
||||
Sign {
|
||||
#[structopt(short, long)]
|
||||
number: usize,
|
||||
#[structopt(short, long)]
|
||||
data: String,
|
||||
},
|
||||
Verify {
|
||||
#[structopt(name = "KEYS")]
|
||||
keys: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct VerifyRequest {
|
||||
data: String,
|
||||
public_key: String,
|
||||
signature: String,
|
||||
}
|
||||
|
||||
fn read_input(value: &str) -> Result<Vec<u8>, String> {
|
||||
if !value.is_empty() {
|
||||
match get_file(value) {
|
||||
Some(file) => match File::open(file.as_path()) {
|
||||
Ok(mut f) => Ok(read_stream(&mut f)),
|
||||
Err(_) => Err(format!("Unable to read file {}", file.to_str().unwrap())),
|
||||
},
|
||||
None => Ok(value.as_bytes().to_vec()),
|
||||
}
|
||||
} else {
|
||||
let mut f = io::stdin();
|
||||
Ok(read_stream(&mut f))
|
||||
}
|
||||
}
|
||||
|
||||
fn read_stream<R: Read>(f: &mut R) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
let mut buffer = [0u8; 4096];
|
||||
|
||||
let mut read = f.read(&mut buffer);
|
||||
while read.is_ok() {
|
||||
let n = read.unwrap();
|
||||
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&buffer[..n]);
|
||||
|
||||
read = f.read(&mut buffer);
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
fn get_file(name: &str) -> Option<PathBuf> {
|
||||
if name.len() > 256 {
|
||||
// too long to be a file
|
||||
return None;
|
||||
}
|
||||
let mut file = PathBuf::new();
|
||||
file.push(name);
|
||||
if file.as_path().is_file() {
|
||||
let metadata = file
|
||||
.as_path()
|
||||
.symlink_metadata()
|
||||
.expect("symlink_metadata call failed");
|
||||
if metadata.file_type().is_symlink() {
|
||||
if let Ok(f) = file.as_path().read_link() {
|
||||
file = f
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(file)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// fn from_encoding(src: &str) -> Result<Encoding, std::io::Error> {
|
||||
// Encoding::parse(src).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
|
||||
// }
|
||||
|
||||
// fn hash_to_g2(d: &[u8]) -> ECP2 {
|
||||
// const DST: &'static str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
|
||||
// let y = <G2 as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(d.as_ref(), DST);
|
||||
// let mut d = [0u8; 193];
|
||||
// d[0] = 0x4;
|
||||
// y.serialize(&mut d[1..].as_mut(), false);
|
||||
// ECP2::frombytes(&d)
|
||||
// }
|
||||
|
||||
// fn ceil(a: usize, b: usize) -> usize {
|
||||
// (a-1)/b+1
|
||||
// }
|
||||
//
|
||||
// fn hash_to_field(hash: usize, hlen: usize, u: &mut [FP2], dst: &[u8], m: &[u8], ctr: usize) {
|
||||
// let q = BIG { w: MODULUS };
|
||||
// let el = ceil(q.nbits()+AESKEY*16, 8);
|
||||
//
|
||||
// let mut okm = [0u8; 512];
|
||||
// let mut fd = [0u8; 256];
|
||||
// xmd_expand(hash, hlen, &mut okm, el*ctr, &dst, &m);
|
||||
// u[0] = FP2::new_fps(
|
||||
// &FP::new_big(&DBIG::frombytes(&okm[0..el]).dmod(&q)),
|
||||
// &FP::new_big(&DBIG::frombytes(&okm[el..(2*el)]).dmod(&q))
|
||||
// );
|
||||
// u[1] = FP2::new_fps(
|
||||
// &FP::new_big(&DBIG::frombytes(&okm[(2*el)..(3*el)]).dmod(&q)),
|
||||
// &FP::new_big(&DBIG::frombytes(&okm[(3*el)..]).dmod(&q))
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// fn hash_to_ecp2(m: &[u8]) -> ECP2 {
|
||||
// let dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
|
||||
// let mut u = [FP2::new(); 2];
|
||||
// hash_to_field(hmac::MC_SHA2, ecp::HASH_TYPE, &mut u, &dst[..], m, 2);
|
||||
//
|
||||
// let mut p = ECP2::map2point(&u[0]);
|
||||
// let q = ECP2::map2point(&u[1]);
|
||||
// p.add(&q);
|
||||
// p.cfp();
|
||||
// p.affine();
|
||||
// p
|
||||
// }
|
||||
236
signatures/bls/tests/bls/main.go
Normal file
236
signatures/bls/tests/bls/main.go
Normal file
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
bls "github.com/sonr-io/sonr/crypto/signatures/bls/bls_sig"
|
||||
)
|
||||
|
||||
type signOp struct {
|
||||
Data string `json:"data"`
|
||||
PublicKey string `json:"public_key"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type cmdFlags struct {
|
||||
Generate bool
|
||||
PublicKeys bool
|
||||
Sign bool
|
||||
Verify bool
|
||||
Number int
|
||||
}
|
||||
|
||||
func parseCliArgs() cmdFlags {
|
||||
var generate, publickeys, sign, verify bool
|
||||
var number int
|
||||
flag.BoolVar(&generate, "g", false, "Generate public keys")
|
||||
flag.BoolVar(&publickeys, "p", false, "Verify public keys")
|
||||
flag.BoolVar(&sign, "s", false, "Generate signatures")
|
||||
flag.BoolVar(&verify, "v", false, "Verify signatures")
|
||||
flag.IntVar(&number, "n", 25, "The number of items to generate")
|
||||
flag.Parse()
|
||||
return cmdFlags{
|
||||
generate, publickeys, sign, verify, number,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := parseCliArgs()
|
||||
|
||||
if flags.Generate {
|
||||
generate(flags.Number)
|
||||
} else if flags.PublicKeys {
|
||||
publicKeys()
|
||||
} else if flags.Sign {
|
||||
sign(flags.Number)
|
||||
} else if flags.Verify {
|
||||
verify()
|
||||
}
|
||||
}
|
||||
|
||||
func generate(number int) {
|
||||
scheme := bls.NewSigPop()
|
||||
publicKeys := make([]string, number)
|
||||
for i := 0; i < number; i++ {
|
||||
publicKey, _, err := scheme.Keygen()
|
||||
if err != nil {
|
||||
fmt.Printf("Keygen error occurred: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
b, _ := publicKey.MarshalBinary()
|
||||
publicKeys[i] = hex.EncodeToString(b)
|
||||
}
|
||||
out, _ := json.Marshal(publicKeys)
|
||||
fmt.Println(string(out))
|
||||
}
|
||||
|
||||
func publicKeys() {
|
||||
input, err := getInput()
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to read input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var pubkeys []string
|
||||
err = json.Unmarshal(input, &pubkeys)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to parse json input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Checking public keys")
|
||||
for _, pk := range pubkeys {
|
||||
data, err := hex.DecodeString(pk)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to parse hex input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pubkey := new(bls.PublicKey)
|
||||
fmt.Printf("Checking %s - ", pk)
|
||||
err = pubkey.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
fmt.Printf("fail\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("pass\n")
|
||||
}
|
||||
}
|
||||
|
||||
func sign(number int) {
|
||||
tests := []string{
|
||||
"", "aaa", "aaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
}
|
||||
|
||||
var err error
|
||||
scheme := bls.NewSigPop()
|
||||
secretKeys := make([]*bls.SecretKey, number)
|
||||
publicKeys := make([]*bls.PublicKey, number)
|
||||
for i := 0; i < number; i++ {
|
||||
publicKeys[i], secretKeys[i], err = scheme.Keygen()
|
||||
if err != nil {
|
||||
fmt.Printf("Keygen error occurred: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sigs := make([]*signOp, number*len(tests))
|
||||
|
||||
k := 0
|
||||
for _, t := range tests {
|
||||
for j := 0; j < len(secretKeys); j++ {
|
||||
signature, err := scheme.Sign(secretKeys[j], []byte(t))
|
||||
if err != nil {
|
||||
fmt.Printf("Signing failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pk, _ := publicKeys[j].MarshalBinary()
|
||||
sig, _ := signature.MarshalBinary()
|
||||
sigs[k] = &signOp{
|
||||
Data: t,
|
||||
PublicKey: hex.EncodeToString(pk),
|
||||
Signature: hex.EncodeToString(sig),
|
||||
}
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(sigs)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to convert signatures to json")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(string(data))
|
||||
}
|
||||
|
||||
func verify() {
|
||||
input, err := getInput()
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to read input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var operations []signOp
|
||||
err = json.Unmarshal(input, &operations)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to parse json input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
scheme := bls.NewSigPop()
|
||||
for _, op := range operations {
|
||||
fmt.Printf("Checking %s - ", op.PublicKey)
|
||||
data, err := hex.DecodeString(op.PublicKey)
|
||||
if err != nil {
|
||||
fmt.Printf("fail. Unable to parse hex input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
pubkey := new(bls.PublicKey)
|
||||
err = pubkey.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
fmt.Printf("fail. Invalid public key: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
data, err = hex.DecodeString(op.Signature)
|
||||
if err != nil {
|
||||
fmt.Printf("fail. Unable to parse hex input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sig := new(bls.Signature)
|
||||
err = sig.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
fmt.Printf("fail. Invalid signature format: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ok, err := scheme.Verify(pubkey, []byte(op.Data), sig)
|
||||
if !ok || err != nil {
|
||||
fmt.Printf("fail. Invalid signature")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("pass.")
|
||||
}
|
||||
}
|
||||
|
||||
func getInput() ([]byte, error) {
|
||||
fi, _ := os.Stdin.Stat()
|
||||
|
||||
var data []byte
|
||||
|
||||
if (fi.Mode() & os.ModeCharDevice) == 0 {
|
||||
// Read from pipe
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
data = append(data, scanner.Bytes()...)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Read from file
|
||||
arguments := flag.Args()
|
||||
if len(arguments) < 1 {
|
||||
return nil, fmt.Errorf("expected data argument")
|
||||
}
|
||||
|
||||
_, err := os.Stat(arguments[0])
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
data = []byte(arguments[0])
|
||||
} else {
|
||||
contents, err := os.ReadFile(arguments[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = contents
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
14
signatures/common/challenge.go
Normal file
14
signatures/common/challenge.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Challenge generated by fiat-shamir heuristic
|
||||
type Challenge = curves.Scalar
|
||||
15
signatures/common/commitment.go
Normal file
15
signatures/common/commitment.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Commitment represents a point Pedersen commitment of one or more
|
||||
// points multiplied by scalars
|
||||
type Commitment = curves.Point
|
||||
99
signatures/common/hmacdrbg.go
Executable file
99
signatures/common/hmacdrbg.go
Executable file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// HmacDrbg is an HMAC deterministic random bit generator
|
||||
// that can use any hash function. Handles reseeding
|
||||
// automatically
|
||||
type HmacDrbg struct {
|
||||
k, v []byte
|
||||
count int
|
||||
hasher func() hash.Hash
|
||||
}
|
||||
|
||||
func NewHmacDrbg(entropy, nonce, pers []byte, hasher func() hash.Hash) *HmacDrbg {
|
||||
drbg := new(HmacDrbg)
|
||||
h := hasher()
|
||||
drbg.k = make([]byte, h.Size())
|
||||
drbg.v = make([]byte, h.Size())
|
||||
drbg.count = 0
|
||||
drbg.hasher = hasher
|
||||
|
||||
for i := range drbg.v {
|
||||
drbg.v[i] = 1
|
||||
}
|
||||
|
||||
drbg.update([][]byte{entropy, nonce, pers})
|
||||
drbg.count += 1
|
||||
return drbg
|
||||
}
|
||||
|
||||
func (drbg *HmacDrbg) Read(dst []byte) (n int, err error) {
|
||||
toRead := len(dst)
|
||||
if toRead == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
i := 0
|
||||
for i < toRead {
|
||||
vmac := drbg.getHmac()
|
||||
_, _ = vmac.Write(drbg.v)
|
||||
drbg.v = vmac.Sum(nil)
|
||||
|
||||
for j, b := range drbg.v {
|
||||
dst[i+j] = b
|
||||
}
|
||||
i += len(drbg.v)
|
||||
}
|
||||
drbg.update(nil)
|
||||
drbg.count++
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (drbg *HmacDrbg) Reseed(entropy []byte) {
|
||||
drbg.update([][]byte{entropy})
|
||||
}
|
||||
|
||||
func (drbg *HmacDrbg) getHmac() hash.Hash {
|
||||
return hmac.New(drbg.hasher, drbg.k)
|
||||
}
|
||||
|
||||
func (drbg *HmacDrbg) update(seeds [][]byte) {
|
||||
kmac := drbg.getHmac()
|
||||
_, _ = kmac.Write(drbg.v)
|
||||
_, _ = kmac.Write([]byte{0})
|
||||
if len(seeds) > 0 {
|
||||
for _, seed := range seeds {
|
||||
_, _ = kmac.Write(seed)
|
||||
}
|
||||
}
|
||||
drbg.k = kmac.Sum(nil)
|
||||
|
||||
vmac := drbg.getHmac()
|
||||
_, _ = vmac.Write(drbg.v)
|
||||
drbg.v = vmac.Sum(nil)
|
||||
|
||||
if len(seeds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
kmac = drbg.getHmac()
|
||||
_, _ = kmac.Write(drbg.v)
|
||||
_, _ = kmac.Write([]byte{1})
|
||||
for _, seed := range seeds {
|
||||
_, _ = kmac.Write(seed)
|
||||
}
|
||||
drbg.k = kmac.Sum(nil)
|
||||
|
||||
vmac = drbg.getHmac()
|
||||
_, _ = vmac.Write(drbg.v)
|
||||
drbg.v = vmac.Sum(nil)
|
||||
}
|
||||
15
signatures/common/nonce.go
Normal file
15
signatures/common/nonce.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Nonce is used for zero-knowledge proofs to prevent replay attacks
|
||||
// and prove freshness
|
||||
type Nonce = curves.Scalar
|
||||
89
signatures/common/proof_committed_builder.go
Normal file
89
signatures/common/proof_committed_builder.go
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
const limit = 65535
|
||||
|
||||
// ProofCommittedBuilder is used to create
|
||||
// proofs from multiple commitments where
|
||||
// each secret is committed with a random blinding factor
|
||||
// and turned into a Schnorr proof
|
||||
type ProofCommittedBuilder struct {
|
||||
points []curves.Point
|
||||
scalars []curves.Scalar
|
||||
curve *curves.Curve
|
||||
}
|
||||
|
||||
// NewProofCommittedBuilder creates a new builder using the specified curve
|
||||
func NewProofCommittedBuilder(curve *curves.Curve) *ProofCommittedBuilder {
|
||||
return &ProofCommittedBuilder{
|
||||
points: []curves.Point{},
|
||||
scalars: []curves.Scalar{},
|
||||
curve: curve,
|
||||
}
|
||||
}
|
||||
|
||||
// CommitRandom uses the specified point and commits a random value to it
|
||||
func (pcb *ProofCommittedBuilder) CommitRandom(point curves.Point, reader io.Reader) error {
|
||||
if len(pcb.points) > limit {
|
||||
return fmt.Errorf("limit for commitments reached")
|
||||
}
|
||||
pcb.points = append(pcb.points, point)
|
||||
pcb.scalars = append(pcb.scalars, pcb.curve.Scalar.Random(reader))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit uses the specified point and scalar to create a commitment
|
||||
func (pcb *ProofCommittedBuilder) Commit(point curves.Point, scalar curves.Scalar) error {
|
||||
if len(pcb.points) > limit {
|
||||
return fmt.Errorf("limit for commitments reached")
|
||||
}
|
||||
pcb.points = append(pcb.points, point)
|
||||
pcb.scalars = append(pcb.scalars, scalar)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the point and scalar at the specified index
|
||||
func (pcb *ProofCommittedBuilder) Get(index int) (curves.Point, curves.Scalar) {
|
||||
if index >= len(pcb.points) || index < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return pcb.points[index], pcb.scalars[index]
|
||||
}
|
||||
|
||||
// GetChallengeContribution returns the bytes that should be added to
|
||||
// a sigma protocol transcript for generating the challenge
|
||||
func (pcb ProofCommittedBuilder) GetChallengeContribution() []byte {
|
||||
commitment := pcb.curve.Point.SumOfProducts(pcb.points, pcb.scalars)
|
||||
if commitment == nil {
|
||||
return nil
|
||||
}
|
||||
return commitment.ToAffineCompressed()
|
||||
}
|
||||
|
||||
// GenerateProof converts the blinding factors and secrets into Schnorr proofs
|
||||
func (pcb ProofCommittedBuilder) GenerateProof(
|
||||
challenge curves.Scalar,
|
||||
secrets []curves.Scalar,
|
||||
) ([]curves.Scalar, error) {
|
||||
if len(secrets) != len(pcb.scalars) {
|
||||
return nil, fmt.Errorf("secrets is not equal to blinding factors")
|
||||
}
|
||||
|
||||
proofs := make([]curves.Scalar, len(pcb.scalars))
|
||||
for i, sc := range pcb.scalars {
|
||||
proofs[i] = secrets[i].MulAdd(challenge, sc)
|
||||
}
|
||||
return proofs, nil
|
||||
}
|
||||
79
signatures/common/proof_message.go
Normal file
79
signatures/common/proof_message.go
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// ProofMessage classifies how a message is presented in a proof
|
||||
// Either Revealed or Hidden. Hidden has two sub categories:
|
||||
// proof specific i.e. the message is only used for this proof or
|
||||
// shared i.e. the message should be proved to be common across proofs
|
||||
type ProofMessage interface {
|
||||
// IsHidden indicates the message should be hidden
|
||||
IsHidden() bool
|
||||
// GetBlinding is used for hidden messages
|
||||
// blindings can either be proof specific to a signature
|
||||
// or involved with other proofs like boundchecks,
|
||||
// set memberships, or inequalities so the blinding
|
||||
// factor is shared among proofs to produce a common
|
||||
// schnorr linking proof
|
||||
GetBlinding(reader io.Reader) curves.Scalar
|
||||
// GetMessage returns the underlying message
|
||||
GetMessage() curves.Scalar
|
||||
}
|
||||
|
||||
type RevealedMessage struct {
|
||||
Message curves.Scalar
|
||||
}
|
||||
|
||||
func (r RevealedMessage) IsHidden() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r RevealedMessage) GetBlinding(reader io.Reader) curves.Scalar {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r RevealedMessage) GetMessage() curves.Scalar {
|
||||
return r.Message
|
||||
}
|
||||
|
||||
type ProofSpecificMessage struct {
|
||||
Message curves.Scalar
|
||||
}
|
||||
|
||||
func (ps ProofSpecificMessage) IsHidden() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ps ProofSpecificMessage) GetBlinding(reader io.Reader) curves.Scalar {
|
||||
return ps.Message.Random(reader)
|
||||
}
|
||||
|
||||
func (ps ProofSpecificMessage) GetMessage() curves.Scalar {
|
||||
return ps.Message
|
||||
}
|
||||
|
||||
type SharedBlindingMessage struct {
|
||||
Message, Blinding curves.Scalar
|
||||
}
|
||||
|
||||
func (ps SharedBlindingMessage) IsHidden() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ps SharedBlindingMessage) GetBlinding(reader io.Reader) curves.Scalar {
|
||||
return ps.Blinding
|
||||
}
|
||||
|
||||
func (ps SharedBlindingMessage) GetMessage() curves.Scalar {
|
||||
return ps.Message
|
||||
}
|
||||
14
signatures/common/signature_blinding.go
Normal file
14
signatures/common/signature_blinding.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// SignatureBlinding is a value used for computing blind signatures
|
||||
type SignatureBlinding = curves.PairingScalar
|
||||
232
signatures/schnorr/mina/bitvector.go
Executable file
232
signatures/schnorr/mina/bitvector.go
Executable file
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2014 Dropbox, Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software without
|
||||
// specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package mina
|
||||
|
||||
// A BitVector is a variable sized vector of bits. It supports
|
||||
// lookups, sets, appends, insertions, and deletions.
|
||||
//
|
||||
// This class is not thread safe.
|
||||
type BitVector struct {
|
||||
data []byte
|
||||
length int
|
||||
}
|
||||
|
||||
// NewBitVector creates and initializes a new bit vector with length
|
||||
// elements, using data as its initial contents.
|
||||
func NewBitVector(data []byte, length int) *BitVector {
|
||||
return &BitVector{
|
||||
data: data,
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns a slice of the contents of the bit vector. If the caller changes the returned slice,
|
||||
// the contents of the bit vector may change.
|
||||
func (vector *BitVector) Bytes() []byte {
|
||||
return vector.data
|
||||
}
|
||||
|
||||
// Length returns the current number of elements in the bit vector.
|
||||
func (vector *BitVector) Length() int {
|
||||
return vector.length
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit lower (less significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the most significant
|
||||
// position of the last byte in the slice.
|
||||
// This returns the bit that was shifted off of the last byte.
|
||||
func shiftLower(bit byte, b []byte) byte {
|
||||
bit = bit << 7
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
newByte := b[i] >> 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 1) << 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit >> 7
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit higher (more significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the least significant
|
||||
// position of the first byte in the slice.
|
||||
// This returns the bit that was shifted off the last byte.
|
||||
func shiftHigher(bit byte, b []byte) byte {
|
||||
for i := 0; i < len(b); i++ {
|
||||
newByte := b[i] << 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 0x80) >> 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit
|
||||
}
|
||||
|
||||
// Returns the minimum number of bytes needed for storing the bit vector.
|
||||
func (vector *BitVector) bytesLength() int {
|
||||
lastBitIndex := vector.length - 1
|
||||
lastByteIndex := lastBitIndex >> 3
|
||||
return lastByteIndex + 1
|
||||
}
|
||||
|
||||
// Panics if the given index is not within the bounds of the bit vector.
|
||||
func (vector *BitVector) indexAssert(i int) {
|
||||
if i < 0 || i >= vector.length {
|
||||
panic("Attempted to access element outside buffer")
|
||||
}
|
||||
}
|
||||
|
||||
// Append adds a bit to the end of a bit vector.
|
||||
func (vector *BitVector) Append(bit byte) {
|
||||
index := uint32(vector.length)
|
||||
vector.length++
|
||||
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := index >> 3
|
||||
byteOffset := index % 8
|
||||
oldByte := vector.data[byteIndex]
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// Set all bits except the byteOffset
|
||||
mask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & mask
|
||||
}
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Element returns the bit in the ith index of the bit vector.
|
||||
// Returned value is either 1 or 0.
|
||||
func (vector *BitVector) Element(i int) byte {
|
||||
vector.indexAssert(i)
|
||||
byteIndex := i >> 3
|
||||
byteOffset := uint32(i % 8)
|
||||
b := vector.data[byteIndex]
|
||||
// Check the offset bit
|
||||
return (b >> byteOffset) & 1
|
||||
}
|
||||
|
||||
// Set changes the bit in the ith index of the bit vector to the value specified in
|
||||
// bit.
|
||||
func (vector *BitVector) Set(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
// turn on the byteOffset'th bit
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// turn off the byteOffset'th bit
|
||||
removeMask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & removeMask
|
||||
}
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Insert inserts bit into the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted up by one.
|
||||
func (vector *BitVector) Insert(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length++
|
||||
|
||||
// Append an additional byte if necessary.
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
var bitToInsert byte
|
||||
if bit == 1 {
|
||||
bitToInsert = 1 << byteOffset
|
||||
}
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
// This bit will need to be shifted into the next byte
|
||||
leftoverBit := (oldByte & 0x80) >> 7
|
||||
// Make masks to pull off the bits below and above byteOffset
|
||||
// This mask has the byteOffset lowest bits set.
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This mask has the 8 - byteOffset top bits set.
|
||||
topMask := ^bottomMask
|
||||
top := (oldByte & topMask) << 1
|
||||
newByte := bitToInsert | (oldByte & bottomMask) | top
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
// Shift the rest of the bytes in the slice one higher, append
|
||||
// the leftoverBit obtained above.
|
||||
shiftHigher(leftoverBit, vector.data[byteIndex+1:])
|
||||
}
|
||||
|
||||
// Delete removes the bit in the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted down by one.
|
||||
func (vector *BitVector) Delete(index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length--
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
// Shift all the bytes above the byte we're modifying, return the
|
||||
// leftover bit to include in the byte we're modifying.
|
||||
bit := shiftLower(0, vector.data[byteIndex+1:])
|
||||
|
||||
// Modify oldByte.
|
||||
// At a high level, we want to select the bits above byteOffset,
|
||||
// and shift them down by one, removing the bit at byteOffset.
|
||||
|
||||
// This selects the bottom bits
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This selects the top (8 - byteOffset - 1) bits
|
||||
topMask := byte(^((1 << (byteOffset + 1)) - 1))
|
||||
// newTop is the top bits, shifted down one, combined with the leftover bit from shifting
|
||||
// the other bytes.
|
||||
newTop := (oldByte&topMask)>>1 | (bit << 7)
|
||||
// newByte takes the bottom bits and combines with the new top.
|
||||
newByte := (bottomMask & oldByte) | newTop
|
||||
vector.data[byteIndex] = newByte
|
||||
|
||||
// The desired length is the byte index of the last element plus one,
|
||||
// where the byte index of the last element is the bit index of the last
|
||||
// element divided by 8.
|
||||
byteLength := vector.bytesLength()
|
||||
if byteLength < len(vector.data) {
|
||||
vector.data = vector.data[:byteLength]
|
||||
}
|
||||
}
|
||||
46
signatures/schnorr/mina/challenge_derive.go
Normal file
46
signatures/schnorr/mina/challenge_derive.go
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
type MinaTSchnorrHandler struct{}
|
||||
|
||||
func (m MinaTSchnorrHandler) DeriveChallenge(
|
||||
msg []byte,
|
||||
pubKey curves.Point,
|
||||
r curves.Point,
|
||||
) (curves.Scalar, error) {
|
||||
txn := new(Transaction)
|
||||
err := txn.UnmarshalBinary(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input := new(roinput).Init(3, 75)
|
||||
txn.addRoInput(input)
|
||||
|
||||
pt, ok := pubKey.(*curves.PointPallas)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
R, ok := r.(*curves.PointPallas)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
|
||||
pk := new(PublicKey)
|
||||
pk.value = pt.GetEp()
|
||||
|
||||
sc := msgHash(pk, R.X(), input, ThreeW, MainNet)
|
||||
s := new(curves.ScalarPallas)
|
||||
s.SetFq(sc)
|
||||
return s, nil
|
||||
}
|
||||
282
signatures/schnorr/mina/keys.go
Normal file
282
signatures/schnorr/mina/keys.go
Normal file
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
const (
|
||||
version = 0xcb
|
||||
nonZeroCurvePointVersion = 0x01
|
||||
isCompressed = 0x01
|
||||
)
|
||||
|
||||
// PublicKey is the verification key
|
||||
type PublicKey struct {
|
||||
value *curves.Ep
|
||||
}
|
||||
|
||||
// GenerateAddress converts the public key to an address
|
||||
func (pk PublicKey) GenerateAddress() string {
|
||||
var payload [40]byte
|
||||
payload[0] = version
|
||||
payload[1] = nonZeroCurvePointVersion
|
||||
payload[2] = isCompressed
|
||||
|
||||
buffer := pk.value.ToAffineUncompressed()
|
||||
copy(payload[3:35], buffer[:32])
|
||||
payload[35] = buffer[32] & 1
|
||||
hash1 := sha256.Sum256(payload[:36])
|
||||
hash2 := sha256.Sum256(hash1[:])
|
||||
copy(payload[36:40], hash2[:4])
|
||||
return base58.Encode(payload[:])
|
||||
}
|
||||
|
||||
// ParseAddress converts a given string into a public key returning an error on failure
|
||||
func (pk *PublicKey) ParseAddress(b58 string) error {
|
||||
buffer, err := base58.Decode(b58)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buffer) != 40 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
if buffer[0] != version {
|
||||
return fmt.Errorf("invalid version")
|
||||
}
|
||||
if buffer[1] != nonZeroCurvePointVersion {
|
||||
return fmt.Errorf("invalid non-zero curve point version")
|
||||
}
|
||||
if buffer[2] != isCompressed {
|
||||
return fmt.Errorf("invalid compressed flag")
|
||||
}
|
||||
hash1 := sha256.Sum256(buffer[:36])
|
||||
hash2 := sha256.Sum256(hash1[:])
|
||||
if subtle.ConstantTimeCompare(hash2[:4], buffer[36:40]) != 1 {
|
||||
return fmt.Errorf("invalid checksum")
|
||||
}
|
||||
x := buffer[3:35]
|
||||
x[31] |= buffer[35] << 7
|
||||
value, err := new(curves.Ep).FromAffineCompressed(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pk PublicKey) MarshalBinary() ([]byte, error) {
|
||||
return pk.value.ToAffineCompressed(), nil
|
||||
}
|
||||
|
||||
func (pk *PublicKey) UnmarshalBinary(input []byte) error {
|
||||
pt, err := new(curves.Ep).FromAffineCompressed(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = pt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pk *PublicKey) SetPointPallas(pallas *curves.PointPallas) {
|
||||
pk.value = pallas.GetEp()
|
||||
}
|
||||
|
||||
// SecretKey is the signing key
|
||||
type SecretKey struct {
|
||||
value *fq.Fq
|
||||
}
|
||||
|
||||
// GetPublicKey returns the corresponding verification
|
||||
func (sk SecretKey) GetPublicKey() *PublicKey {
|
||||
pk := new(curves.Ep).Mul(new(curves.Ep).Generator(), sk.value)
|
||||
return &PublicKey{pk}
|
||||
}
|
||||
|
||||
func (sk SecretKey) MarshalBinary() ([]byte, error) {
|
||||
t := sk.value.Bytes()
|
||||
return t[:], nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) UnmarshalBinary(input []byte) error {
|
||||
if len(input) != 32 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
var buf [32]byte
|
||||
copy(buf[:], input)
|
||||
value, err := new(fq.Fq).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sk.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) SetFq(fq *fq.Fq) {
|
||||
sk.value = fq
|
||||
}
|
||||
|
||||
// NewKeys creates a new keypair using a CSPRNG
|
||||
func NewKeys() (*PublicKey, *SecretKey, error) {
|
||||
return NewKeysFromReader(crand.Reader)
|
||||
}
|
||||
|
||||
// NewKeysFromReader creates a new keypair using the specified reader
|
||||
func NewKeysFromReader(reader io.Reader) (*PublicKey, *SecretKey, error) {
|
||||
t := new(curves.ScalarPallas).Random(reader)
|
||||
sc, ok := t.(*curves.ScalarPallas)
|
||||
if !ok || t.IsZero() {
|
||||
return nil, nil, fmt.Errorf("invalid key")
|
||||
}
|
||||
sk := sc.GetFq()
|
||||
pk := new(curves.Ep).Mul(new(curves.Ep).Generator(), sk)
|
||||
if pk.IsIdentity() {
|
||||
return nil, nil, fmt.Errorf("invalid key")
|
||||
}
|
||||
|
||||
return &PublicKey{pk}, &SecretKey{sk}, nil
|
||||
}
|
||||
|
||||
// SignTransaction generates a signature over the specified txn and network id
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/crypto.c#L1020
|
||||
func (sk *SecretKey) SignTransaction(transaction *Transaction) (*Signature, error) {
|
||||
input := new(roinput).Init(3, 75)
|
||||
transaction.addRoInput(input)
|
||||
return sk.finishSchnorrSign(input, transaction.NetworkId)
|
||||
}
|
||||
|
||||
// SignMessage signs a _string_. this is somewhat non-standard; we do it by just adding bytes to the roinput.
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/crypto.c#L1020
|
||||
func (sk *SecretKey) SignMessage(message string) (*Signature, error) {
|
||||
input := new(roinput).Init(0, len(message))
|
||||
input.AddBytes([]byte(message))
|
||||
return sk.finishSchnorrSign(input, MainNet)
|
||||
}
|
||||
|
||||
func (sk *SecretKey) finishSchnorrSign(input *roinput, networkId NetworkType) (*Signature, error) {
|
||||
if sk.value.IsZero() {
|
||||
return nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
pk := sk.GetPublicKey()
|
||||
k := sk.msgDerive(input, pk, networkId)
|
||||
if k.IsZero() {
|
||||
return nil, fmt.Errorf("invalid nonce generated")
|
||||
}
|
||||
// r = k*G
|
||||
r := new(curves.Ep).Generator()
|
||||
r.Mul(r, k)
|
||||
|
||||
if r.Y().IsOdd() {
|
||||
k.Neg(k)
|
||||
}
|
||||
rx := r.X()
|
||||
e := msgHash(pk, rx, input, ThreeW, networkId)
|
||||
|
||||
// S = k + e*sk
|
||||
e.Mul(e, sk.value)
|
||||
s := new(fq.Fq).Add(k, e)
|
||||
if rx.IsZero() || s.IsZero() {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
return &Signature{
|
||||
R: rx,
|
||||
S: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyTransaction checks if the signature is over the given transaction using this public key
|
||||
func (pk *PublicKey) VerifyTransaction(sig *Signature, transaction *Transaction) error {
|
||||
input := new(roinput).Init(3, 75)
|
||||
transaction.addRoInput(input)
|
||||
return pk.finishSchnorrVerify(sig, input, transaction.NetworkId)
|
||||
}
|
||||
|
||||
// VerifyMessage checks if the claimed signature on a _string_ is valid. this is nonstandard; see above.
|
||||
func (pk *PublicKey) VerifyMessage(sig *Signature, message string) error {
|
||||
input := new(roinput).Init(0, len(message))
|
||||
input.AddBytes([]byte(message))
|
||||
return pk.finishSchnorrVerify(sig, input, MainNet)
|
||||
}
|
||||
|
||||
func (pk *PublicKey) finishSchnorrVerify(
|
||||
sig *Signature,
|
||||
input *roinput,
|
||||
networkId NetworkType,
|
||||
) error {
|
||||
if pk.value.IsIdentity() {
|
||||
return fmt.Errorf("invalid public key")
|
||||
}
|
||||
if sig.R.IsZero() || sig.S.IsZero() {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
e := msgHash(pk, sig.R, input, ThreeW, networkId)
|
||||
sg := new(curves.Ep).Generator()
|
||||
sg.Mul(sg, sig.S)
|
||||
|
||||
epk := new(curves.Ep).Mul(pk.value, e)
|
||||
epk.Neg(epk)
|
||||
|
||||
r := new(curves.Ep).Add(sg, epk)
|
||||
if !r.Y().IsOdd() && r.X().Equal(sig.R) {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("signature verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func msgHash(
|
||||
pk *PublicKey,
|
||||
rx *fp.Fp,
|
||||
input *roinput,
|
||||
hashType Permutation,
|
||||
networkId NetworkType,
|
||||
) *fq.Fq {
|
||||
input.AddFp(pk.value.X())
|
||||
input.AddFp(pk.value.Y())
|
||||
input.AddFp(rx)
|
||||
|
||||
ctx := new(Context).Init(hashType, networkId)
|
||||
fields := input.Fields()
|
||||
ctx.Update(fields)
|
||||
return ctx.Digest()
|
||||
}
|
||||
|
||||
func (sk SecretKey) msgDerive(msg *roinput, pk *PublicKey, networkId NetworkType) *fq.Fq {
|
||||
input := msg.Clone()
|
||||
input.AddFp(pk.value.X())
|
||||
input.AddFp(pk.value.Y())
|
||||
input.AddFq(sk.value)
|
||||
input.AddBytes([]byte{byte(networkId)})
|
||||
inputBytes := input.Bytes()
|
||||
|
||||
h, _ := blake2b.New(32, []byte{})
|
||||
_, _ = h.Write(inputBytes)
|
||||
hash := h.Sum(nil)
|
||||
|
||||
// Clear top two bits
|
||||
hash[31] &= 0x3F
|
||||
tmp := [4]uint64{
|
||||
binary.LittleEndian.Uint64(hash[:8]),
|
||||
binary.LittleEndian.Uint64(hash[8:16]),
|
||||
binary.LittleEndian.Uint64(hash[16:24]),
|
||||
binary.LittleEndian.Uint64(hash[24:32]),
|
||||
}
|
||||
return new(fq.Fq).SetRaw(&tmp)
|
||||
}
|
||||
132
signatures/schnorr/mina/keys_test.go
Normal file
132
signatures/schnorr/mina/keys_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
func TestNewKeys(t *testing.T) {
|
||||
pk, sk, err := NewKeys()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
}
|
||||
|
||||
func TestSecretKeySignTransaction(t *testing.T) {
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L15
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
/*
|
||||
This illustrates constructing and signing the following transaction.
|
||||
amounts are in nanocodas.
|
||||
{
|
||||
"common": {
|
||||
"fee": "3",
|
||||
"fee_token": "1",
|
||||
"fee_payer_pk": "B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg",
|
||||
"nonce": "200",
|
||||
"valid_until": "10000",
|
||||
"memo": "E4Yq8cQXC1m9eCYL8mYtmfqfJ5cVdhZawrPQ6ahoAay1NDYfTi44K"
|
||||
},
|
||||
"body": [
|
||||
"Payment",
|
||||
{
|
||||
"source_pk": "B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg",
|
||||
"receiver_pk": "B62qrcFstkpqXww1EkSGrqMCwCNho86kuqBd4FrAAUsPxNKdiPzAUsy",
|
||||
"token_id": "1",
|
||||
"amount": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
feePayerPk := new(PublicKey)
|
||||
err := feePayerPk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
sourcePk := new(PublicKey)
|
||||
err = sourcePk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress("B62qrcFstkpqXww1EkSGrqMCwCNho86kuqBd4FrAAUsPxNKdiPzAUsy")
|
||||
require.NoError(t, err)
|
||||
txn := &Transaction{
|
||||
Fee: 3,
|
||||
FeeToken: 1,
|
||||
Nonce: 200,
|
||||
ValidUntil: 10000,
|
||||
Memo: "this is a memo",
|
||||
FeePayerPk: feePayerPk,
|
||||
SourcePk: sourcePk,
|
||||
ReceiverPk: receiverPk,
|
||||
TokenId: 1,
|
||||
Amount: 42,
|
||||
Locked: false,
|
||||
Tag: [3]bool{false, false, false},
|
||||
NetworkId: MainNet,
|
||||
}
|
||||
sig, err := sk.SignTransaction(txn)
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyTransaction(sig, txn))
|
||||
}
|
||||
|
||||
func TestSecretKeySignMessage(t *testing.T) {
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L15
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
sig, err := sk.SignMessage("A test message.")
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyMessage(sig, "A test message."))
|
||||
}
|
||||
|
||||
func TestSecretKeySignTransactionStaking(t *testing.T) {
|
||||
// https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L128
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
|
||||
feePayerPk := new(PublicKey)
|
||||
err := feePayerPk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
sourcePk := new(PublicKey)
|
||||
err = sourcePk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress("B62qkfHpLpELqpMK6ZvUTJ5wRqKDRF3UHyJ4Kv3FU79Sgs4qpBnx5RR")
|
||||
require.NoError(t, err)
|
||||
txn := &Transaction{
|
||||
Fee: 3,
|
||||
FeeToken: 1,
|
||||
Nonce: 10,
|
||||
ValidUntil: 4000,
|
||||
Memo: "more delegates more fun",
|
||||
FeePayerPk: feePayerPk,
|
||||
SourcePk: sourcePk,
|
||||
ReceiverPk: receiverPk,
|
||||
TokenId: 1,
|
||||
Amount: 0,
|
||||
Locked: false,
|
||||
Tag: [3]bool{false, false, true},
|
||||
NetworkId: MainNet,
|
||||
}
|
||||
sig, err := sk.SignTransaction(txn)
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyTransaction(sig, txn))
|
||||
}
|
||||
112
signatures/schnorr/mina/poseidon_config.go
Normal file
112
signatures/schnorr/mina/poseidon_config.go
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
)
|
||||
|
||||
// SBox is the type of exponentiation to perform
|
||||
type SBox int
|
||||
|
||||
const (
|
||||
Cube = iota // x^3
|
||||
Quint // x^5
|
||||
Sept // x^7
|
||||
Inverse // x^-1
|
||||
)
|
||||
|
||||
// Exp mutates f by computing x^3, x^5, x^7 or x^-1 as described in
|
||||
// https://eprint.iacr.org/2019/458.pdf page 8
|
||||
func (sbox SBox) Exp(f *fp.Fp) {
|
||||
switch sbox {
|
||||
case Cube:
|
||||
t := new(fp.Fp).Square(f)
|
||||
f.Mul(t, f)
|
||||
case Quint:
|
||||
t := new(fp.Fp).Square(f)
|
||||
t.Square(t)
|
||||
f.Mul(t, f)
|
||||
case Sept:
|
||||
f2 := new(fp.Fp).Square(f)
|
||||
f4 := new(fp.Fp).Square(f2)
|
||||
t := new(fp.Fp).Mul(f2, f4)
|
||||
f.Mul(t, f)
|
||||
case Inverse:
|
||||
f.Invert(f)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Permutation is the permute function to use
|
||||
type Permutation int
|
||||
|
||||
const (
|
||||
ThreeW = iota
|
||||
FiveW
|
||||
Three
|
||||
)
|
||||
|
||||
// Permute executes the poseidon hash function
|
||||
func (p Permutation) Permute(ctx *Context) {
|
||||
switch p {
|
||||
case ThreeW:
|
||||
for r := 0; r < ctx.fullRounds; r++ {
|
||||
ark(ctx, r)
|
||||
sbox(ctx)
|
||||
mds(ctx)
|
||||
}
|
||||
ark(ctx, ctx.fullRounds)
|
||||
case Three:
|
||||
fallthrough
|
||||
case FiveW:
|
||||
// Full rounds only
|
||||
for r := 0; r < ctx.fullRounds; r++ {
|
||||
sbox(ctx)
|
||||
mds(ctx)
|
||||
ark(ctx, r)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func ark(ctx *Context, round int) {
|
||||
for i := 0; i < ctx.spongeWidth; i++ {
|
||||
ctx.state[i].Add(ctx.state[i], ctx.roundKeys[round][i])
|
||||
}
|
||||
}
|
||||
|
||||
func sbox(ctx *Context) {
|
||||
for i := 0; i < ctx.spongeWidth; i++ {
|
||||
ctx.sBox.Exp(ctx.state[i])
|
||||
}
|
||||
}
|
||||
|
||||
func mds(ctx *Context) {
|
||||
state2 := make([]*fp.Fp, len(ctx.state))
|
||||
for i := range ctx.state {
|
||||
state2[i] = new(fp.Fp).SetZero()
|
||||
}
|
||||
for row := 0; row < ctx.spongeWidth; row++ {
|
||||
for col := 0; col < ctx.spongeWidth; col++ {
|
||||
t := new(fp.Fp).Mul(ctx.state[col], ctx.mdsMatrix[row][col])
|
||||
state2[row].Add(state2[row], t)
|
||||
}
|
||||
}
|
||||
for i, f := range state2 {
|
||||
ctx.state[i].Set(f)
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkType is which Mina network id to use
|
||||
type NetworkType int
|
||||
|
||||
const (
|
||||
TestNet = iota
|
||||
MainNet
|
||||
NullNet
|
||||
)
|
||||
1182
signatures/schnorr/mina/poseidon_hash.go
Normal file
1182
signatures/schnorr/mina/poseidon_hash.go
Normal file
File diff suppressed because it is too large
Load Diff
122
signatures/schnorr/mina/poseidon_hash_test.go
Normal file
122
signatures/schnorr/mina/poseidon_hash_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
func TestPoseidonHash(t *testing.T) {
|
||||
// Reference https://github.com/o1-labs/proof-systems/blob/master/oracle/tests/test_vectors/3w.json
|
||||
testVectors := []struct {
|
||||
input []*fp.Fp
|
||||
output *fq.Fq
|
||||
}{
|
||||
{
|
||||
input: []*fp.Fp{},
|
||||
output: hexToFq("1b3251b6912d82edc78bbb0a5c88f0c6fde1781bc3e654123fa6862a4c63e617"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("df698e389c6f1987ffe186d806f8163738f5bf22e8be02572cce99dc6a4ab030"),
|
||||
},
|
||||
output: hexToFq("f9b1b6c5f8c98017c6b35ac74bc689b6533d6dbbee1fd868831b637a43ea720c"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("56b648a5a85619814900a6b40375676803fe16fb1ad2d1fb79115eb1b52ac026"),
|
||||
hexToFp("f26a8a03d9c9bbd9c6b2a1324d2a3f4d894bafe25a7e4ad1a498705f4026ff2f"),
|
||||
},
|
||||
output: hexToFq("7a556e93bcfbd27b55867f533cd1df293a7def60dd929a086fdd4e70393b0918"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("075c41fa23e4690694df5ded43624fd60ab7ee6ec6dd48f44dc71bc206cecb26"),
|
||||
hexToFp("a4e2beebb09bd02ad42bbccc11051e8262b6ef50445d8382b253e91ab1557a0d"),
|
||||
hexToFp("7dfc23a1242d9c0d6eb16e924cfba342bb2fccf36b8cbaf296851f2e6c469639"),
|
||||
},
|
||||
output: hexToFq("f94b39a919aab06f43f4a4b5a3e965b719a4dbd2b9cd26d2bba4197b10286b35"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("a1a659b14e80d47318c6fcdbbd388de4272d5c2815eb458cf4f196d52403b639"),
|
||||
hexToFp("5e33065d1801131b64d13038ff9693a7ef6283f24ec8c19438d112ff59d50f04"),
|
||||
hexToFp("38a8f4d0a9b6d0facdc4e825f6a2ba2b85401d5de119bf9f2bcb908235683e06"),
|
||||
hexToFp("3456d0313a30d7ccb23bd71ed6aa70ab234dad683d8187b677aef73f42f4f52e"),
|
||||
},
|
||||
output: hexToFq("cc1ccfa964fd6ef9ff1994beb53cfce9ebe1212847ce30e4c64f0777875aec34"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("bccfee48dc76bb991c97bd531cf489f4ee37a66a15f5cfac31bdd4f159d4a905"),
|
||||
hexToFp("2d106fb21a262f85fd400a995c6d74bad48d8adab2554046871c215e585b072b"),
|
||||
hexToFp("8300e93ee8587956534d0756bb2aa575e5878c670cff5c8e3e55c62632333c06"),
|
||||
hexToFp("879c32da31566f6d16afdefff94cba5260fec1057e97f19fc9a61dc2c54a6417"),
|
||||
hexToFp("9c0aa6e5501cfb2d08aeaea5b3cddac2c9bee85d13324118b44bafb63a59611e"),
|
||||
},
|
||||
output: hexToFq("cf7b9c2128f0e2c0fed4e1eca8d5954b629640c2458d24ba238c1bd3ccbc8e12"),
|
||||
},
|
||||
}
|
||||
for _, tv := range testVectors {
|
||||
ctx := new(Context).Init(ThreeW, NetworkType(NullNet))
|
||||
ctx.Update(tv.input)
|
||||
res := ctx.Digest()
|
||||
require.True(t, res.Equal(tv.output))
|
||||
}
|
||||
testVectors = []struct {
|
||||
input []*fp.Fp
|
||||
output *fq.Fq
|
||||
}{
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("f34b505e1a05ecfb327d8d664ff6272ddf5cc1f69618bb6a4407e9533067e703"),
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("ac7cb9c568955737eca56f855954f394cc6b05ac9b698ba3d974f029177cb427"),
|
||||
hexToFp("010141eca06991fe68dcbd799d93037522dc6c4dead1d77202e8c2ea8f5b1005"),
|
||||
hexToFp("0300000000000000010000000000000090010000204e0000021ce8d0d2e64012"),
|
||||
hexToFp("9b030903692b6b7b030000000000000000000000000000000000800100000000"),
|
||||
hexToFp("000000a800000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
output: &fq.Fq{
|
||||
1348483115953159504,
|
||||
14115862092770957043,
|
||||
15858311826851986539,
|
||||
1644043871107534594,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tv := range testVectors {
|
||||
ctx := new(Context).Init(ThreeW, NetworkType(MainNet))
|
||||
ctx.Update(tv.input)
|
||||
res := ctx.Digest()
|
||||
require.True(t, res.Equal(tv.output))
|
||||
}
|
||||
}
|
||||
|
||||
func hexToFp(s string) *fp.Fp {
|
||||
var buffer [32]byte
|
||||
input, _ := hex.DecodeString(s)
|
||||
copy(buffer[:], input)
|
||||
f, _ := new(fp.Fp).SetBytes(&buffer)
|
||||
return f
|
||||
}
|
||||
|
||||
func hexToFq(s string) *fq.Fq {
|
||||
var buffer [32]byte
|
||||
input, _ := hex.DecodeString(s)
|
||||
copy(buffer[:], input)
|
||||
f, _ := new(fq.Fq).SetBytes(&buffer)
|
||||
return f
|
||||
}
|
||||
136
signatures/schnorr/mina/roinput.go
Normal file
136
signatures/schnorr/mina/roinput.go
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
// Handles the packing of bits and fields according to Mina spec
|
||||
type roinput struct {
|
||||
fields []*fp.Fp
|
||||
bits *BitVector
|
||||
}
|
||||
|
||||
var conv = map[bool]int{
|
||||
true: 1,
|
||||
false: 0,
|
||||
}
|
||||
|
||||
func (r *roinput) Init(fields int, bytes int) *roinput {
|
||||
r.fields = make([]*fp.Fp, 0, fields)
|
||||
r.bits = NewBitVector(make([]byte, bytes), 0)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *roinput) Clone() *roinput {
|
||||
t := new(roinput)
|
||||
t.fields = make([]*fp.Fp, len(r.fields))
|
||||
for i, f := range r.fields {
|
||||
t.fields[i] = new(fp.Fp).Set(f)
|
||||
}
|
||||
buffer := r.bits.Bytes()
|
||||
data := make([]byte, len(buffer))
|
||||
copy(data, buffer)
|
||||
t.bits = NewBitVector(data, r.bits.Length())
|
||||
return t
|
||||
}
|
||||
|
||||
func (r *roinput) AddFp(fp *fp.Fp) {
|
||||
r.fields = append(r.fields, fp)
|
||||
}
|
||||
|
||||
func (r *roinput) AddFq(fq *fq.Fq) {
|
||||
scalar := fq.ToRaw()
|
||||
// Mina handles fields as 255 bit numbers
|
||||
// with each field we lose a bit
|
||||
for i := 0; i < 255; i++ {
|
||||
limb := i / 64
|
||||
idx := i % 64
|
||||
b := (scalar[limb] >> idx) & 1
|
||||
r.bits.Append(byte(b))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddBit(b bool) {
|
||||
r.bits.Append(byte(conv[b]))
|
||||
}
|
||||
|
||||
func (r *roinput) AddBytes(input []byte) {
|
||||
for _, b := range input {
|
||||
for i := 0; i < 8; i++ {
|
||||
r.bits.Append(byte((b >> i) & 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddUint32(x uint32) {
|
||||
for i := 0; i < 32; i++ {
|
||||
r.bits.Append(byte((x >> i) & 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddUint64(x uint64) {
|
||||
for i := 0; i < 64; i++ {
|
||||
r.bits.Append(byte((x >> i) & 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (r roinput) Bytes() []byte {
|
||||
out := make([]byte, (r.bits.Length()+7)/8+32*len(r.fields))
|
||||
res := NewBitVector(out, 0)
|
||||
// Mina handles fields as 255 bit numbers
|
||||
// with each field we lose a bit
|
||||
for _, f := range r.fields {
|
||||
buf := f.ToRaw()
|
||||
for i := 0; i < 255; i++ {
|
||||
limb := i / 64
|
||||
idx := i % 64
|
||||
b := (buf[limb] >> idx) & 1
|
||||
res.Append(byte(b))
|
||||
}
|
||||
}
|
||||
for i := 0; i < r.bits.Length(); i++ {
|
||||
res.Append(r.bits.Element(i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r roinput) Fields() []*fp.Fp {
|
||||
fields := make([]*fp.Fp, 0, len(r.fields)+r.bits.Length()/256)
|
||||
for _, f := range r.fields {
|
||||
fields = append(fields, new(fp.Fp).Set(f))
|
||||
}
|
||||
const maxChunkSize = 254
|
||||
bitsConsumed := 0
|
||||
bitIdx := 0
|
||||
|
||||
for bitsConsumed < r.bits.Length() {
|
||||
var chunk [4]uint64
|
||||
|
||||
remaining := r.bits.Length() - bitsConsumed
|
||||
var chunkSizeInBits int
|
||||
if remaining > maxChunkSize {
|
||||
chunkSizeInBits = maxChunkSize
|
||||
} else {
|
||||
chunkSizeInBits = remaining
|
||||
}
|
||||
|
||||
for i := 0; i < chunkSizeInBits; i++ {
|
||||
limb := i >> 6
|
||||
idx := i & 0x3F
|
||||
b := r.bits.Element(bitIdx)
|
||||
chunk[limb] |= uint64(b) << idx
|
||||
bitIdx++
|
||||
}
|
||||
fields = append(fields, new(fp.Fp).SetRaw(&chunk))
|
||||
bitsConsumed += chunkSizeInBits
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
49
signatures/schnorr/mina/signature.go
Normal file
49
signatures/schnorr/mina/signature.go
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
// Signature is a Mina compatible signature either for payment or delegation
|
||||
type Signature struct {
|
||||
R *fp.Fp
|
||||
S *fq.Fq
|
||||
}
|
||||
|
||||
func (sig Signature) MarshalBinary() ([]byte, error) {
|
||||
var buf [64]byte
|
||||
rx := sig.R.Bytes()
|
||||
s := sig.S.Bytes()
|
||||
copy(buf[:32], rx[:])
|
||||
copy(buf[32:], s[:])
|
||||
return buf[:], nil
|
||||
}
|
||||
|
||||
func (sig *Signature) UnmarshalBinary(input []byte) error {
|
||||
if len(input) != 64 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
var buf [32]byte
|
||||
copy(buf[:], input[:32])
|
||||
rx, err := new(fp.Fp).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(buf[:], input[32:])
|
||||
s, err := new(fq.Fq).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig.R = rx
|
||||
sig.S = s
|
||||
return nil
|
||||
}
|
||||
224
signatures/schnorr/mina/txn.go
Normal file
224
signatures/schnorr/mina/txn.go
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/btcutil/base58"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Transaction is a Mina transaction for payments or delegations
|
||||
type Transaction struct {
|
||||
Fee, FeeToken uint64
|
||||
FeePayerPk *PublicKey
|
||||
Nonce, ValidUntil uint32
|
||||
Memo string
|
||||
Tag [3]bool
|
||||
SourcePk, ReceiverPk *PublicKey
|
||||
TokenId, Amount uint64
|
||||
Locked bool
|
||||
NetworkId NetworkType
|
||||
}
|
||||
|
||||
type txnJson struct {
|
||||
Common txnCommonJson
|
||||
Body [2]any
|
||||
}
|
||||
|
||||
type txnCommonJson struct {
|
||||
Fee uint64 `json:"fee"`
|
||||
FeeToken uint64 `json:"fee_token"`
|
||||
FeePayerPk string `json:"fee_payer_pk"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
ValidUntil uint32 `json:"valid_until"`
|
||||
Memo string `json:"memo"`
|
||||
NetworkId uint8 `json:"network_id"`
|
||||
}
|
||||
|
||||
type txnBodyPaymentJson struct {
|
||||
SourcePk string `json:"source_pk"`
|
||||
ReceiverPk string `json:"receiver_pk"`
|
||||
TokenId uint64 `json:"token_id"`
|
||||
Amount uint64 `json:"amount"`
|
||||
}
|
||||
|
||||
type txnBodyDelegationJson struct {
|
||||
Delegator string `json:"delegator"`
|
||||
NewDelegate string `json:"new_delegate"`
|
||||
}
|
||||
|
||||
func (txn *Transaction) MarshalBinary() ([]byte, error) {
|
||||
mapper := map[bool]byte{
|
||||
true: 1,
|
||||
false: 0,
|
||||
}
|
||||
out := make([]byte, 175)
|
||||
binary.LittleEndian.PutUint64(out, txn.Fee)
|
||||
binary.LittleEndian.PutUint64(out[8:16], txn.FeeToken)
|
||||
copy(out[16:48], txn.FeePayerPk.value.ToAffineCompressed())
|
||||
binary.LittleEndian.PutUint32(out[48:52], txn.Nonce)
|
||||
binary.LittleEndian.PutUint32(out[52:56], txn.ValidUntil)
|
||||
|
||||
out[56] = 0x01
|
||||
out[57] = byte(len(txn.Memo))
|
||||
copy(out[58:90], txn.Memo[:])
|
||||
out[90] = mapper[txn.Tag[0]]
|
||||
out[91] = mapper[txn.Tag[1]]
|
||||
out[92] = mapper[txn.Tag[2]]
|
||||
copy(out[93:125], txn.SourcePk.value.ToAffineCompressed())
|
||||
copy(out[125:157], txn.ReceiverPk.value.ToAffineCompressed())
|
||||
binary.LittleEndian.PutUint64(out[157:165], txn.TokenId)
|
||||
binary.LittleEndian.PutUint64(out[165:173], txn.Amount)
|
||||
out[173] = mapper[txn.Locked]
|
||||
out[174] = byte(txn.NetworkId)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (txn *Transaction) UnmarshalBinary(input []byte) error {
|
||||
mapper := map[byte]bool{
|
||||
1: true,
|
||||
0: false,
|
||||
}
|
||||
if len(input) < 175 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
sourcePk := new(PublicKey)
|
||||
receiverPk := new(PublicKey)
|
||||
err := feePayerPk.UnmarshalBinary(input[16:48])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sourcePk.UnmarshalBinary(input[93:125])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = receiverPk.UnmarshalBinary(input[125:157])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txn.Fee = binary.LittleEndian.Uint64(input[:8])
|
||||
txn.FeeToken = binary.LittleEndian.Uint64(input[8:16])
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.Nonce = binary.LittleEndian.Uint32(input[48:52])
|
||||
txn.ValidUntil = binary.LittleEndian.Uint32(input[52:56])
|
||||
txn.Memo = string(input[58 : 58+input[57]])
|
||||
txn.Tag[0] = mapper[input[90]]
|
||||
txn.Tag[1] = mapper[input[91]]
|
||||
txn.Tag[2] = mapper[input[92]]
|
||||
txn.SourcePk = sourcePk
|
||||
txn.ReceiverPk = receiverPk
|
||||
txn.TokenId = binary.LittleEndian.Uint64(input[157:165])
|
||||
txn.Amount = binary.LittleEndian.Uint64(input[165:173])
|
||||
txn.Locked = mapper[input[173]]
|
||||
txn.NetworkId = NetworkType(input[174])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txn *Transaction) UnmarshalJSON(input []byte) error {
|
||||
var t txnJson
|
||||
err := json.Unmarshal(input, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strType, ok := t.Body[0].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
memo, _, err := base58.CheckDecode(t.Common.Memo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch strType {
|
||||
case "Payment":
|
||||
b, ok := t.Body[1].(txnBodyPaymentJson)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
err = feePayerPk.ParseAddress(b.SourcePk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress(b.ReceiverPk)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.ReceiverPk = receiverPk
|
||||
case "Stake_delegation":
|
||||
bType, ok := t.Body[1].([2]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
delegateType, ok := bType[0].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
if delegateType == "Set_delegate" {
|
||||
b, ok := bType[1].(txnBodyDelegationJson)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
err = feePayerPk.ParseAddress(b.Delegator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress(b.NewDelegate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.ReceiverPk = receiverPk
|
||||
} else {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
txn.Memo = string(memo[2 : 2+memo[1]])
|
||||
sourcePk := new(PublicKey)
|
||||
sourcePk.value = new(curves.Ep).Set(txn.FeePayerPk.value)
|
||||
txn.Fee = t.Common.Fee
|
||||
txn.FeeToken = t.Common.FeeToken
|
||||
txn.Nonce = t.Common.Nonce
|
||||
txn.ValidUntil = t.Common.ValidUntil
|
||||
txn.NetworkId = NetworkType(t.Common.NetworkId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txn Transaction) addRoInput(input *roinput) {
|
||||
input.AddFp(txn.FeePayerPk.value.X())
|
||||
input.AddFp(txn.SourcePk.value.X())
|
||||
input.AddFp(txn.ReceiverPk.value.X())
|
||||
|
||||
input.AddUint64(txn.Fee)
|
||||
input.AddUint64(txn.FeeToken)
|
||||
input.AddBit(txn.FeePayerPk.value.Y().IsOdd())
|
||||
input.AddUint32(txn.Nonce)
|
||||
input.AddUint32(txn.ValidUntil)
|
||||
memo := [34]byte{0x01, byte(len(txn.Memo))}
|
||||
copy(memo[2:], txn.Memo)
|
||||
input.AddBytes(memo[:])
|
||||
for _, b := range txn.Tag {
|
||||
input.AddBit(b)
|
||||
}
|
||||
|
||||
input.AddBit(txn.SourcePk.value.Y().IsOdd())
|
||||
input.AddBit(txn.ReceiverPk.value.Y().IsOdd())
|
||||
input.AddUint64(txn.TokenId)
|
||||
input.AddUint64(txn.Amount)
|
||||
input.AddBit(txn.Locked)
|
||||
}
|
||||
323
signatures/schnorr/nem/ed25519_keccak.go
Normal file
323
signatures/schnorr/nem/ed25519_keccak.go
Normal file
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// This file implements the Ed25519 signature algorithm. See
|
||||
// https://ed25519.cr.yp.to/.
|
||||
//
|
||||
// These functions are also compatible with the “Ed25519” function defined in
|
||||
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
|
||||
// representation includes a public key suffix to make multiple signing
|
||||
// operations with the same key more efficient. This package refers to the RFC
|
||||
// 8032 private key as the “seed”.
|
||||
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||
// from SUPERCOP.
|
||||
|
||||
package nem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
cryptorand "crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"filippo.io/edwards25519"
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = 32
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = 64
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = 64
|
||||
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
|
||||
SeedSize = 32
|
||||
)
|
||||
|
||||
// PublicKey is the type of Ed25519 public keys.
|
||||
type PublicKey []byte
|
||||
|
||||
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||
type PrivateKey []byte
|
||||
|
||||
// Bytes returns the publicKey in byte array
|
||||
func (p PublicKey) Bytes() []byte {
|
||||
return p
|
||||
}
|
||||
|
||||
// Public returns the PublicKey corresponding to priv.
|
||||
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||
publicKey := make([]byte, PublicKeySize)
|
||||
copy(publicKey, priv[32:])
|
||||
return PublicKey(publicKey)
|
||||
}
|
||||
|
||||
func Keccak512(data []byte) ([]byte, error) {
|
||||
k512 := sha3.NewLegacyKeccak512()
|
||||
_, err := k512.Write(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k512.Sum(nil), nil
|
||||
}
|
||||
|
||||
// Seed returns the private key seed corresponding to priv. It is provided for
|
||||
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
|
||||
// in this package.
|
||||
func (priv PrivateKey) Seed() []byte {
|
||||
seed := make([]byte, SeedSize)
|
||||
copy(seed, priv[:32])
|
||||
return seed
|
||||
}
|
||||
|
||||
// Sign signs the given message with priv.
|
||||
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||
// crypto.Hash(0) as the value for opts.
|
||||
func (priv PrivateKey) Sign(
|
||||
rand io.Reader,
|
||||
message []byte,
|
||||
opts crypto.SignerOpts,
|
||||
) (signature []byte, err error) {
|
||||
if opts.HashFunc() != crypto.Hash(0) {
|
||||
return nil, fmt.Errorf("ed25519: cannot sign hashed message")
|
||||
}
|
||||
sig, err := Sign(priv, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||
// If rand is nil, crypto/rand.Reader will be used.
|
||||
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||
if rand == nil {
|
||||
rand = cryptorand.Reader
|
||||
}
|
||||
|
||||
seed := make([]byte, SeedSize)
|
||||
if _, err := io.ReadFull(rand, seed); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
privateKey, err := NewKeyFromSeed(seed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
publicKey := make([]byte, PublicKeySize)
|
||||
copy(publicKey, privateKey[32:])
|
||||
|
||||
return publicKey, privateKey, nil
|
||||
}
|
||||
|
||||
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||
// len(seed) is not SeedSize. This function is provided for interoperability
|
||||
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||
// package.
|
||||
func NewKeyFromSeed(seed []byte) (PrivateKey, error) {
|
||||
// Outline the function body so that the returned key can be stack-allocated.
|
||||
privateKey := make([]byte, PrivateKeySize)
|
||||
err := newKeyFromSeed(privateKey, seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func newKeyFromSeed(privateKey, seed []byte) error {
|
||||
if l := len(seed); l != SeedSize {
|
||||
return fmt.Errorf("ed25519: bad seed length: %d", l)
|
||||
}
|
||||
|
||||
// Weird required step to get compatibility with the NEM test vectors
|
||||
// Have to reverse the bytes from the given seed
|
||||
digest, err := Keccak512(internal.ReverseScalarBytes(seed))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc, err := edwards25519.NewScalar().SetBytesWithClamping(digest[:32])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
A := edwards25519.Point{}
|
||||
A.ScalarBaseMult(sc)
|
||||
publicKeyBytes := A.Bytes()
|
||||
|
||||
copy(privateKey, seed)
|
||||
copy(privateKey[32:], publicKeyBytes[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature. It will
|
||||
// panic if len(privateKey) is not PrivateKeySize.
|
||||
func Sign(privateKey PrivateKey, message []byte) ([]byte, error) {
|
||||
// Outline the function body so that the returned signature can be
|
||||
// stack-allocated.
|
||||
signature := make([]byte, SignatureSize)
|
||||
err := sign(signature, privateKey, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func sign(signature, privateKey, message []byte) error {
|
||||
if l := len(privateKey); l != PrivateKeySize {
|
||||
return fmt.Errorf("ed25519: bad private key length: %d", l)
|
||||
}
|
||||
|
||||
seed := privateKey[:32]
|
||||
digest, err := Keccak512(internal.ReverseScalarBytes(seed))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// H(seed) ie. privkey
|
||||
expandedSecretKey := digest[:32]
|
||||
sc, err := edwards25519.NewScalar().SetBytesWithClamping(expandedSecretKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// r = H(H(seed) + msg)
|
||||
hEngine := sha3.NewLegacyKeccak512()
|
||||
_, err = hEngine.Write(digest[32:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = hEngine.Write(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hOut1 [64]byte
|
||||
hEngine.Sum(hOut1[:0])
|
||||
|
||||
// hash output -> scalar
|
||||
// Take 64 byte output from keccak512 so need to set bytes as long
|
||||
r, err := edwards25519.NewScalar().SetUniformBytes(hOut1[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// R = r*G
|
||||
R := edwards25519.Point{}
|
||||
R.ScalarBaseMult(r)
|
||||
RBytes := R.Bytes()
|
||||
|
||||
// s = H(R + pubkey + msg)
|
||||
hEngine.Reset()
|
||||
_, err = hEngine.Write(RBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = hEngine.Write(privateKey[32:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = hEngine.Write(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hOut2 [64]byte
|
||||
hEngine.Sum(hOut2[:0])
|
||||
|
||||
// hash output -> scalar
|
||||
// Take 64 byte output from keccak512 so need to set bytes as long
|
||||
h, err := edwards25519.NewScalar().SetUniformBytes(hOut2[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// s = (r + h * privKey)
|
||||
s := edwards25519.NewScalar().MultiplyAdd(h, sc, r)
|
||||
|
||||
copy(signature[:], RBytes)
|
||||
copy(signature[32:], s.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||
// will panic if len(publicKey) is not PublicKeySize.
|
||||
// Previously publicKey is of type PublicKey
|
||||
func Verify(publicKey PublicKey, message, sig []byte) (bool, error) {
|
||||
if l := len(publicKey); l != PublicKeySize {
|
||||
return false, fmt.Errorf("ed25519: bad public key length: %d", l)
|
||||
}
|
||||
|
||||
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||
return false, fmt.Errorf("ed25519: bad signature size: %d", len(sig))
|
||||
}
|
||||
|
||||
RBytes := sig[:32]
|
||||
sBytes := sig[32:]
|
||||
|
||||
var publicKeyBytes [32]byte
|
||||
copy(publicKeyBytes[:], publicKey)
|
||||
|
||||
A := edwards25519.Point{}
|
||||
_, err := A.SetBytes(publicKeyBytes[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
negA := edwards25519.Point{}
|
||||
negA.Negate(&A)
|
||||
|
||||
// h = H(R + pubkey + msg)
|
||||
hEngine := sha3.NewLegacyKeccak512()
|
||||
_, err = hEngine.Write(RBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = hEngine.Write(publicKeyBytes[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = hEngine.Write(message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var hOut1 [64]byte
|
||||
hEngine.Sum(hOut1[:0])
|
||||
|
||||
// hash output -> scalar
|
||||
// Take 64 byte output from keccak512 so need to set bytes as long
|
||||
h, err := edwards25519.NewScalar().SetUniformBytes(hOut1[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// s was generated in sign so can set as canonical
|
||||
s, err := edwards25519.NewScalar().SetCanonicalBytes(sBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// R' = s*G - h*Pubkey = h*negPubkey + s*G
|
||||
RPrime := edwards25519.Point{}
|
||||
RPrime.VarTimeDoubleScalarBaseMult(h, &negA, s)
|
||||
RPrimeBytes := RPrime.Bytes()
|
||||
|
||||
// Check R == R'
|
||||
return bytes.Equal(RBytes, RPrimeBytes), nil
|
||||
}
|
||||
190
signatures/schnorr/nem/ed25519_keccak_test.go
Executable file
190
signatures/schnorr/nem/ed25519_keccak_test.go
Executable file
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package nem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
type KeyPair struct {
|
||||
Privkey string `json:"privateKey"`
|
||||
Pubkey string `json:"publicKey"`
|
||||
}
|
||||
|
||||
type TestSig struct {
|
||||
Privkey string `json:"privateKey"`
|
||||
Pubkey string `json:"publicKey"`
|
||||
Data string `json:"data"`
|
||||
Length int `json:"length"`
|
||||
Sig string `json:"signature"`
|
||||
}
|
||||
|
||||
// NOTE: NEM provides no test vectors for Keccak512, but has test vectors for Keccak256
|
||||
// We use Keccak256 and 512 in the exact same manner, so ensuring this test passes
|
||||
// gives decent confidence in our Keccak512 use as well
|
||||
func TestKeccak256SanityCheck(t *testing.T) {
|
||||
data := "A6151D4904E18EC288243028CEDA30556E6C42096AF7150D6A7232CA5DBA52BD2192E23DAA5FA2BEA3D4BD95EFA2389CD193FCD3376E70A5C097B32C1C62C80AF9D710211545F7CDDDF63747420281D64529477C61E721273CFD78F8890ABB4070E97BAA52AC8FF61C26D195FC54C077DEF7A3F6F79B36E046C1A83CE9674BA1983EC2FB58947DE616DD797D6499B0385D5E8A213DB9AD5078A8E0C940FF0CB6BF92357EA5609F778C3D1FB1E7E36C35DB873361E2BE5C125EA7148EFF4A035B0CCE880A41190B2E22924AD9D1B82433D9C023924F2311315F07B88BFD42850047BF3BE785C4CE11C09D7E02065D30F6324365F93C5E7E423A07D754EB314B5FE9DB4614275BE4BE26AF017ABDC9C338D01368226FE9AF1FB1F815E7317BDBB30A0F36DC69"
|
||||
toMatch := "4E9E79AB7434F6C7401FB3305D55052EE829B9E46D5D05D43B59FEFB32E9A619"
|
||||
toMatchBytes, err := hex.DecodeString(toMatch)
|
||||
require.NoError(t, err)
|
||||
dataBytes, err := hex.DecodeString(data)
|
||||
require.NoError(t, err)
|
||||
k256 := sha3.NewLegacyKeccak256()
|
||||
_, err = k256.Write(dataBytes)
|
||||
require.NoError(t, err)
|
||||
var hashed []byte
|
||||
hashed = k256.Sum(hashed)
|
||||
require.Equal(t, hashed, toMatchBytes)
|
||||
}
|
||||
|
||||
// Test that the pubkey can get derived correctly from privkey
|
||||
func TestPrivToPubkey(t *testing.T) {
|
||||
testVectors := GetPrivToPubkeyTestCases()
|
||||
|
||||
for _, pair := range testVectors {
|
||||
privkeyBytes, err := hex.DecodeString(pair.Privkey)
|
||||
require.NoError(t, err)
|
||||
pubkeyBytes, err := hex.DecodeString(pair.Pubkey)
|
||||
require.NoError(t, err)
|
||||
|
||||
privKeyCalced, err := NewKeyFromSeed(privkeyBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKeyCalced := privKeyCalced.Public().(PublicKey)
|
||||
|
||||
require.Equal(t, pubKeyCalced.Bytes(), pubkeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that we can:
|
||||
// Get pubkey from privkey
|
||||
// Obtain the correct signature
|
||||
// Verify the test vector provided signature
|
||||
func TestSigs(t *testing.T) {
|
||||
testVectors := GetSigTestCases()
|
||||
|
||||
for _, ts := range testVectors {
|
||||
// Test priv -> pubkey again
|
||||
privkeyBytes, err := hex.DecodeString(ts.Privkey)
|
||||
require.NoError(t, err)
|
||||
pubkeyBytes, err := hex.DecodeString(ts.Pubkey)
|
||||
require.NoError(t, err)
|
||||
|
||||
privKeyCalced, err := NewKeyFromSeed(privkeyBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKeyCalced := privKeyCalced.Public().(PublicKey)
|
||||
|
||||
require.True(t, bytes.Equal(pubKeyCalced.Bytes(), pubkeyBytes))
|
||||
|
||||
dataBytes, err := hex.DecodeString(ts.Data)
|
||||
require.NoError(t, err)
|
||||
sigBytes, err := hex.DecodeString(ts.Sig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test sign
|
||||
sigCalced, err := Sign(privKeyCalced, dataBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, bytes.Equal(sigCalced, sigBytes))
|
||||
|
||||
// Test verify
|
||||
verified, err := Verify(pubKeyCalced, dataBytes, sigBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, verified)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Test cases were obtained from NEM
|
||||
// See link: https://github.com/symbol/test-vectors
|
||||
// Pulled 5 test vectors for each test case, at time of writing confirmed that all 10000 vectors passed
|
||||
func GetPrivToPubkeyTestCases() []KeyPair {
|
||||
var toReturn []KeyPair
|
||||
|
||||
kp1 := KeyPair{
|
||||
Privkey: "575DBB3062267EFF57C970A336EBBC8FBCFE12C5BD3ED7BC11EB0481D7704CED",
|
||||
Pubkey: "C5F54BA980FCBB657DBAAA42700539B207873E134D2375EFEAB5F1AB52F87844",
|
||||
}
|
||||
|
||||
kp2 := KeyPair{
|
||||
Privkey: "5B0E3FA5D3B49A79022D7C1E121BA1CBBF4DB5821F47AB8C708EF88DEFC29BFE",
|
||||
Pubkey: "96EB2A145211B1B7AB5F0D4B14F8ABC8D695C7AEE31A3CFC2D4881313C68EEA3",
|
||||
}
|
||||
|
||||
kp3 := KeyPair{
|
||||
Privkey: "738BA9BB9110AEA8F15CAA353ACA5653B4BDFCA1DB9F34D0EFED2CE1325AEEDA",
|
||||
Pubkey: "2D8425E4CA2D8926346C7A7CA39826ACD881A8639E81BD68820409C6E30D142A",
|
||||
}
|
||||
|
||||
kp4 := KeyPair{
|
||||
Privkey: "E8BF9BC0F35C12D8C8BF94DD3A8B5B4034F1063948E3CC5304E55E31AA4B95A6",
|
||||
Pubkey: "4FEED486777ED38E44C489C7C4E93A830E4C4A907FA19A174E630EF0F6ED0409",
|
||||
}
|
||||
|
||||
kp5 := KeyPair{
|
||||
Privkey: "C325EA529674396DB5675939E7988883D59A5FC17A28CA977E3BA85370232A83",
|
||||
Pubkey: "83EE32E4E145024D29BCA54F71FA335A98B3E68283F1A3099C4D4AE113B53E54",
|
||||
}
|
||||
|
||||
toReturn = append(toReturn, kp1, kp2, kp3, kp4, kp5)
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func GetSigTestCases() []TestSig {
|
||||
var toReturn []TestSig
|
||||
|
||||
t1 := TestSig{
|
||||
Privkey: "ABF4CF55A2B3F742D7543D9CC17F50447B969E6E06F5EA9195D428AB12B7318D",
|
||||
Pubkey: "8A558C728C21C126181E5E654B404A45B4F0137CE88177435A69978CC6BEC1F4",
|
||||
Data: "8CE03CD60514233B86789729102EA09E867FC6D964DEA8C2018EF7D0A2E0E24BF7E348E917116690B9",
|
||||
Length: 41,
|
||||
Sig: "D9CEC0CC0E3465FAB229F8E1D6DB68AB9CC99A18CB0435F70DEB6100948576CD5C0AA1FEB550BDD8693EF81EB10A556A622DB1F9301986827B96716A7134230C",
|
||||
}
|
||||
|
||||
t2 := TestSig{
|
||||
Privkey: "6AA6DAD25D3ACB3385D5643293133936CDDDD7F7E11818771DB1FF2F9D3F9215",
|
||||
Pubkey: "BBC8CBB43DDA3ECF70A555981A351A064493F09658FFFE884C6FAB2A69C845C6",
|
||||
Data: "E4A92208A6FC52282B620699191EE6FB9CF04DAF48B48FD542C5E43DAA9897763A199AAA4B6F10546109F47AC3564FADE0",
|
||||
Length: 49,
|
||||
Sig: "98BCA58B075D1748F1C3A7AE18F9341BC18E90D1BEB8499E8A654C65D8A0B4FBD2E084661088D1E5069187A2811996AE31F59463668EF0F8CB0AC46A726E7902",
|
||||
}
|
||||
|
||||
t3 := TestSig{
|
||||
Privkey: "8E32BC030A4C53DE782EC75BA7D5E25E64A2A072A56E5170B77A4924EF3C32A9",
|
||||
Pubkey: "72D0E65F1EDE79C4AF0BA7EC14204E10F0F7EA09F2BC43259CD60EA8C3A087E2",
|
||||
Data: "13ED795344C4448A3B256F23665336645A853C5C44DBFF6DB1B9224B5303B6447FBF8240A2249C55",
|
||||
Length: 40,
|
||||
Sig: "EF257D6E73706BB04878875C58AA385385BF439F7040EA8297F7798A0EA30C1C5EFF5DDC05443F801849C68E98111AE65D088E726D1D9B7EECA2EB93B677860C",
|
||||
}
|
||||
|
||||
t4 := TestSig{
|
||||
Privkey: "C83CE30FCB5B81A51BA58FF827CCBC0142D61C13E2ED39E78E876605DA16D8D7",
|
||||
Pubkey: "3EC8923F9EA5EA14F8AAA7E7C2784653ED8C7DE44E352EF9FC1DEE81FC3FA1A3",
|
||||
Data: "A2704638434E9F7340F22D08019C4C8E3DBEE0DF8DD4454A1D70844DE11694F4C8CA67FDCB08FED0CEC9ABB2112B5E5F89",
|
||||
Length: 49,
|
||||
Sig: "0C684E71B35FED4D92B222FC60561DB34E0D8AFE44BDD958AAF4EE965911BEF5991236F3E1BCED59FC44030693BCAC37F34D29E5AE946669DC326E706E81B804",
|
||||
}
|
||||
|
||||
t5 := TestSig{
|
||||
Privkey: "2DA2A0AAE0F37235957B51D15843EDDE348A559692D8FA87B94848459899FC27",
|
||||
Pubkey: "D73D0B14A9754EEC825FCB25EF1CFA9AE3B1370074EDA53FC64C22334A26C254",
|
||||
Data: "D2488E854DBCDFDB2C9D16C8C0B2FDBC0ABB6BAC991BFE2B14D359A6BC99D66C00FD60D731AE06D0",
|
||||
Length: 40,
|
||||
Sig: "6F17F7B21EF9D6907A7AB104559F77D5A2532B557D95EDFFD6D88C073D87AC00FC838FC0D05282A0280368092A4BD67E95C20F3E14580BE28D8B351968C65E03",
|
||||
}
|
||||
|
||||
toReturn = append(toReturn, t1, t2, t3, t4, t5)
|
||||
|
||||
return toReturn
|
||||
}
|
||||
Reference in New Issue
Block a user