Smart Wallet Vulnerability using The Rogue Public Key Attack

A good deal of our current research work is focused on digital wallets, and we hope that through our current work we can create an…

Photo by Towfiqu barbhuiya on Unsplash

Smart Wallet Vulnerability using The Rogue Public Key Attack

A good deal of our current research work is focused on digital wallets, and we hope that through our current work we can create an infrastructure in the EU, and where every citizen will have a digital wallet. And, so, at the core of security of this is the fundamental implementation of wallets. If there were to be any security vulnerabilities, it could compromise the whole infrastructure.

And, so, when you see a “new” vulnerability on wallets, you take notice. This happened this week with this tweet from the Defi Security Summit [here]:

With this, the compromise uses the BLS aggregated signature method, and inverts one of the public keys in order to produce a valid signature. Of course, there would have to be some bad programming for this to happen, especially if there was no checking for the public keys used. The first thing that must be said is that this is NOT a new attack, and was outlined in [1]. While we implement Hyperledger Fabric wallets (as part of a proposed integration with EBSI), our implementations are not vulnerable.

So, please stay with me here, as I outline how the Rogue Public Key attack works.

The basics of BLS signatures

Elliptic curves are used fairly extensively in public key encryption (such as in Bitcoin and Tor). A BN-curve (Barreto-Naehrig curve) [3] defines an elliptic curve which can be used for pairings that allow for a high security and efficiency level. One of these curves a 256-bit BN curve. Basically, it uses two curves (G1 and G2) and then can map points onto these curves, and then perform a pairing operation, and where the special feature is that we can produce a pairing function (e) for:

e(a.g1, g2) = e(g1,a.g2)

and where a.g1 is a point on G1, with g1 as base point, and where a.g2 is a point on G2, and with g2 as the base point. Now, let’s create a digital signature with this.

With BN256, we create the private key from a random number. This is a private key value (sk) and a public key mapped to the G2 curve:

pk=sk.G2

Next, we create a hash of the message (h(M)) and then create the signature of:

σ=sk.H(M)

Next, we check the pair:

e(σ,G2)==e(H(m),pk)

This works because:

e(σ,G2)==e(H(m),pk)

is:

e(sk.H(M),G2)==e(H(m),pk)

and:

e(H(M),sk.G2)==e(H(m),pk)

which is:

e(H(M),pk)==e(H(m),pk)

If lhs is equal to rhs, the pairing works, and the signature is verified. To illustrate, I have coded here:

https://asecuritysite.com/bn/bls_sig

Signature aggregation

Now, BLS signatures have a special property, and where we can aggregate signatures together. This could apply where there are many signers to a transaction, and where each party can add their own part of the signature. The overall public key is then the aggregation of all the public keys involved.

We now have two sets of keys:

pub_1=sk_1.G2

and:

pub_2=sk2.G2

Then the aggregated public key will be:

pk_a=pub1+pub2

Then the aggregated signature will be:

σ_a=σ_1+σ_2

The check is then:

e(σ_a,G2)==e(H(m),pk_a)

Coding

An outline of the Go code to aggregate two signatures and two public keys is [here]:

package main
import (
"bytes"
"fmt"
"math/big"
	"crypto/rand"
	"github.com/cloudflare/bn256"
)
func main() {
	salt := []byte{11, 12, 13, 14}
msg := []byte("hello")
	G2 := new(bn256.G2).ScalarBaseMult(big.NewInt(1))
	privKey, _, _ := bn256.RandomG2(rand.Reader)
pubKey := new(bn256.G2).ScalarBaseMult(privKey)
	hash := bn256.HashG1(msg, salt)
sigma := hash.ScalarMult(hash, privKey)
	//  check e(sigma, g2) == e(H(m), pk )
	h := bn256.HashG1(msg, salt)
	rhs := bn256.Pair(h, pubKey)
lhs := bn256.Pair(sigma, G2)
	fmt.Printf("Private key 1: %x\n", privKey)
fmt.Printf("\nPublic key 1: %s\n", pubKey)
fmt.Printf("\nSignature (sigma): %x\n", sigma.Marshal())
	if bytes.Equal(rhs.Marshal(), lhs.Marshal()) {
fmt.Printf("\nSignature verified!\n")
}
	beta, _, _ := bn256.RandomG2(rand.Reader)
betapubKey := new(bn256.G2).ScalarBaseMult(beta)
hash = bn256.HashG1(msg, salt)
sigmabeta := hash.ScalarMult(hash, beta)
	fmt.Printf("Private key 2: %x\n", privKey)
fmt.Printf("\nPublic key 2: %s\n", pubKey)
fmt.Printf("\nSignature (sigma2): %x\n", sigma.Marshal())
	sigma_aggr := sigma.Add(sigma, sigmabeta)
	pub_aggr := pubKey.Add(pubKey, betapubKey)
	fmt.Printf("\nSignature aggregated: %x\n", sigma.Marshal())
fmt.Printf("\nPublic key aggregated: %x\n", sigma.Marshal())
	rhs = bn256.Pair(h, pub_aggr)
lhs = bn256.Pair(sigma_aggr, G2)
	if bytes.Equal(rhs.Marshal(), lhs.Marshal()) {
fmt.Printf("\nAggregated Signature verified!")
}
}

A sample run [here]:

Private key 1: 1ae7d6e33792d86d96353c92a4fcc786e99671d95a404ae86cf968111f69ff00
Public key 1: bn256.G2((5b6345be6d9e0277b81d4b1a48eff04cc51096d7b1de5bbc15bef6092b8f00e6, 420ff26d940919c65b48db8c0391b742e41df5b720f8017d27271335113e199f), (1d71735172172c627401366a53ce4e1e7e71bf9f95ff13cd64aac18a47f5b70f, 02790bca8999d1423d888f7d90181b5b91e53e6f0bdd001cb5f4651787145617))
Signature (sigma): 79c4eb80e68d65b4ca4c4881d6c748fd3025b14f460983306f5b65b1a4b6c07f174a90b25016742014d224d405916ca505fdf2c9a974232a1db7840a010a7523
Signature verified!Private key 2: 1ae7d6e33792d86d96353c92a4fcc786e99671d95a404ae86cf968111f69ff00
Public key 2: bn256.G2((5b6345be6d9e0277b81d4b1a48eff04cc51096d7b1de5bbc15bef6092b8f00e6, 420ff26d940919c65b48db8c0391b742e41df5b720f8017d27271335113e199f), (1d71735172172c627401366a53ce4e1e7e71bf9f95ff13cd64aac18a47f5b70f, 02790bca8999d1423d888f7d90181b5b91e53e6f0bdd001cb5f4651787145617))
Signature (sigma2): 79c4eb80e68d65b4ca4c4881d6c748fd3025b14f460983306f5b65b1a4b6c07f174a90b25016742014d224d405916ca505fdf2c9a974232a1db7840a010a7523
Signature aggregated: 20a834d3ee41cfff5aed8a6fe1167550711df016001b8cbf598510dea0b3e0781bca3bc8c18e16bacd019dcdfc38b4fab62f5337b87db9021e8ebb59418ba737
Public key aggregated: 20a834d3ee41cfff5aed8a6fe1167550711df016001b8cbf598510dea0b3e0781bca3bc8c18e16bacd019dcdfc38b4fab62f5337b87db9021e8ebb59418ba737
Aggregated Signature verified!

Rogue Public Key Attack

The Rogue Key Key Attack is then quite simple [1, 2]. Eve generates a public key of:

pk_2 = (-pk_1)

and then is able to create an aggregated public key which is zero (or the point at infinity. The signature check will then be:

e(σ,G2)==e(H(m),[0].G2)

and which will equal:

e([0].σ,G2)==e(H(m),G2)

and where Eve can sign the aggregated message with [0]. The negative of the public key is fairly easy to implement:

betapubKey := new(bn256.G2).ScalarBaseMult(big.NewInt(1))
pubKey.Neg(betapubKey)
pub_aggr := pubKey.Add(pubKey, betapubKey)

In the HTML from [1], this is described in log form as:

I will post another article on how this translates to elliptic curves, later.

Conclusions

To not check for the zero public key is just sloppy coding, so don’t let it happen to you.

References

[1] Boneh, D., Drijvers, M., & Neven, G. (2018). BLS multi-signatures with public-key aggregation [here].

[2] Boneh, D., Gorbunov, S., Wee, H., & Zhang, Z. (2019). Bls signature scheme. Technical Report draft-boneh-bls-signature-00, Internet Engineering Task Force [here].

[3] Barreto, P. S., & Naehrig, M. (2005, August). Pairing-friendly elliptic curves of prime order. In International workshop on selected areas in cryptography (pp. 319–331). Springer, Berlin, Heidelberg. [paper]