341 lines
7.6 KiB
Go
341 lines
7.6 KiB
Go
package solana
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
crypto_rand "crypto/rand"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math"
|
|
|
|
"filippo.io/edwards25519"
|
|
"github.com/mr-tron/base58"
|
|
)
|
|
|
|
type PrivateKey []byte
|
|
|
|
func MustPrivateKeyFromBase58(in string) PrivateKey {
|
|
out, err := PrivateKeyFromBase58(in)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func PrivateKeyFromBase58(privkey string) (PrivateKey, error) {
|
|
res, err := base58.Decode(privkey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func PrivateKeyFromSolanaKeygenFile(file string) (PrivateKey, error) {
|
|
content, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read keygen file: %w", err)
|
|
}
|
|
|
|
var values []byte
|
|
err = json.Unmarshal(content, &values)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode keygen file: %w", err)
|
|
}
|
|
|
|
return PrivateKey([]byte(values)), nil
|
|
}
|
|
|
|
func (k PrivateKey) String() string {
|
|
return base58.Encode(k)
|
|
}
|
|
|
|
func NewRandomPrivateKey() (PrivateKey, error) {
|
|
pub, priv, err := ed25519.GenerateKey(crypto_rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var publicKey PublicKey
|
|
copy(publicKey[:], pub)
|
|
return PrivateKey(priv), nil
|
|
}
|
|
|
|
func (k PrivateKey) Sign(payload []byte) (Signature, error) {
|
|
p := ed25519.PrivateKey(k)
|
|
signData, err := p.Sign(crypto_rand.Reader, payload, crypto.Hash(0))
|
|
if err != nil {
|
|
return Signature{}, err
|
|
}
|
|
|
|
var signature Signature
|
|
copy(signature[:], signData)
|
|
|
|
return signature, err
|
|
}
|
|
|
|
func (k PrivateKey) PublicKey() PublicKey {
|
|
p := ed25519.PrivateKey(k)
|
|
pub := p.Public().(ed25519.PublicKey)
|
|
|
|
var publicKey PublicKey
|
|
copy(publicKey[:], pub)
|
|
|
|
return publicKey
|
|
}
|
|
|
|
type PublicKey [PublicKeyLength]byte
|
|
|
|
func PublicKeyFromBytes(in []byte) (out PublicKey) {
|
|
byteCount := len(in)
|
|
if byteCount == 0 {
|
|
return
|
|
}
|
|
|
|
max := PublicKeyLength
|
|
if byteCount < max {
|
|
max = byteCount
|
|
}
|
|
|
|
copy(out[:], in[0:max])
|
|
return
|
|
}
|
|
|
|
func MustPublicKeyFromBase58(in string) PublicKey {
|
|
out, err := PublicKeyFromBase58(in)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func PublicKeyFromBase58(in string) (out PublicKey, err error) {
|
|
val, err := base58.Decode(in)
|
|
if err != nil {
|
|
return out, fmt.Errorf("decode: %w", err)
|
|
}
|
|
|
|
if len(val) != PublicKeyLength {
|
|
return out, fmt.Errorf("invalid length, expected %v, got %d", PublicKeyLength, len(val))
|
|
}
|
|
|
|
copy(out[:], val)
|
|
return
|
|
}
|
|
|
|
func (p PublicKey) MarshalText() ([]byte, error) {
|
|
return []byte(base58.Encode(p[:])), nil
|
|
}
|
|
|
|
func (p *PublicKey) UnmarshalText(data []byte) (err error) {
|
|
*p, err = PublicKeyFromBase58(string(data))
|
|
if err != nil {
|
|
return fmt.Errorf("invalid public key %q: %w", data, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p PublicKey) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(base58.Encode(p[:]))
|
|
}
|
|
|
|
func (p *PublicKey) UnmarshalJSON(data []byte) (err error) {
|
|
var s string
|
|
if err := json.Unmarshal(data, &s); err != nil {
|
|
return err
|
|
}
|
|
|
|
*p, err = PublicKeyFromBase58(s)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid public key %q: %w", s, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p PublicKey) Equals(pb PublicKey) bool {
|
|
return p == pb
|
|
}
|
|
|
|
// ToPointer returns a pointer to the pubkey.
|
|
func (p PublicKey) ToPointer() *PublicKey {
|
|
return &p
|
|
}
|
|
|
|
func (p PublicKey) Bytes() []byte {
|
|
return []byte(p[:])
|
|
}
|
|
|
|
var zeroPublicKey = PublicKey{}
|
|
|
|
// IsZero returns whether the public key is zero.
|
|
// NOTE: the System Program public key is also zero.
|
|
func (p PublicKey) IsZero() bool {
|
|
return p == zeroPublicKey
|
|
}
|
|
|
|
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[:],
|
|
TokenProgramID[:],
|
|
splTokenMintAddress[:],
|
|
},
|
|
programID,
|
|
)
|
|
}
|