package secp256k1 import ( "bytes" "crypto/rand" "fmt" "math/big" "testing" "go.dedis.ch/kyber/v3/group/curve25519" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/certusone/wormhole/bridge/pkg/signatures/cryptotest" ) var numPointSamples = 10 var randomStreamPoint = cryptotest.NewStream(&testing.T{}, 0) func TestPoint_String(t *testing.T) { require.Equal(t, NewPoint().String(), "Secp256k1{X: fieldElt{0}, Y: fieldElt{0}}") } func TestPoint_CloneAndEqual(t *testing.T) { f := NewPoint() for i := 0; i < numPointSamples; i++ { g := f.Clone() f.Pick(randomStreamPoint) assert.NotEqual(t, f, g, "modifying original shouldn't change clone") g, h := f.Clone(), f.Clone() assert.Equal(t, f, g, "clones should be equal") g.Add(g, f) assert.Equal(t, h, f, "modifying a clone shouldn't change originial") } } func TestPoint_NullAndAdd(t *testing.T) { f, g := NewPoint(), NewPoint() for i := 0; i < numPointSamples; i++ { g.Null() f.Pick(randomStreamPoint) g.Add(f, g) assert.Equal(t, f, g, "adding zero should have no effect") } } func TestPoint_Set(t *testing.T) { p := NewPoint() base := NewPoint().Base() assert.NotEqual(t, p, base, "generator should not be zero") p.Set(base) assert.Equal(t, p, base, "setting to generator should yield generator") } func TestPoint_Embed(t *testing.T) { p := NewPoint() for i := 0; i < numPointSamples; i++ { data := make([]byte, p.EmbedLen()) _, err := rand.Read(data) require.Nil(t, err) p.Embed(data, randomStreamPoint) require.True(t, s256.IsOnCurve(p.X.Int(), p.Y.Int()), "should embed to a secp256k1 point") output, err := p.Data() require.NoError(t, err) require.True(t, bytes.Equal(data, output), "should get same value back after round-trip "+ "embedding, got %v, then %v", data, output) } var uint256Bytes [32]byte uint256Bytes[0] = 30 p.X.SetBytes(uint256Bytes) _, err := p.Data() require.Error(t, err) require.Contains(t, err.Error(), "specifies too much data") var b bytes.Buffer p.Pick(randomStreamPoint) _, err = p.MarshalTo(&b) require.NoError(t, err) _, err = p.UnmarshalFrom(&b) require.NoError(t, err) data := make([]byte, p.EmbedLen()+1) // Check length validation. This test defer func() { // comes last, because it triggers panic r := recover() require.NotNil(t, r, "calling embed with too much data should panic") require.Contains(t, r, "too much data to embed in a point") }() p.Embed(data, randomStreamPoint) } func TestPoint_AddSubAndNeg(t *testing.T) { zero := NewPoint().Null() p := NewPoint() for i := 0; i < numPointSamples; i++ { p.Pick(randomStreamPoint) q := p.Clone() p.Sub(p, q) require.True(t, p.Equal(zero), "subtracting a point from itself should give zero, "+ "got %v - %v = %v ≠ %v", q, q, p, zero) p.Neg(q) r := NewPoint().Add(p, q) require.True(t, r.Equal(zero), "adding a point to its negative should give zero"+ " got %v+%v=%v≠%v", q, p, r, zero) r.Neg(q) p.Sub(q, r) s := NewPoint().Add(q, q) require.True(t, p.Equal(s), "q-(-q)=q+q?"+ " got %v-%v=%v≠%v", q, r, p, s) } } func TestPoint_Mul(t *testing.T) { zero := NewPoint().Null() multiplier := newScalar(bigZero) one := newScalar(big.NewInt(int64(1))) var p *Secp256k1Point for i := 0; i < numPointSamples/5; i++ { if i%20 == 0 { p = nil // Test default to generator point } else { p = NewPoint() p.Pick(randomStreamPoint) } multiplier.Pick(randomStreamPoint) q := NewPoint().Mul(one, p) comparee := NewPoint() if p == (*Secp256k1Point)(nil) { comparee.Base() } else { comparee = p.Clone().(*Secp256k1Point) } require.True(t, comparee.Equal(q), "1*p=p? %v * %v ≠ %v", one, comparee, q) q.Mul(multiplier, p) negMultiplier := newScalar(bigZero).Neg(multiplier) r := NewPoint().Mul(negMultiplier, p) s := NewPoint().Add(q, r) require.True(t, s.Equal(zero), "s*p+(-s)*p=0? got "+ "%v*%v + %v*%v = %v + %v = %v ≠ %v", multiplier, p, ) } } func TestPoint_Marshal(t *testing.T) { p := NewPoint() for i := 0; i < numPointSamples; i++ { p.Pick(randomStreamPoint) serialized, err := p.MarshalBinary() require.Nil(t, err) q := NewPoint() err = q.UnmarshalBinary(serialized) require.Nil(t, err) require.True(t, p.Equal(q), "%v marshalled to %x, which "+ "unmarshalled to %v", p, serialized, q) } p.X.SetInt(big.NewInt(0)) // 0³+7 is not a square in the base field. _, err := p.MarshalBinary() require.Error(t, err) require.Contains(t, err.Error(), "not a square") p.X.SetInt(big.NewInt(1)) _, err = p.MarshalBinary() require.Error(t, err) require.Contains(t, err.Error(), "not a point on the curve") id := p.MarshalID() require.Equal(t, string(id[:]), "sp256.po") data := make([]byte, 34) err = p.UnmarshalBinary(data) require.Error(t, err) require.Contains(t, err.Error(), "wrong length for marshaled point") require.Contains(t, p.UnmarshalBinary(data[:32]).Error(), "wrong length for marshaled point") data[32] = 2 require.Contains(t, p.UnmarshalBinary(data[:33]).Error(), "bad sign byte") data[32] = 0 data[31] = 5 // I.e., x-ordinate is now 5 require.Contains(t, p.UnmarshalBinary(data[:33]).Error(), "does not correspond to a curve point") } func TestPoint_BaseTakesCopy(t *testing.T) { p := NewPoint().Base() p.Add(p, p) q := NewPoint().Base() assert.False(t, p.Equal(q), "modifying output from Base changes S256.G{x,y}") } func TestPoint_EthereumAddress(t *testing.T) { // Example taken from // https://theethereum.wiki/w/index.php/Accounts,_Addresses,_Public_And_Private_Keys,_And_Tokens pString := "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266" pInt, ok := big.NewInt(0).SetString(pString, 16) require.True(t, ok, "failed to parse private key") private := newScalar(pInt) public := NewPoint().Mul(private, nil) address := EthereumAddress(public) assert.Equal(t, fmt.Sprintf("%x", address), "c2d7cf95645d33006175b78989035c7c9061d3f9") } func TestIsSecp256k1Point(t *testing.T) { p := curve25519.NewBlakeSHA256Curve25519(false).Point() require.False(t, IsSecp256k1Point(p)) require.True(t, IsSecp256k1Point(NewPoint())) } func TestCoordinates(t *testing.T) { x, y := Coordinates(NewPoint()) require.Equal(t, x, bigZero) require.Equal(t, y, bigZero) } func TestValidPublicKey(t *testing.T) { require.False(t, ValidPublicKey(NewPoint()), "zero is not a valid key") require.True(t, ValidPublicKey(NewPoint().Base())) } func TestGenerate(t *testing.T) { for { if ValidPublicKey(Generate(randomStreamPoint).Public) { break } } }