Add address derivation funcs
This commit is contained in:
parent
3853032c9c
commit
deed41d82f
|
@ -3,7 +3,4 @@ package solana
|
|||
const (
|
||||
// There are 1-billion lamports in one SOL.
|
||||
LAMPORTS_PER_SOL uint64 = 1000000000
|
||||
|
||||
// Maximum length of derived pubkey seed.
|
||||
MAX_SEED_LENGTH uint8 = 32
|
||||
)
|
||||
|
|
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ retract (
|
|||
|
||||
require (
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1
|
||||
github.com/AlekSi/pointer v1.1.0
|
||||
github.com/GeertJohan/go.rice v1.0.0
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59
|
||||
|
|
2
go.sum
2
go.sum
|
@ -27,6 +27,8 @@ contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/
|
|||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
|
|
174
keys.go
174
keys.go
|
@ -4,9 +4,13 @@ import (
|
|||
"crypto"
|
||||
"crypto/ed25519"
|
||||
crypto_rand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"filippo.io/edwards25519"
|
||||
"github.com/mr-tron/base58"
|
||||
)
|
||||
|
||||
|
@ -80,7 +84,7 @@ func (k PrivateKey) PublicKey() PublicKey {
|
|||
return publicKey
|
||||
}
|
||||
|
||||
type PublicKey [32]byte
|
||||
type PublicKey [PublicKeyLength]byte
|
||||
|
||||
func PublicKeyFromBytes(in []byte) (out PublicKey) {
|
||||
byteCount := len(in)
|
||||
|
@ -88,7 +92,7 @@ func PublicKeyFromBytes(in []byte) (out PublicKey) {
|
|||
return
|
||||
}
|
||||
|
||||
max := 32
|
||||
max := PublicKeyLength
|
||||
if byteCount < max {
|
||||
max = byteCount
|
||||
}
|
||||
|
@ -111,8 +115,8 @@ func PublicKeyFromBase58(in string) (out PublicKey, err error) {
|
|||
return out, fmt.Errorf("decode: %w", err)
|
||||
}
|
||||
|
||||
if len(val) != 32 {
|
||||
return out, fmt.Errorf("invalid length, expected 32, got %d", len(val))
|
||||
if len(val) != PublicKeyLength {
|
||||
return out, fmt.Errorf("invalid length, expected %v, got %d", PublicKeyLength, len(val))
|
||||
}
|
||||
|
||||
copy(out[:], val)
|
||||
|
@ -172,3 +176,165 @@ func (p PublicKey) IsZero() bool {
|
|||
func (p PublicKey) String() string {
|
||||
return base58.Encode(p[:])
|
||||
}
|
||||
|
||||
type PublicKeySlice []PublicKey
|
||||
|
||||
// UniqueAppend appends the provided pubkey only if it is not
|
||||
// already present in the slice.
|
||||
// Returns true when the provided pubkey wasn't already present.
|
||||
func (slice *PublicKeySlice) UniqueAppend(pubkey PublicKey) bool {
|
||||
if !slice.Has(pubkey) {
|
||||
slice.Append(pubkey)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (slice *PublicKeySlice) Append(pubkey PublicKey) {
|
||||
*slice = append(*slice, pubkey)
|
||||
}
|
||||
|
||||
func (slice PublicKeySlice) Has(pubkey PublicKey) bool {
|
||||
for _, key := range slice {
|
||||
if key.Equals(pubkey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var nativeProgramIDs = PublicKeySlice{
|
||||
BPFLoaderProgramID,
|
||||
BPFLoaderDeprecatedProgramID,
|
||||
FeatureProgramID,
|
||||
ConfigProgramID,
|
||||
StakeProgramID,
|
||||
VoteProgramID,
|
||||
Secp256k1ProgramID,
|
||||
SystemProgramID,
|
||||
SysVarClockPubkey,
|
||||
SysVarEpochSchedulePubkey,
|
||||
SysVarFeesPubkey,
|
||||
SysVarInstructionsPubkey,
|
||||
SysVarRecentBlockHashesPubkey,
|
||||
SysVarRentPubkey,
|
||||
SysVarRewardsPubkey,
|
||||
SysVarSlotHashesPubkey,
|
||||
SysVarSlotHistoryPubkey,
|
||||
SysVarStakeHistoryPubkey,
|
||||
}
|
||||
|
||||
// https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L372
|
||||
func isNativeProgramID(key PublicKey) bool {
|
||||
return nativeProgramIDs.Has(key)
|
||||
}
|
||||
|
||||
const (
|
||||
/// Number of bytes in a pubkey.
|
||||
PublicKeyLength = 32
|
||||
// Maximum length of derived pubkey seed.
|
||||
MaxSeedLength = 32
|
||||
// Maximum number of seeds.
|
||||
MaxSeeds = 16
|
||||
// // Maximum string length of a base58 encoded pubkey.
|
||||
// MaxBase58Length = 44
|
||||
)
|
||||
|
||||
// Ported from https://github.com/solana-labs/solana/blob/216983c50e0a618facc39aa07472ba6d23f1b33a/sdk/program/src/pubkey.rs#L159
|
||||
func CreateWithSeed(base PublicKey, seed string, owner PublicKey) (PublicKey, error) {
|
||||
if len(seed) > MaxSeedLength {
|
||||
return PublicKey{}, errors.New("Max seed length exceeded")
|
||||
}
|
||||
|
||||
// let owner = owner.as_ref();
|
||||
// if owner.len() >= PDA_MARKER.len() {
|
||||
// let slice = &owner[owner.len() - PDA_MARKER.len()..];
|
||||
// if slice == PDA_MARKER {
|
||||
// return Err(PubkeyError::IllegalOwner);
|
||||
// }
|
||||
// }
|
||||
|
||||
b := make([]byte, 0, 64+len(seed))
|
||||
b = append(b, base[:]...)
|
||||
b = append(b, seed[:]...)
|
||||
b = append(b, owner[:]...)
|
||||
hash := sha256.Sum256(b)
|
||||
return PublicKeyFromBytes(hash[:]), nil
|
||||
}
|
||||
|
||||
const PDA_MARKER = "ProgramDerivedAddress"
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
for _, seed := range seeds {
|
||||
if len(seed) > MaxSeedLength {
|
||||
return PublicKey{}, errors.New("Max seed length exceeded")
|
||||
}
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
buf = append(buf, programID[:]...)
|
||||
buf = append(buf, []byte(PDA_MARKER)...)
|
||||
hash := sha256.Sum256(buf)
|
||||
|
||||
_, err := new(edwards25519.Point).SetBytes(hash[:])
|
||||
isOnCurve := err == nil
|
||||
if isOnCurve {
|
||||
return PublicKey{}, errors.New("invalid seeds; address must fall off the curve")
|
||||
}
|
||||
|
||||
return PublicKeyFromBytes(hash[:]), nil
|
||||
}
|
||||
|
||||
// Find a valid program address and its corresponding bump seed.
|
||||
func FindProgramAddress(seed [][]byte, programID PublicKey) (PublicKey, uint8, error) {
|
||||
var address PublicKey
|
||||
var err error
|
||||
bumpSeed := uint8(math.MaxUint8)
|
||||
for bumpSeed != 0 {
|
||||
address, err = CreateProgramAddress(append(seed, []byte{byte(bumpSeed)}), programID)
|
||||
if err == nil {
|
||||
return address, bumpSeed, nil
|
||||
}
|
||||
bumpSeed--
|
||||
}
|
||||
return PublicKey{}, bumpSeed, errors.New("unable to find a valid program address")
|
||||
}
|
||||
|
||||
func FindAssociatedTokenAddress(
|
||||
walletAddress PublicKey,
|
||||
splTokenMintAddress PublicKey,
|
||||
) (PublicKey, uint8, error) {
|
||||
return findAssociatedTokenAddressAndBumpSeed(
|
||||
walletAddress,
|
||||
splTokenMintAddress,
|
||||
SPLAssociatedTokenAccountProgramID,
|
||||
)
|
||||
}
|
||||
|
||||
func findAssociatedTokenAddressAndBumpSeed(
|
||||
walletAddress PublicKey,
|
||||
splTokenMintAddress PublicKey,
|
||||
programID PublicKey,
|
||||
) (PublicKey, uint8, error) {
|
||||
return FindProgramAddress([][]byte{
|
||||
walletAddress[:],
|
||||
SPLTokenProgramID[:],
|
||||
splTokenMintAddress[:],
|
||||
},
|
||||
programID,
|
||||
)
|
||||
}
|
||||
|
|
103
keys_test.go
103
keys_test.go
|
@ -140,3 +140,106 @@ func TestPublicKey_MarshalText(t *testing.T) {
|
|||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
func TestPublicKeySlice(t *testing.T) {
|
||||
slice := make(PublicKeySlice, 0)
|
||||
require.False(t, slice.Has(BPFLoaderProgramID))
|
||||
|
||||
slice.Append(BPFLoaderProgramID)
|
||||
require.True(t, slice.Has(BPFLoaderProgramID))
|
||||
require.Len(t, slice, 1)
|
||||
|
||||
slice.UniqueAppend(BPFLoaderProgramID)
|
||||
require.Len(t, slice, 1)
|
||||
slice.Append(ConfigProgramID)
|
||||
require.Len(t, slice, 2)
|
||||
require.True(t, slice.Has(ConfigProgramID))
|
||||
}
|
||||
|
||||
func TestIsNativeProgramID(t *testing.T) {
|
||||
require.True(t, isNativeProgramID(ConfigProgramID))
|
||||
}
|
||||
|
||||
func TestCreateWithSeed(t *testing.T) {
|
||||
{
|
||||
got, err := CreateWithSeed(PublicKey{}, "limber chicken: 4/45", PublicKey{})
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Equals(MustPublicKeyFromBase58("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProgramAddress(t *testing.T) {
|
||||
program_id := MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
public_key := MustPublicKeyFromBase58("SeedPubey1111111111111111111111111111111111")
|
||||
|
||||
{
|
||||
got, err := CreateProgramAddress([][]byte{
|
||||
{},
|
||||
{1},
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Equals(MustPublicKeyFromBase58("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe")))
|
||||
}
|
||||
|
||||
{
|
||||
got, err := CreateProgramAddress([][]byte{
|
||||
[]byte("☉"),
|
||||
{0},
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Equals(MustPublicKeyFromBase58("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19")))
|
||||
}
|
||||
|
||||
{
|
||||
got, err := CreateProgramAddress([][]byte{
|
||||
[]byte("Talking"),
|
||||
[]byte("Squirrels"),
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Equals(MustPublicKeyFromBase58("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk")))
|
||||
}
|
||||
|
||||
{
|
||||
got, err := CreateProgramAddress([][]byte{
|
||||
public_key[:],
|
||||
{1},
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, got.Equals(MustPublicKeyFromBase58("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL")))
|
||||
}
|
||||
}
|
||||
|
||||
// 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++ {
|
||||
|
||||
program_id := NewAccount().PrivateKey.PublicKey()
|
||||
address, bump_seed, err := FindProgramAddress(
|
||||
[][]byte{
|
||||
[]byte("Lil'"),
|
||||
[]byte("Bits"),
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := CreateProgramAddress(
|
||||
[][]byte{
|
||||
[]byte("Lil'"),
|
||||
[]byte("Bits"),
|
||||
[]byte{bump_seed},
|
||||
},
|
||||
program_id,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, address, got)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package solana
|
||||
|
||||
var (
|
||||
// Create new accounts, allocate account data, assign accounts to owning programs,
|
||||
// transfer lamports from System Program owned accounts and pay transacation fees.
|
||||
SystemProgramID = MustPublicKeyFromBase58("11111111111111111111111111111111")
|
||||
|
||||
// Add configuration data to the chain and the list of public keys that are permitted to modify it.
|
||||
ConfigProgramID = MustPublicKeyFromBase58("Config1111111111111111111111111111111111111")
|
||||
|
||||
// Create and manage accounts representing stake and rewards for delegations to validators.
|
||||
StakeProgramID = MustPublicKeyFromBase58("Stake11111111111111111111111111111111111111")
|
||||
|
||||
// Create and manage accounts that track validator voting state and rewards.
|
||||
VoteProgramID = MustPublicKeyFromBase58("Vote111111111111111111111111111111111111111")
|
||||
|
||||
BPFLoaderDeprecatedProgramID = MustPublicKeyFromBase58("BPFLoader1111111111111111111111111111111111")
|
||||
// Deploys, upgrades, and executes programs on the chain.
|
||||
BPFLoaderProgramID = MustPublicKeyFromBase58("BPFLoader2111111111111111111111111111111111")
|
||||
BPFLoaderUpgradeableProgramID = MustPublicKeyFromBase58("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
|
||||
// Verify secp256k1 public key recovery operations (ecrecover).
|
||||
Secp256k1ProgramID = MustPublicKeyFromBase58("KeccakSecp256k11111111111111111111111111111")
|
||||
|
||||
FeatureProgramID = MustPublicKeyFromBase58("Feature111111111111111111111111111111111111")
|
||||
)
|
||||
|
||||
// SPL:
|
||||
var (
|
||||
// A Token program on the Solana blockchain.
|
||||
// This program defines a common implementation for Fungible and Non Fungible tokens.
|
||||
SPLTokenProgramID = MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
|
||||
// A Uniswap-like exchange for the Token program on the Solana blockchain,
|
||||
// implementing multiple automated market maker (AMM) curves.
|
||||
SPLTokenSwapProgramID = MustPublicKeyFromBase58("SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8")
|
||||
SPLTokenSwapFeeOwner = MustPublicKeyFromBase58("HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN")
|
||||
|
||||
// A lending protocol for the Token program on the Solana blockchain inspired by Aave and Compound.
|
||||
SPLTokenLendingProgramID = MustPublicKeyFromBase58("LendZqTs8gn5CTSJU1jWKhKuVpjJGom45nnwPb2AMTi")
|
||||
|
||||
// This program defines the convention and provides the mechanism for mapping
|
||||
// the user's wallet address to the associated token accounts they hold.
|
||||
SPLAssociatedTokenAccountProgramID = MustPublicKeyFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||
|
||||
// The Memo program is a simple program that validates a string of UTF-8 encoded characters
|
||||
// and verifies that any accounts provided are signers of the transaction.
|
||||
// The program also logs the memo, as well as any verified signer addresses,
|
||||
// to the transaction log, so that anyone can easily observe memos
|
||||
// and know they were approved by zero or more addresses
|
||||
// by inspecting the transaction log from a trusted provider.
|
||||
SPLMemoProgramID = MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
||||
)
|
|
@ -35,6 +35,9 @@ var (
|
|||
// The Rent burn percentage is modified by manual feature activation.
|
||||
SysVarRentPubkey = MustPublicKeyFromBase58("SysvarRent111111111111111111111111111111111")
|
||||
|
||||
//
|
||||
SysVarRewardsPubkey = MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111")
|
||||
|
||||
// The SlotHashes sysvar contains the most recent hashes of the slot's parent banks.
|
||||
// It is updated every slot.
|
||||
SysVarSlotHashesPubkey = MustPublicKeyFromBase58("SysvarS1otHashes111111111111111111111111111")
|
||||
|
@ -45,7 +48,4 @@ var (
|
|||
// The StakeHistory sysvar contains the history of cluster-wide stake activations and de-activations per epoch.
|
||||
// It is updated at the start of every epoch.
|
||||
SysVarStakeHistoryPubkey = MustPublicKeyFromBase58("SysvarStakeHistory1111111111111111111111111")
|
||||
|
||||
//
|
||||
SysVarRewardsPubkey = MustPublicKeyFromBase58("SysvarRewards111111111111111111111111111111")
|
||||
)
|
||||
|
|
|
@ -36,32 +36,6 @@ func TransactionPayer(payer PublicKey) TransactionOption {
|
|||
return transactionOptionFunc(func(opts *transactionOptions) { opts.payer = payer })
|
||||
}
|
||||
|
||||
type pubkeySlice []PublicKey
|
||||
|
||||
// uniqueAppend appends the provided pubkey only if it is not
|
||||
// already present in the slice.
|
||||
// Returns true when the provided pubkey wasn't already present.
|
||||
func (slice *pubkeySlice) uniqueAppend(pubkey PublicKey) bool {
|
||||
if !slice.has(pubkey) {
|
||||
slice.append(pubkey)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (slice *pubkeySlice) append(pubkey PublicKey) {
|
||||
*slice = append(*slice, pubkey)
|
||||
}
|
||||
|
||||
func (slice *pubkeySlice) has(pubkey PublicKey) bool {
|
||||
for _, key := range *slice {
|
||||
if key.Equals(pubkey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var debugNewTransaction = false
|
||||
|
||||
type TransactionBuilder struct {
|
||||
|
@ -134,13 +108,13 @@ func NewTransaction(instructions []Instruction, recentBlockHash Hash, opts ...Tr
|
|||
}
|
||||
}
|
||||
|
||||
programIDs := make(pubkeySlice, 0)
|
||||
programIDs := make(PublicKeySlice, 0)
|
||||
accounts := []*AccountMeta{}
|
||||
for _, instruction := range instructions {
|
||||
for _, key := range instruction.Accounts() {
|
||||
accounts = append(accounts, key)
|
||||
}
|
||||
programIDs.uniqueAppend(instruction.ProgramID())
|
||||
programIDs.UniqueAppend(instruction.ProgramID())
|
||||
}
|
||||
|
||||
// Add programID to the account list
|
||||
|
|
Loading…
Reference in New Issue