Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(crypto): supporting ed25519 #1481

Merged
merged 3 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crypto/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const (
)

const (
SignatureTypeBLS byte = 1
SignatureTypeBLS byte = 1
SignatureTypeEd25519 byte = 3
)

const (
Expand Down
3 changes: 3 additions & 0 deletions crypto/bls/hdkeychain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ var (
// ErrInvalidKeyData describes an error in which the provided key is
// not valid.
ErrInvalidKeyData = errors.New("key data is invalid")

// ErrInvalidHRP describes an error in which the HRP is not valid.
ErrInvalidHRP = errors.New("HRP is invalid")
)
10 changes: 7 additions & 3 deletions crypto/bls/hdkeychain/extendedkey.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package hdkeychain

// References:
// [PIP-11]: Deterministic key hierarchy for BLS12-381 curve
// PIP-11: Deterministic key hierarchy for BLS12-381 curve
// https://pips.pactus.org/PIPs/pip-11

import (
Expand Down Expand Up @@ -448,9 +448,13 @@ func NewKeyFromString(str string) (*ExtendedKey, error) {
return nil, err
}

isPrivate := true
if hrp == crypto.XPublicKeyHRP {
var isPrivate bool
if hrp == crypto.XPrivateKeyHRP {
isPrivate = true
} else if hrp == crypto.XPublicKeyHRP {
isPrivate = false
} else {
return nil, ErrInvalidHRP
}

return newExtendedKey(key, chainCode, path, isPrivate, pubOnG1), nil
Expand Down
88 changes: 80 additions & 8 deletions crypto/bls/hdkeychain/extendedkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,77 @@ func TestGenerateSeed(t *testing.T) {
seed, err := GenerateSeed(test.length)
assert.ErrorIs(t, err, test.err)

if test.err == nil && len(seed) != int(test.length) {
t.Errorf("GenerateSeed #%d (%s): length mismatch -- "+
"got %d, want %d", i, test.name, len(seed),
test.length)
if test.err == nil {
assert.Len(t, seed, int(test.length),
"GenerateSeed #%d (%s): length mismatch -- got %d, want %d",
i, test.name, len(seed), test.length)
}
}
}

// TestNewMaster ensures the NewMaster function works as intended.
func TestNewMaster(t *testing.T) {
tests := []struct {
name string
seed string
privKey string
err error
}{
// Test various valid seeds.
{
name: "16 bytes",
seed: "000102030405060708090a0b0c0d0e0f",
privKey: "4f55e31ee1c4f58af0840fd3f5e635fd6c07eacd14283c45d7d43729003abb84",
},
{
name: "32 bytes",
seed: "3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678",
privKey: "4c101174339ffca5cc0afca5d2d8e2538834781318e5e1c8afdabf7e6fb77444",
},
{
name: "64 bytes",
seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7" +
"b7875726f6c696663605d5a5754514e4b484542",
privKey: "47b660cc8dc2d4dc2cdf8893048bda9d5dc6318eb31f301b272b291b26cb20a1",
},

// Test invalid seeds.
{
name: "empty seed",
seed: "",
err: ErrInvalidSeedLen,
},
{
name: "15 bytes",
seed: "000000000000000000000000000000",
err: ErrInvalidSeedLen,
},
{
name: "65 bytes",
seed: "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000",
err: ErrInvalidSeedLen,
},
}

for i, test := range tests {
seed, _ := hex.DecodeString(test.seed)
extKeyG1, err := NewMaster(seed, true)
assert.ErrorIs(t, err, test.err)

extKeyG2, err := NewMaster(seed, true)
assert.ErrorIs(t, err, test.err)

if test.err == nil {
privKeyG1, _ := extKeyG1.RawPrivateKey()
assert.Equal(t, test.privKey, hex.EncodeToString(privKeyG1),
"NewMaster #%d (%s): privKeyG1 mismatch -- got %x, want %s",
i+1, test.name, privKeyG1, test.privKey)

continue
privKeyG2, _ := extKeyG2.RawPrivateKey()
assert.Equal(t, test.privKey, hex.EncodeToString(privKeyG2),
"NewMaster #%d (%s): privKeyG2 mismatch -- got %x, want %s",
i+1, test.name, privKeyG2, test.privKey)
}
}
}
Expand Down Expand Up @@ -411,12 +476,19 @@ func TestInvalidString(t *testing.T) {
expectedError: ErrInvalidKeyData,
},
{
str: "SECRET1ZQ5QQQQYQQYQQQQQZQQQGQQSQQQQQPJ568VS9LZ67JKWW0P6TQY9NY58LV0PCVRQQTAEMKGV6ULJNS99Y68JHCVGPYPZTWSAST8PWFJMJQDU0FU8D4YMF58CZ998PGRN29EZYHLWNDVDDJE7XP6L",
desc: "invalid type",
str: "XPUBLIC1ZQ5QQQQYQQYQQQQQZQQQGQQSQQQQQPJ568VS9LZ67JKWW0P6TQY9NY58LV0PCVRQQTAEMKGV6ULJNS99Y68JHCVGPYPZTWSAST8PWFJMJQDU0FU8D4YMF58CZ998PGRN29EZYHLWNDVDDJ3HALEC",
expectedError: ErrInvalidKeyData,
},
{
str: "XPUBLIC1ZQ5QQQQYQQYQQQQQZQQQGQQSQQQQQPJ568VS9LZ67JKWW0P6TQY9NY58LV0PCVRQQTAEMKGV6ULJNS99Y68JHCVGPYPZTWSAST8PWFJMJQDU0FU8D4YMF58CZ998PGRN29EZYHLWNDVDDJ3HALEC",
expectedError: ErrInvalidKeyData,
desc: "invalid hrp",
str: "SECRET1PQ5QQQQYQQYQQQQQZQQQGQQSQQQQQPJ568VS9LZ67JKWW0P6TQY9NY58LV0PCVRQQTAEMKGV6ULJNS99Y68JHCVGPYPZTWSAST8PWFJMJQDU0FU8D4YMF58CZ998PGRN29EZYHLWNDVDDJ98PYV5",
expectedError: ErrInvalidHRP,
},
{
desc: "invalid hrp",
str: "PUBLIC1PQ5QQQQYQQYQQQQQZQQQGQQSQQQQQPJ568VS9LZ67JKWW0P6TQY9NY58LV0PCVRQQTAEMKGV6ULJNS99Y68JHCVGPYPZTWSAST8PWFJMJQDU0FU8D4YMF58CZ998PGRN29EZYHLWNDVDDJ4Z2HK2",
expectedError: ErrInvalidHRP,
},
}

Expand Down
4 changes: 2 additions & 2 deletions crypto/bls/private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,6 @@ func (prv *PrivateKey) PublicKey() crypto.PublicKey {
return prv.PublicKeyNative()
}

func (prv *PrivateKey) EqualsTo(right crypto.PrivateKey) bool {
return prv.fr.Equal(&right.(*PrivateKey).fr)
func (prv *PrivateKey) EqualsTo(x crypto.PrivateKey) bool {
return prv.fr.Equal(&x.(*PrivateKey).fr)
}
5 changes: 3 additions & 2 deletions crypto/bls/public_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bls

import (
"bytes"
"crypto/subtle"
"fmt"
"io"

Expand Down Expand Up @@ -139,8 +140,8 @@ func (pub *PublicKey) Verify(msg []byte, sig crypto.Signature) error {
}

// EqualsTo checks if the current public key is equal to another public key.
func (pub *PublicKey) EqualsTo(right crypto.PublicKey) bool {
return bytes.Equal(pub.data, right.(*PublicKey).data)
func (pub *PublicKey) EqualsTo(x crypto.PublicKey) bool {
return subtle.ConstantTimeCompare(pub.data, x.(*PublicKey).data) == 1
}

// AccountAddress returns the account address derived from the public key.
Expand Down
5 changes: 3 additions & 2 deletions crypto/bls/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bls

import (
"bytes"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
Expand Down Expand Up @@ -88,8 +89,8 @@ func (sig *Signature) Decode(r io.Reader) error {
}

// EqualsTo checks if the current signature is equal to another signature.
func (sig *Signature) EqualsTo(right crypto.Signature) bool {
return bytes.Equal(sig.data, right.(*Signature).data)
func (sig *Signature) EqualsTo(x crypto.Signature) bool {
return subtle.ConstantTimeCompare(sig.data, x.(*Signature).data) == 1
}

// PointG1 returns the point on G1 for the signature.
Expand Down
1 change: 1 addition & 0 deletions crypto/ed25519/ed25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package ed25519
186 changes: 186 additions & 0 deletions crypto/ed25519/ed25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package ed25519_test

import (
"encoding/hex"
"testing"

bls12381 "github.com/kilic/bls12-381"
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/bls"
"github.com/pactus-project/pactus/util/testsuite"
"github.com/stretchr/testify/assert"
)

func TestSigning(t *testing.T) {
msg := []byte("zarb")
prv, _ := bls.PrivateKeyFromString(
"SECRET1PDRWTLP5PX0FAHDX39GXZJP7FKZFALML0D5U9TT9KVQHDUC99CMGQQJVK67")
pub, _ := bls.PublicKeyFromString(
"public1p4u8hfytl2pj6l9rj0t54gxcdmna4hq52ncqkkqjf3arha5mlk3x4mzpyjkhmdl20jae7f65aamjr" +
"vqcvf4sudcapz52ctcwc8r9wz3z2gwxs38880cgvfy49ta5ssyjut05myd4zgmjqstggmetyuyg7v5jhx47a")
sig, _ := bls.SignatureFromString(
"ad0f88cec815e9b8af3f0136297cb242ed8b6369af723fbdac077fa927f5780db7df47c77fb53f3a22324673f000c792")
addr, _ := crypto.AddressFromString("pc1p5x2a0lkt5nrrdqe0rkcv6r4pfkmdhrr3xk73tq")

sig1 := prv.Sign(msg)
assert.Equal(t, sig.Bytes(), sig1.Bytes())
assert.NoError(t, pub.Verify(msg, sig))
assert.Equal(t, pub, prv.PublicKey())
assert.Equal(t, addr, pub.ValidatorAddress())
}

func TestSignatureAggregate(t *testing.T) {
msg := []byte("zarb")
prv1, _ := bls.PrivateKeyFromString(
"SECRET1PDRWTLP5PX0FAHDX39GXZJP7FKZFALML0D5U9TT9KVQHDUC99CMGQQJVK67")
prv2, _ := bls.PrivateKeyFromString(
"SECRET1PDUV97560CWDGW2DR453YPUT84REN04G0DZFAPJQL5DV0CKDAN75QCJEV6F")
agg, _ := bls.SignatureFromString(
"a390ffec7061827b7e89193a26841dd9e3537b5db0af55661b624e8b93b855e9f65278850002ea72fb3098e674220eca")
sig1 := prv1.Sign(msg).(*bls.Signature)
sig2 := prv2.Sign(msg).(*bls.Signature)

assert.True(t, bls.SignatureAggregate(sig1, sig2).EqualsTo(agg))
assert.False(t, prv1.EqualsTo(prv2))
}

func TestAggregateFailed(t *testing.T) {
ts := testsuite.NewTestSuite(t)

pub1, prv1 := ts.RandBLSKeyPair()
pub2, prv2 := ts.RandBLSKeyPair()
pub3, prv3 := ts.RandBLSKeyPair()
pub4, prv4 := ts.RandBLSKeyPair()
msg1 := []byte("zarb")
msg2 := []byte("zarb0")

sig1 := prv1.Sign(msg1).(*bls.Signature)
sig11 := prv1.Sign(msg2).(*bls.Signature)
sig2 := prv2.Sign(msg1).(*bls.Signature)
sig3 := prv3.Sign(msg1).(*bls.Signature)
sig4 := prv4.Sign(msg1).(*bls.Signature)

agg1 := bls.SignatureAggregate(sig1, sig2, sig3)
agg2 := bls.SignatureAggregate(sig1, sig2, sig4)
agg3 := bls.SignatureAggregate(sig11, sig2, sig3)
agg4 := bls.SignatureAggregate(sig1, sig2)
agg5 := bls.SignatureAggregate(sig3, sig2, sig1)

pubs1 := []*bls.PublicKey{pub1, pub2, pub3}
pubs2 := []*bls.PublicKey{pub1, pub2, pub4}
pubs3 := []*bls.PublicKey{pub1, pub2}
pubs4 := []*bls.PublicKey{pub3, pub2, pub1}

pubAgg1 := bls.PublicKeyAggregate(pubs1...)
pubAgg2 := bls.PublicKeyAggregate(pubs2...)
pubAgg3 := bls.PublicKeyAggregate(pubs3...)
pubAgg4 := bls.PublicKeyAggregate(pubs4...)

assert.NoError(t, pub1.Verify(msg1, sig1))
assert.NoError(t, pub2.Verify(msg1, sig2))
assert.NoError(t, pub3.Verify(msg1, sig3))
assert.Error(t, pub2.Verify(msg1, sig1))
assert.Error(t, pub3.Verify(msg1, sig1))
assert.Error(t, pub1.Verify(msg1, agg1))
assert.Error(t, pub2.Verify(msg1, agg1))
assert.Error(t, pub3.Verify(msg1, agg1))

assert.NoError(t, pubAgg1.Verify(msg1, agg1))
assert.Error(t, pubAgg1.Verify(msg2, agg1))
assert.Error(t, pubAgg1.Verify(msg1, agg2))
assert.Error(t, pubAgg2.Verify(msg1, agg1))
assert.NoError(t, pubAgg2.Verify(msg1, agg2))
assert.Error(t, pubAgg2.Verify(msg2, agg2))
assert.Error(t, pubAgg1.Verify(msg1, agg3))
assert.Error(t, pubAgg1.Verify(msg2, agg3))
assert.Error(t, pubAgg1.Verify(msg1, agg4))
assert.Error(t, pubAgg3.Verify(msg1, agg1))
assert.NoError(t, pubAgg1.Verify(msg1, agg5))
assert.NoError(t, pubAgg4.Verify(msg1, agg1))
}

func TestAggregateOnlyOneSignature(t *testing.T) {
ts := testsuite.NewTestSuite(t)

_, prv1 := ts.RandBLSKeyPair()
msg1 := []byte("zarb")
sig1 := prv1.Sign(msg1).(*bls.Signature)
agg1 := bls.SignatureAggregate(sig1)

assert.True(t, agg1.EqualsTo(sig1))
}

func TestAggregateOnlyOnePublicKey(t *testing.T) {
ts := testsuite.NewTestSuite(t)

pub1, _ := ts.RandBLSKeyPair()
agg1 := bls.PublicKeyAggregate(pub1)

assert.True(t, agg1.EqualsTo(pub1))
}

// TODO: should we check for duplication here?
func TestDuplicatedAggregate(t *testing.T) {
ts := testsuite.NewTestSuite(t)

pub1, prv1 := ts.RandBLSKeyPair()
pub2, prv2 := ts.RandBLSKeyPair()

msg1 := []byte("zarb")

sig1 := prv1.Sign(msg1).(*bls.Signature)
sig2 := prv2.Sign(msg1).(*bls.Signature)

agg1 := bls.SignatureAggregate(sig1, sig2, sig1)
agg2 := bls.SignatureAggregate(sig1, sig2)
assert.False(t, agg1.EqualsTo(agg2))

pubs1 := []*bls.PublicKey{pub1, pub2}
pubs2 := []*bls.PublicKey{pub1, pub2, pub1}
pubAgg1 := bls.PublicKeyAggregate(pubs1...)
pubAgg2 := bls.PublicKeyAggregate(pubs2...)
assert.False(t, pubAgg1.EqualsTo(pubAgg2))
}

// TestHashToCurve ensures that the hash-to-curve function in kilic/bls12-381
// works as intended and is compatible with the spec.
// test vectors can be found here:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-J.9.1
func TestHashToCurve(t *testing.T) {
domain := []byte("QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_")
tests := []struct {
msg string
expected string
}{
{
"",
"052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1" +
"08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265",
},
{
"abc",
"03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903" +
"0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d",
},
{
"abcdef0123456789",
"11e0b079dea29a68f0383ee94fed1b940995272407e3bb916bbf268c263ddd57a6a27200a784cbc248e84f357ce82d98" +
"03a87ae2caf14e8ee52e51fa2ed8eefe80f02457004ba4d486d6aa1f517c0889501dc7413753f9599b099ebcbbd2d709",
},
{
"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" +
"qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
"15f68eaa693b95ccb85215dc65fa81038d69629f70aeee0d0f677cf22285e7bf58d7cb86eefe8f2e9bc3f8cb84fac488" +
"1807a1d50c29f430b8cafc4f8638dfeeadf51211e1602a5f184443076715f91bb90a48ba1e370edce6ae1062f5e6dd38",
},
}

g1 := bls12381.NewG1()
for no, test := range tests {
mappedPoint, _ := g1.HashToCurve([]byte(test.msg), domain)
d, _ := hex.DecodeString(test.expected)
expectedPoint, _ := g1.FromBytes(d)
assert.Equal(t, expectedPoint, mappedPoint,
"test %v: not match", no)
}
}
1 change: 1 addition & 0 deletions crypto/ed25519/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package ed25519
Loading
Loading