-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: created pure go alternative to ./crypt/bls12381
- Loading branch information
Showing
3 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package gobls | ||
|
||
import ( | ||
"io" | ||
"testing" | ||
|
||
"github.com/dashpay/tenderdash/crypto" | ||
"github.com/dashpay/tenderdash/crypto/internal/benchmarking" | ||
) | ||
|
||
func BenchmarkKeyGeneration(b *testing.B) { | ||
benchmarkKeygenWrapper := func(reader io.Reader) crypto.PrivKey { | ||
return genPrivKey(reader) | ||
} | ||
benchmarking.BenchmarkKeyGeneration(b, benchmarkKeygenWrapper) | ||
} | ||
|
||
func BenchmarkSigning(b *testing.B) { | ||
priv := GenPrivKey() | ||
benchmarking.BenchmarkSigning(b, priv) | ||
} | ||
|
||
func BenchmarkVerification(b *testing.B) { | ||
priv := GenPrivKey() | ||
benchmarking.BenchmarkVerification(b, priv) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,285 @@ | ||
package gobls | ||
|
||
import ( | ||
"bytes" | ||
"crypto/rand" | ||
"crypto/subtle" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math/big" | ||
|
||
chiabls "github.com/chuwt/chia-bls-go" | ||
"github.com/dashpay/tenderdash/blsbasic" | ||
"github.com/dashpay/tenderdash/internal/blscore" | ||
bls12381 "github.com/kilic/bls12-381" | ||
|
||
"github.com/dashpay/tenderdash/crypto" | ||
) | ||
|
||
//------------------------------------- | ||
|
||
var _ crypto.PrivKey = PrivKey{} | ||
|
||
const ( | ||
PrivKeyName = "tendermint/PrivKeyBLS12381" | ||
PubKeyName = "tendermint/PubKeyBLS12381" | ||
// PubKeySize is is the size, in bytes, of public keys as used in this package. | ||
PubKeySize = 48 | ||
// PrivateKeySize is the size, in bytes, of private keys as used in this package. | ||
PrivateKeySize = 32 | ||
// SignatureSize of an BLS12381 signature. | ||
SignatureSize = 96 | ||
// SeedSize is the size, in bytes, of private key seeds. These are the | ||
// private key representations used by RFC 8032. | ||
SeedSize = 32 | ||
|
||
KeyType = "bls12381" | ||
) | ||
|
||
var ( | ||
errPubKeyIsEmpty = errors.New("public key should not be empty") | ||
errPubKeyInvalidSize = errors.New("invalid public key size") | ||
|
||
emptyPubKeyVal = []byte{ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
} | ||
|
||
schema = blsbasic.New() | ||
) | ||
|
||
// BasicScheme returns basic bls scheme | ||
func BasicScheme() blscore.SchemeMPL { | ||
return schema | ||
} | ||
|
||
// PrivKey implements crypto.PrivKey. | ||
type PrivKey []byte | ||
|
||
// TypeTag satisfies the jsontypes.Tagged interface. | ||
func (PrivKey) TypeTag() string { return PrivKeyName } | ||
|
||
// Bytes returns the privkey byte format. | ||
func (privKey PrivKey) Bytes() []byte { | ||
return privKey | ||
} | ||
|
||
func privKeyFromBytes(privBytes []byte) (*chiabls.PrivateKey, error) { | ||
if keyLen := len(privBytes); keyLen != PrivateKeySize { | ||
err := errInvalidPrivateKeySize(keyLen) | ||
return nil, err | ||
} | ||
|
||
privBigInt := &big.Int{} | ||
privBigInt.SetBytes(privBytes) | ||
|
||
q := bls12381.NewG1().Q() | ||
privWrap := &big.Int{} | ||
privWrap = privWrap.Mod(privBigInt, q) | ||
|
||
privBytes = privWrap.Bytes() | ||
privKey := chiabls.KeyFromBytes(privBytes) | ||
|
||
return &privKey, nil | ||
} | ||
|
||
// Sign produces a signature on the provided message. | ||
// This assumes the privkey is wellformed in the golang format. | ||
// The first 32 bytes should be random, | ||
// corresponding to the normal bls12381 private key. | ||
// The latter 32 bytes should be the compressed public key. | ||
// If these conditions aren't met, Sign will panic or produce an | ||
// incorrect signature. | ||
func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { | ||
privKeyChia, err := privKeyFromBytes(privKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
sigBytes := schema.Sign(privKeyChia, msg) | ||
return sigBytes, nil | ||
} | ||
|
||
// SignDigest produces a signature on the message digest (hash). | ||
// This assumes the privkey is well-formed in the golang format. | ||
// The first 32 bytes should be random, | ||
// corresponding to the normal bls12381 private key. | ||
// The latter 32 bytes should be the compressed public key. | ||
// If these conditions aren't met, Sign will panic or produce an | ||
// incorrect signature. | ||
func (privKey PrivKey) SignDigest(hash []byte) ([]byte, error) { | ||
sigBytes, err := privKey.Sign(hash) | ||
return sigBytes, err | ||
} | ||
|
||
// PubKey gets the corresponding public key from the private key. | ||
// | ||
// Panics if the private key is not initialized. | ||
func (privKey PrivKey) PubKey() crypto.PubKey { | ||
blsPrivKey, err := privKeyFromBytes(privKey) | ||
if err != nil { | ||
// should probably change method sign to return an error but since | ||
// that's not available just panic... | ||
panic("bad key") | ||
} | ||
|
||
pubKey := blsPrivKey.GetPublicKey() | ||
pubBytes := pubKey.Bytes() | ||
pubIface := PubKey(pubBytes) | ||
|
||
return pubIface | ||
} | ||
|
||
// Equals - you probably don't need to use this. | ||
// Runs in constant time based on length of the keys. | ||
func (privKey PrivKey) Equals(other crypto.PrivKey) bool { | ||
if otherBLS, ok := other.(PrivKey); ok { | ||
return subtle.ConstantTimeCompare(privKey[:], otherBLS[:]) == 1 | ||
} | ||
|
||
return false | ||
} | ||
|
||
func (privKey PrivKey) Type() string { | ||
return KeyType | ||
} | ||
|
||
func (privKey PrivKey) TypeValue() crypto.KeyType { | ||
return crypto.BLS12381 | ||
} | ||
|
||
// GenPrivKey generates a new bls12381 private key. | ||
// It uses OS randomness in conjunction with the current global random seed | ||
// in tendermint/libs/common to generate the private key. | ||
func GenPrivKey() PrivKey { | ||
return genPrivKey(rand.Reader) | ||
} | ||
|
||
// genPrivKey generates a new bls12381 private key using the provided reader. | ||
func genPrivKey(rand io.Reader) PrivKey { | ||
seed := make([]byte, SeedSize) | ||
|
||
_, err := io.ReadFull(rand, seed) | ||
if err != nil { | ||
panic(err) | ||
} | ||
sk := chiabls.KeyGen(seed) | ||
skBytes := sk.Bytes() | ||
|
||
return skBytes | ||
} | ||
|
||
// GenPrivKeyFromSecret hashes the secret with SHA2, and uses | ||
// that 32 byte output to create the private key. | ||
// NOTE: secret should be the output of a KDF like bcrypt, | ||
// if it's derived from user input. | ||
func GenPrivKeyFromSecret(secret []byte) PrivKey { | ||
seed := crypto.Checksum(secret) // Not Ripemd160 because we want 32 bytes. | ||
|
||
sk := chiabls.KeyGen(seed) | ||
skBytes := sk.Bytes() | ||
|
||
return skBytes | ||
} | ||
|
||
func ReverseProTxHashes(proTxHashes []crypto.ProTxHash) []crypto.ProTxHash { | ||
reversedProTxHashes := make([]crypto.ProTxHash, len(proTxHashes)) | ||
for i := 0; i < len(proTxHashes); i++ { | ||
reversedProTxHashes[i] = proTxHashes[i].ReverseBytes() | ||
} | ||
return reversedProTxHashes | ||
} | ||
|
||
//------------------------------------- | ||
|
||
var _ crypto.PubKey = PubKey{} | ||
|
||
// PubKey PubKeyBLS12381 implements crypto.PubKey for the bls12381 signature scheme. | ||
type PubKey []byte | ||
|
||
// TypeTag satisfies the jsontypes.Tagged interface. | ||
func (PubKey) TypeTag() string { return PubKeyName } | ||
|
||
// Address is the SHA256-20 of the raw pubkey bytes. | ||
func (pubKey PubKey) Address() crypto.Address { | ||
if len(pubKey) != PubKeySize { | ||
panic("pubkey is incorrect size") | ||
} | ||
return crypto.AddressHash(pubKey) | ||
} | ||
|
||
// Bytes returns the PubKey byte format. | ||
func (pubKey PubKey) Bytes() []byte { | ||
return pubKey | ||
} | ||
|
||
func (pubKey PubKey) VerifySignatureDigest(hash []byte, sig []byte) bool { | ||
verified := pubKey.VerifySignature(hash, sig) | ||
return verified | ||
} | ||
|
||
func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool { | ||
// make sure we use the same algorithm to sign | ||
if len(sig) == 0 { | ||
// fmt.Printf("bls verifying error (signature empty) from message %X with key %X\n", msg, pubKey.Bytes()) | ||
return false | ||
} | ||
|
||
if len(sig) != SignatureSize { | ||
// fmt.Printf("bls verifying error (signature size) sig %X from message %X with key %X\n", sig, msg, pubKey.Bytes()) | ||
return false | ||
} | ||
|
||
pubKeyChia, err := chiabls.NewPublicKey(pubKey) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
verified := schema.Verify(&pubKeyChia, msg, sig) | ||
|
||
return verified | ||
} | ||
|
||
func (pubKey PubKey) String() string { | ||
return fmt.Sprintf("PubKeyBLS12381{%X}", []byte(pubKey)) | ||
} | ||
|
||
// HexString returns hex-string representation of pubkey | ||
func (pubKey PubKey) HexString() string { | ||
return hex.EncodeToString(pubKey) | ||
} | ||
|
||
func (pubKey PubKey) TypeValue() crypto.KeyType { | ||
return crypto.BLS12381 | ||
} | ||
|
||
func (pubKey PubKey) Type() string { | ||
return KeyType | ||
} | ||
|
||
func (pubKey PubKey) Equals(other crypto.PubKey) bool { | ||
if otherBLS, ok := other.(PubKey); ok { | ||
return bytes.Equal(pubKey[:], otherBLS[:]) | ||
} | ||
|
||
return false | ||
} | ||
|
||
// Validate validates a public key value | ||
func (pubKey PubKey) Validate() error { | ||
size := len(pubKey) | ||
if size != PubKeySize { | ||
return fmt.Errorf("public key has wrong size %d: %w", size, errPubKeyInvalidSize) | ||
} | ||
if bytes.Equal(pubKey, emptyPubKeyVal) { | ||
return errPubKeyIsEmpty | ||
} | ||
return nil | ||
} | ||
|
||
func errInvalidPrivateKeySize(size int) error { | ||
return fmt.Errorf("incorrect private key %d bytes but expected %d bytes", size, PrivateKeySize) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// nolint:lll | ||
package gobls | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/hex" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/dashpay/tenderdash/crypto" | ||
) | ||
|
||
func TestSignAndValidateBLS12381(t *testing.T) { | ||
privKey := GenPrivKey() | ||
pubKey := privKey.PubKey() | ||
|
||
hash := crypto.CRandBytes(128) | ||
sig, err := privKey.SignDigest(hash) | ||
require.Nil(t, err) | ||
|
||
// Test the signature | ||
assert.True(t, pubKey.VerifySignatureDigest(hash, sig)) | ||
} | ||
|
||
func TestBLSAddress(t *testing.T) { | ||
testCases := []struct { | ||
skB64 string | ||
pkB64 string | ||
addrHex string | ||
}{ | ||
{ | ||
skB64: "N3CR8OcoRjvC2n1UbFO59rgd9KHMGrW/KcWQi3FRoy0=", | ||
pkB64: "hiQykLvL/ZrnW97OeYGWU1AgjrXpmwTVzSTpVa2pYfjAoWLe50C+e9xsPAYTui6x", | ||
addrHex: "BB8F983D64252213936C9E962FDB066B3266C335", | ||
}, | ||
} | ||
for i, tc := range testCases { | ||
t.Run(fmt.Sprintf("test-case #%d", i+1), func(t *testing.T) { | ||
skBytes, err := base64.StdEncoding.DecodeString(tc.skB64) | ||
assert.NoError(t, err) | ||
pkBytes, err := base64.StdEncoding.DecodeString(tc.pkB64) | ||
assert.NoError(t, err) | ||
addrBytes, err := hex.DecodeString(tc.addrHex) | ||
assert.NoError(t, err) | ||
privKey := PrivKey(skBytes) | ||
pubKey := privKey.PubKey() | ||
assert.EqualValues(t, pkBytes, pubKey) | ||
assert.EqualValues(t, addrBytes, pubKey.Address()) | ||
}) | ||
} | ||
} | ||
|
||
func TestPublicKeyGeneration(t *testing.T) { | ||
testCases := []struct { | ||
sk string | ||
wantPk string | ||
}{ | ||
{ | ||
sk: "Bl1GYvRPgMpa/dpxb6gtph374TkDWoOv3OMH+jEoTWI=", | ||
wantPk: "pWtWZTAnU0lSZKziSOtp8XbueEzfLDlaOuyH9RYteQeWCremf9expxa57A6k33iU", | ||
}, | ||
} | ||
for i, tc := range testCases { | ||
t.Run(fmt.Sprintf("test-case #%d", i+1), func(t *testing.T) { | ||
skBytes, err := base64.StdEncoding.DecodeString(tc.sk) | ||
require.NoError(t, err) | ||
privateKey := PrivKey(skBytes) | ||
pkBytes := base64.StdEncoding.EncodeToString(privateKey.PubKey().Bytes()) | ||
require.Equal(t, tc.wantPk, pkBytes) | ||
}) | ||
} | ||
} |