Skip to content

Commit

Permalink
CreateProgramAddress: remove isNativeProgramID check (as was remo…
Browse files Browse the repository at this point in the history
…ved in the rust implementation).

- Make `ErrMaxSeedLengthExceeded` public.
- Add `IsOnCurve` func and method on pubkey.
- Add more `CreateProgramAddress` tests
  • Loading branch information
gagliardetto committed Jan 6, 2022
1 parent 7fd68cb commit 16c7172
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 10 deletions.
26 changes: 17 additions & 9 deletions keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ func (p PublicKey) Bytes() []byte {
return []byte(p[:])
}

// Check if a `Pubkey` is on the ed25519 curve.
func (p PublicKey) IsOnCurve() bool {
return IsOnCurve(p[:])
}

var zeroPublicKey = PublicKey{}

// IsZero returns whether the public key is zero.
Expand Down Expand Up @@ -281,23 +286,21 @@ func CreateWithSeed(base PublicKey, seed string, owner PublicKey) (PublicKey, er

const PDA_MARKER = "ProgramDerivedAddress"

var ErrMaxSeedLengthExceeded = errors.New("Max seed length exceeded")

// Create a program address.
// Ported from https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L204
func CreateProgramAddress(seeds [][]byte, programID PublicKey) (PublicKey, error) {
if len(seeds) > MaxSeeds {
return PublicKey{}, errors.New("Max seed length exceeded")
return PublicKey{}, ErrMaxSeedLengthExceeded
}

for _, seed := range seeds {
if len(seed) > MaxSeedLength {
return PublicKey{}, errors.New("Max seed length exceeded")
return PublicKey{}, ErrMaxSeedLengthExceeded
}
}

if isNativeProgramID(programID) {
return PublicKey{}, fmt.Errorf("illegal owner: %s is a native program", programID)
}

buf := []byte{}
for _, seed := range seeds {
buf = append(buf, seed...)
Expand All @@ -307,15 +310,20 @@ func CreateProgramAddress(seeds [][]byte, programID PublicKey) (PublicKey, error
buf = append(buf, []byte(PDA_MARKER)...)
hash := sha256.Sum256(buf)

_, err := new(edwards25519.Point).SetBytes(hash[:])
isOnCurve := err == nil
if isOnCurve {
if IsOnCurve(hash[:]) {
return PublicKey{}, errors.New("invalid seeds; address must fall off the curve")
}

return PublicKeyFromBytes(hash[:]), nil
}

// Check if the provided `b` is on the ed25519 curve.
func IsOnCurve(b []byte) bool {
_, err := new(edwards25519.Point).SetBytes(b)
isOnCurve := err == nil
return isOnCurve
}

// Find a valid program address and its corresponding bump seed.
func FindProgramAddress(seed [][]byte, programID PublicKey) (PublicKey, uint8, error) {
var address PublicKey
Expand Down
127 changes: 126 additions & 1 deletion keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package solana

import (
"encoding/binary"
"encoding/hex"
"errors"
"testing"
Expand Down Expand Up @@ -185,7 +186,8 @@ func TestCreateWithSeed(t *testing.T) {
}
}

func TestCreateProgramAddress(t *testing.T) {
func TestCreateProgramAddressFromRust(t *testing.T) {
// Ported from https://github.com/solana-labs/solana/blob/f32216588dfdbc7a7160c26331ce657a90f95ae7/sdk/program/src/pubkey.rs#L636
program_id := MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111")
public_key := MustPublicKeyFromBase58("SeedPubey1111111111111111111111111111111111")

Expand Down Expand Up @@ -234,6 +236,129 @@ func TestCreateProgramAddress(t *testing.T) {
}
}

func TestCreateProgramAddressFromTypescript(t *testing.T) {
t.Run(
"createProgramAddress",
// Ported from https://github.com/solana-labs/solana-web3.js/blob/168d5e088edd48f9f0c1a877e888592ca4cfdf38/test/publickey.test.ts#L113
func(t *testing.T) {
program_id := MustPublicKeyFromBase58("BPFLoader1111111111111111111111111111111111")
public_key := MustPublicKeyFromBase58("SeedPubey1111111111111111111111111111111111")

{
programAddress, err := CreateProgramAddress([][]byte{
[]byte(""),
{1},
},
program_id,
)
require.NoError(t, err)
require.True(t, programAddress.Equals(MustPublicKeyFromBase58("3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT")))
}
{
programAddress, err := CreateProgramAddress([][]byte{
[]byte("☉"),
},
program_id,
)
require.NoError(t, err)
require.True(t, programAddress.Equals(MustPublicKeyFromBase58("7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7")))
}
{
programAddress, err := CreateProgramAddress([][]byte{
[]byte("Talking"),
[]byte("Squirrels"),
},
program_id,
)
require.NoError(t, err)
require.True(t, programAddress.Equals(MustPublicKeyFromBase58("HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds")))
}
{
programAddress, err := CreateProgramAddress([][]byte{
public_key[:],
},
program_id,
)
require.NoError(t, err)
require.True(t, programAddress.Equals(MustPublicKeyFromBase58("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K")))

{
programAddress2, err := CreateProgramAddress([][]byte{
[]byte("Talking"),
},
program_id,
)
require.NoError(t, err)
require.False(t, programAddress.Equals(programAddress2))
}
}
{
_, err := CreateProgramAddress([][]byte{
make([]byte, MaxSeedLength+1),
},
program_id,
)
require.EqualError(t, err, ErrMaxSeedLengthExceeded.Error())
}
{
bn := make([]byte, 8)
binary.LittleEndian.PutUint64(bn, 2)
programAddress, err := CreateProgramAddress([][]byte{
MustPublicKeyFromBase58("H4snTKK9adiU15gP22ErfZYtro3aqR9BTMXiH3AwiUTQ").Bytes(),
bn,
},
MustPublicKeyFromBase58("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn"),
)
require.NoError(t, err)
require.True(t, programAddress.Equals(MustPublicKeyFromBase58("12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA")))
}
},
)

t.Run(
"findProgramAddress",
// Ported from https://github.com/solana-labs/solana-web3.js/blob/168d5e088edd48f9f0c1a877e888592ca4cfdf38/test/publickey.test.ts#L194
func(t *testing.T) {
programId := MustPublicKeyFromBase58("BPFLoader1111111111111111111111111111111111")

programAddress, nonce, err := FindProgramAddress(
[][]byte{
[]byte(""),
},
programId,
)
require.NoError(t, err)

{
got, err := CreateProgramAddress([][]byte{
[]byte(""),
{nonce},
},
programId,
)
require.NoError(t, err)
require.True(t, programAddress.Equals(got))
}
},
)

t.Run(
"isOnCurve",
// Ported from https://github.com/solana-labs/solana-web3.js/blob/168d5e088edd48f9f0c1a877e888592ca4cfdf38/test/publickey.test.ts#L212
func(t *testing.T) {
onCurve := NewWallet().PublicKey()
require.True(t, onCurve.IsOnCurve())

// A program address, yanked from one of the above tests. This is a pretty
// poor test vector since it was created by the same code it is testing.
// Unfortunately, I've been unable to find a golden negative example input
// for curve25519 point decompression :/
offCurve := MustPublicKeyFromBase58("12rqwuEgBYiGhBrDJStCiqEtzQpTTiZbh7teNVLuYcFA")
require.False(t, offCurve.IsOnCurve())
},
)
}

// https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L590
func TestFindProgramAddress(t *testing.T) {
for i := 0; i < 1_000; i++ {
Expand Down

0 comments on commit 16c7172

Please sign in to comment.