diff --git a/go.mod b/go.mod index 980d0c687728de123020e38bb4e4da3ab4a70300..1f82ebd56d7467c7be2725fa4916c85662a2fe80 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/spf13/viper v1.6.2 github.com/stretchr/testify v1.5.1 github.com/valyala/fasttemplate v1.1.0 // indirect + go.dedis.ch/kyber/v3 v3.0.12 go.uber.org/atomic v1.6.0 go.uber.org/zap v1.14.0 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 diff --git a/go.sum b/go.sum index f6d9cee1c09a466bd97fb74cbe5a13201f4a93e7..c219da8b4765320160bf03a4a663da758b7bc29c 100644 --- a/go.sum +++ b/go.sum @@ -309,6 +309,15 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= +go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= +go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= +go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= +go.dedis.ch/kyber/v3 v3.0.12 h1:15d61EyBcBoFIS97kS2c/Vz4o3FR8ALnZ2ck9J/ebYM= +go.dedis.ch/kyber/v3 v3.0.12/go.mod h1:kXy7p3STAurkADD+/aZcsznZGKVHEqbtmdIzvPfrs1U= +go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= +go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= +go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= @@ -332,6 +341,7 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -384,6 +394,7 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/packages/binary/valuetransfer/address/address.go b/packages/binary/valuetransfer/address/address.go index 265fbf0cd394b153480ac557fc1c99bc8d310803..b73092fd6fd8d9c522be45fd802fda80824088fe 100644 --- a/packages/binary/valuetransfer/address/address.go +++ b/packages/binary/valuetransfer/address/address.go @@ -3,7 +3,6 @@ package address import ( "crypto/rand" "fmt" - "github.com/mr-tron/base58" "golang.org/x/crypto/blake2b" @@ -17,17 +16,20 @@ type Digest = []byte type Address [Length]byte +const ( + // every signature scheme has a version byte associated to it. + VERSION_ED25519 = byte(1) + VERSION_BLS = byte(2) +) + // Random creates a random address, which can for example be used in unit tests. +// first byte (version) is also random + func Random() (address Address) { // generate a random sequence of bytes - addressBytes := make([]byte, Length) - if _, err := rand.Read(addressBytes); err != nil { + if _, err := rand.Read(address[:]); err != nil { panic(err) } - - // copy the generated bytes into the result - copy(address[:], addressBytes) - return } @@ -56,7 +58,19 @@ func FromBase58(base58String string) (address Address, err error) { func FromED25519PubKey(key ed25119.PublicKey) (address Address) { digest := blake2b.Sum256(key[:]) - address[0] = 0 + address[0] = VERSION_ED25519 + copy(address[1:], digest[:]) + + return +} + +// FromBLSPubKey creates an address from marshaled BLS public key +// unmarshaled BLS public key conforms to interface kyber.Point + +func FromBLSPubKey(pubKey []byte) (address Address) { + digest := blake2b.Sum256(pubKey) + + address[0] = VERSION_BLS copy(address[1:], digest[:]) return diff --git a/packages/binary/valuetransfer/address/signaturescheme/bls.go b/packages/binary/valuetransfer/address/signaturescheme/bls.go new file mode 100644 index 0000000000000000000000000000000000000000..5f77819e79a96eef89f45b673344fd1b64be566e --- /dev/null +++ b/packages/binary/valuetransfer/address/signaturescheme/bls.go @@ -0,0 +1,249 @@ +package signaturescheme + +import ( + "fmt" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/mr-tron/base58" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/pairing/bn256" + "go.dedis.ch/kyber/v3/sign" + "go.dedis.ch/kyber/v3/sign/bdn" + "go.dedis.ch/kyber/v3/util/random" + "math/rand" +) + +// BLS implements BLS signature scheme which is robust against rogue public key attacks, or BDN +// it uses go.dedis/kyber library +// more info https://github.com/dedis/kyber/blob/master/sign/bdn/bdn.go +// usually BLS signatures are used as threshold signatures. +// This package doesn't implement any threshold signature related primitives. +// it only contains what is needed for the node to check validity of the BLS signatures against addresses +// and also minimum signing required for testing + +var suite = bn256.NewSuite() + +const ( + BLS_SIGNATURE_SIZE = 64 + BLS_PUBLIC_KEY_SIZE = 128 + BLS_PRIVATE_KEY_SIZE = 32 + BLS_FULL_SIGNATURE_SIZE = 1 + BLS_PUBLIC_KEY_SIZE + BLS_SIGNATURE_SIZE +) + +// ---------------- implements SignatureScheme interface +// blsSignatureScheme defines an interface for the key pairs of BLS signatures. + +type blsSignatureScheme struct { + priKey kyber.Scalar + pubKey kyber.Point +} + +// deterministic sequence +var rnd = random.New(rand.New(rand.NewSource(42))) + +// RandBLS creates a RANDOM instance of a signature scheme, that is used to sign the corresponding address. +// mostly intended for testing. +// only for testing: each time same sequence! + +func RandBLS() SignatureScheme { + ret := &blsSignatureScheme{} + ret.priKey, ret.pubKey = bdn.NewKeyPair(suite, rnd) + return ret +} + +// BLS creates an instance of BLS signature scheme +// from given private and public keys in marshaled binary form + +func BLS(priKey, pubKey []byte) (SignatureScheme, error) { + if len(priKey) != BLS_PRIVATE_KEY_SIZE || len(pubKey) != BLS_PUBLIC_KEY_SIZE { + return nil, fmt.Errorf("wrong key size") + } + ret := &blsSignatureScheme{ + priKey: suite.G2().Scalar(), + pubKey: suite.G2().Point(), + } + if err := ret.pubKey.UnmarshalBinary(pubKey); err != nil { + return nil, err + } + if err := ret.priKey.UnmarshalBinary(priKey); err != nil { + return nil, err + } + return ret, nil +} + +func (sigscheme *blsSignatureScheme) Version() byte { + return address.VERSION_BLS +} + +func (sigscheme *blsSignatureScheme) Address() address.Address { + b, err := sigscheme.pubKey.MarshalBinary() + if err != nil { + panic(err) + } + return address.FromBLSPubKey(b) +} + +func (sigscheme *blsSignatureScheme) Sign(data []byte) Signature { + sig, err := bdn.Sign(suite, sigscheme.priKey, data) + if err != nil { + panic(err) + } + pubKeyBin, err := sigscheme.pubKey.MarshalBinary() + if err != nil { + panic(err) + } + return newBLSSignature(pubKeyBin, sig) +} + +func (sigscheme *blsSignatureScheme) String() string { + pri, err := sigscheme.priKey.MarshalBinary() + if err != nil { + return fmt.Sprintf("BLS sigsheme: %v", err) + } + pub, err := sigscheme.pubKey.MarshalBinary() + if err != nil { + return fmt.Sprintf("BLS sigsheme: %v", err) + } + return base58.Encode(pri) + ", " + base58.Encode(pub) +} + +// interface contract (allow the compiler to check if the implementation has all of the required methods). +var _ SignatureScheme = &blsSignatureScheme{} + +// ---------------- implements Signature interface + +type blsSignature [BLS_FULL_SIGNATURE_SIZE]byte + +func BLSSignatureFromBytes(data []byte) (result *blsSignature, err error, consumedBytes int) { + consumedBytes = 0 + err = nil + if len(data) < BLS_FULL_SIGNATURE_SIZE { + err = fmt.Errorf("marshalled BLS signature size must be %d", BLS_FULL_SIGNATURE_SIZE) + return + } + if data[0] != address.VERSION_BLS { + err = fmt.Errorf("wrong version byte, expected %d", address.VERSION_BLS) + } + result = &blsSignature{} + copy(result[:BLS_FULL_SIGNATURE_SIZE], data) + consumedBytes = BLS_FULL_SIGNATURE_SIZE + return +} + +func newBLSSignature(pubKey, signature []byte) *blsSignature { + var ret blsSignature + ret[0] = address.VERSION_BLS + copy(ret.pubKey(), pubKey) + copy(ret.signature(), signature) + return &ret +} + +func (sig *blsSignature) pubKey() []byte { + return sig[1 : BLS_PUBLIC_KEY_SIZE+1] +} + +func (sig *blsSignature) signature() []byte { + return sig[1+BLS_PUBLIC_KEY_SIZE:] +} + +func (sig *blsSignature) IsValid(signedData []byte) bool { + if sig[0] != address.VERSION_BLS { + return false + } + // unmarshal public key + pubKey := suite.G2().Point() + if err := pubKey.UnmarshalBinary(sig.pubKey()); err != nil { + return false + } + return bdn.Verify(suite, pubKey, signedData, sig.signature()) == nil +} + +func (sig *blsSignature) Bytes() []byte { + return sig[:] +} + +func (sig *blsSignature) Address() address.Address { + return address.FromBLSPubKey(sig.pubKey()) +} + +func (sig *blsSignature) String() string { + return base58.Encode(sig[:]) +} + +func AggregateBLSSignatureSchemes(sigSchemes ...SignatureScheme) (SignatureScheme, error) { + priKeys := make([]kyber.Scalar, len(sigSchemes)) + pubKeys := make([]kyber.Point, len(sigSchemes)) + for i, s := range sigSchemes { + ss, ok := s.(*blsSignatureScheme) + if !ok { + return nil, fmt.Errorf("not a BLS signature scheme") + } + priKeys[i] = ss.priKey + pubKeys[i] = ss.pubKey + } + aggregatedPriKey := suite.G2().Scalar().Zero() + // sum up all private keys + for i := range priKeys { + aggregatedPriKey = aggregatedPriKey.Add(aggregatedPriKey, priKeys[i]) + } + mask, _ := sign.NewMask(suite, pubKeys, nil) + for i := range pubKeys { + _ = mask.SetBit(i, true) + } + aggregatedPubKey, err := bdn.AggregatePublicKeys(suite, mask) + if err != nil { + return nil, err + } + return &blsSignatureScheme{ + priKey: aggregatedPriKey, + pubKey: aggregatedPubKey, + }, nil +} + +func AggregateBLSSignatures(sigs ...Signature) (Signature, error) { + if len(sigs) == 0 { + return nil, fmt.Errorf("must be at least one signature to aggregate") + } + if len(sigs) == 1 { + return sigs[0], nil + } + + pubKeys := make([]kyber.Point, len(sigs)) + signatures := make([][]byte, len(sigs)) + + var err error + for i, sig := range sigs { + sigBls, ok := sig.(*blsSignature) + if !ok { + return nil, fmt.Errorf("not a BLS signature") + } + pubKeys[i] = suite.G2().Point() + if err = pubKeys[i].UnmarshalBinary(sigBls.pubKey()); err != nil { + return nil, err + } + signatures[i] = sigBls.signature() + } + mask, _ := sign.NewMask(suite, pubKeys, nil) + for i := range pubKeys { + _ = mask.SetBit(i, true) + } + aggregatedSignature, err := bdn.AggregateSignatures(suite, signatures, mask) + if err != nil { + return nil, err + } + sigBin, err := aggregatedSignature.MarshalBinary() + if err != nil { + return nil, err + } + aggregatedPubKey, err := bdn.AggregatePublicKeys(suite, mask) + if err != nil { + return nil, err + } + pubKeyBin, err := aggregatedPubKey.MarshalBinary() + if err != nil { + return nil, err + } + return newBLSSignature(pubKeyBin, sigBin), nil +} + +// interface contract (allow the compiler to check if the implementation has all of the required methods). +var _ Signature = &blsSignature{} diff --git a/packages/binary/valuetransfer/address/signaturescheme/bls_test.go b/packages/binary/valuetransfer/address/signaturescheme/bls_test.go new file mode 100644 index 0000000000000000000000000000000000000000..28e1a3a78b508d3d304f2aa365eabeb696e44694 --- /dev/null +++ b/packages/binary/valuetransfer/address/signaturescheme/bls_test.go @@ -0,0 +1,44 @@ +package signaturescheme + +import ( + "github.com/magiconair/properties/assert" + "testing" +) + +var dataToSign = []byte("Hello Boneh-Lynn-Shacham (BLS) --> Boneh-Drijvers-Neven (BDN)") + +func TestBLS_base(t *testing.T) { + + blsSigScheme := RandBLS() + t.Logf("generating random BLS signature scheme: %s\n", blsSigScheme.(*blsSignatureScheme).String()) + signature := blsSigScheme.Sign(dataToSign) + + assert.Equal(t, blsSigScheme.Address(), signature.Address()) + res := signature.IsValid(dataToSign) + assert.Equal(t, res, true) +} + +// number of signatures to aggregate +const numSigs = 100 + +func TestBLS_aggregation(t *testing.T) { + sigs := make([]Signature, numSigs) + sigSchemes := make([]SignatureScheme, numSigs) + + for i := range sigs { + sigSchemes[i] = RandBLS() + sigs[i] = sigSchemes[i].Sign(dataToSign) + } + aggregatedSig1, err := AggregateBLSSignatures(sigs...) + assert.Equal(t, err, nil) + + assert.Equal(t, aggregatedSig1.IsValid(dataToSign), true) + + aggregatedScheme, err := AggregateBLSSignatureSchemes(sigSchemes...) + assert.Equal(t, err, nil) + + if err == nil { + aggregatedSig2 := aggregatedScheme.Sign(dataToSign) + assert.Equal(t, aggregatedSig2, aggregatedSig2) + } +} diff --git a/packages/binary/valuetransfer/address/signaturescheme/ed25519.go b/packages/binary/valuetransfer/address/signaturescheme/ed25519.go index 997de93a6391b5dbb511091c0d69b7960d8d8fe3..4a3db62f31091ec1ab9d092a1c66049f586c6ca8 100644 --- a/packages/binary/valuetransfer/address/signaturescheme/ed25519.go +++ b/packages/binary/valuetransfer/address/signaturescheme/ed25519.go @@ -10,11 +10,6 @@ import ( // region PUBLIC API /////////////////////////////////////////////////////////////////////////////////////////////////// -const ( - // every signature scheme has a version byte associated to it. - VERSION_ED25519 = byte(1) -) - // ED25519 creates an instance of a signature scheme, that is used to sign the corresponding address. func ED25519(keyPair ed25119.KeyPair) SignatureScheme { return &ed25519SignatureScheme{ @@ -33,7 +28,7 @@ type ed25519SignatureScheme struct { // Version returns the version byte that is associated to this signature scheme. func (signatureScheme *ed25519SignatureScheme) Version() byte { - return VERSION_ED25519 + return address.VERSION_ED25519 } // Address returns the address that this signature scheme instance is securing. @@ -82,7 +77,7 @@ func Ed25519SignatureFromBytes(bytes []byte, optionalTargetObject ...*ed25519Sig versionByte, err := marshalUtil.ReadByte() if err != nil { return - } else if versionByte != VERSION_ED25519 { + } else if versionByte != address.VERSION_ED25519 { err = fmt.Errorf("invalid version byte when parsing ed25519 signature") return @@ -116,7 +111,7 @@ func (signature *ed25519Signature) IsValid(signedData []byte) bool { // Bytes returns a marshaled version of the signature. func (signature *ed25519Signature) Bytes() []byte { marshalUtil := marshalutil.New(1 + ed25119.PublicKeySize + ed25119.SignatureSize) - marshalUtil.WriteByte(VERSION_ED25519) + marshalUtil.WriteByte(address.VERSION_ED25519) marshalUtil.WriteBytes(signature.publicKey[:]) marshalUtil.WriteBytes(signature.signature[:]) diff --git a/packages/binary/valuetransfer/transaction/signatures.go b/packages/binary/valuetransfer/transaction/signatures.go index 26f3f45f468fc2a45e35fb56f3bacb70fbc3f362..843389e3cb50b025fe7895de56a1258e004bcb52 100644 --- a/packages/binary/valuetransfer/transaction/signatures.go +++ b/packages/binary/valuetransfer/transaction/signatures.go @@ -44,10 +44,12 @@ func SignaturesFromBytes(bytes []byte, optionalTargetObject ...*Signatures) (res } // 0 byte encodes the end of the signatures + var typeCastedSignature signaturescheme.Signature for versionByte != 0 { + typeCastedSignature = nil // perform signature scheme specific decoding switch versionByte { - case signaturescheme.VERSION_ED25519: + case address.VERSION_ED25519: marshalUtil.ReadSeek(-1) signature, signatureErr := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return signaturescheme.Ed25519SignatureFromBytes(data) }) if signatureErr != nil { @@ -55,8 +57,21 @@ func SignaturesFromBytes(bytes []byte, optionalTargetObject ...*Signatures) (res return } - typeCastedSignature := signature.(signaturescheme.Signature) + typeCastedSignature = signature.(signaturescheme.Signature) + case address.VERSION_BLS: + marshalUtil.ReadSeek(-1) + signature, signatureErr := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return signaturescheme.BLSSignatureFromBytes(data) }) + if signatureErr != nil { + err = signatureErr + + return + } + typeCastedSignature = signature.(signaturescheme.Signature) + default: + // unknown signature type... + } + if typeCastedSignature != nil { result.orderedMap.Set(typeCastedSignature.Address(), typeCastedSignature) } diff --git a/packages/binary/valuetransfer/transaction/signatures_test.go b/packages/binary/valuetransfer/transaction/signatures_test.go index 88589852620a60aef23c75b1e89a150c75f08ac3..a27426c1b16b22d03f41db1caa1b9dc94ab09111 100644 --- a/packages/binary/valuetransfer/transaction/signatures_test.go +++ b/packages/binary/valuetransfer/transaction/signatures_test.go @@ -15,16 +15,18 @@ func TestSignatures(t *testing.T) { address1SigScheme := signaturescheme.ED25519(ed25119.GenerateKeyPair()) address2SigScheme := signaturescheme.ED25519(ed25119.GenerateKeyPair()) + address3SigScheme := signaturescheme.RandBLS() signatures := NewSignatures() signatures.Add(address1SigScheme.Address(), address1SigScheme.Sign(dataToSign)) signatures.Add(address2SigScheme.Address(), address2SigScheme.Sign(dataToSign)) + signatures.Add(address3SigScheme.Address(), address3SigScheme.Sign(dataToSign)) - assert.Equal(t, 2, signatures.Size()) + assert.Equal(t, 3, signatures.Size()) signatures.Add(address1SigScheme.Address(), address1SigScheme.Sign(dataToSign)) - assert.Equal(t, 2, signatures.Size()) + assert.Equal(t, 3, signatures.Size()) signatures.ForEach(func(address address.Address, signature signaturescheme.Signature) bool { assert.Equal(t, true, signature.IsValid(dataToSign)) @@ -39,7 +41,7 @@ func TestSignatures(t *testing.T) { return } - assert.Equal(t, 2, clonedSignatures.Size()) + assert.Equal(t, 3, clonedSignatures.Size()) clonedSignatures.ForEach(func(address address.Address, signature signaturescheme.Signature) bool { assert.Equal(t, true, signature.IsValid(dataToSign))