No commit suggestions generated

This commit is contained in:
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions

View 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),
}
}

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

View 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)
}
}

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

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

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

View 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)
}
}

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

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

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

View 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
View 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 &#x1D53E;<sub>1</sub>, &#x1D53E;<sub>2</sub>.
Data in &#x1D53E;<sub>2</sub> is twice the size of &#x1D53E;<sub>1</sub> and operations are slower.
BLS signatures require signatures and public keys to be in opposite groups i.e. signatures in &#x1D53E;<sub>1</sub> and public keys in &#x1D53E;<sub>2</sub> or
signatures in &#x1D53E;<sub>2</sub> and public keys in &#x1D53E;<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 &#x1D53E;<sub>2</sub> and public keys in &#x1D53E;<sub>1</sub>
- **Tiny Bls Basic -> SigBasicVt**: Provides all the functions for the Basic signature scheme with signatures in &#x1D53E;<sub>1</sub> and public keys in &#x1D53E;<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).

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

View 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,
)
}
}
}

View 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)
}

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

View 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")
}
}

View 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")
}
}

View 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.")
}
}

View 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)
}

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

View 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")
}
}

View 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")
}
}

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

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

View 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

View 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
View 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)
}

View 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

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

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

View 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

View 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]
}
}

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

View 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)
}

View 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))
}

View 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
)

File diff suppressed because it is too large Load Diff

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

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

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

View 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)
}

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

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