From 29f1ed1c0c306668d53a74085c32be2cabf9e968 Mon Sep 17 00:00:00 2001 From: gagliardetto Date: Sun, 3 Nov 2024 21:07:00 +0100 Subject: [PATCH] Fix missing key checks --- go.mod | 1 - go.sum | 12 --------- keys.go | 67 ++++++++++++++++++++++++++++++++++++++++--------- nativetypes.go | 22 ++++++++++++++++ rpc/getBlock.go | 9 ++++--- rpc/types.go | 19 +++++++------- transaction.go | 8 ++---- 7 files changed, 93 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index fbf2f2e1..44b4c460 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/spf13/cast v1.3.0 // indirect github.com/spf13/jwalterweatherman v1.0.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect - github.com/tidwall/pretty v1.2.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect diff --git a/go.sum b/go.sum index c997b0ec..d26f0dbc 100644 --- a/go.sum +++ b/go.sum @@ -309,16 +309,11 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= @@ -326,8 +321,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= -go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws= go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -415,7 +408,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -467,14 +459,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -483,7 +472,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= diff --git a/keys.go b/keys.go index e94ca6fb..d11c4cde 100644 --- a/keys.go +++ b/keys.go @@ -37,6 +37,17 @@ import ( type PrivateKey []byte +// IsValid returns whether the private key is valid. +func (k PrivateKey) IsValid() bool { + _, err := ValidatePrivateKey(k) + return err == nil +} + +func (k PrivateKey) Validate() error { + _, err := ValidatePrivateKey(k) + return err +} + func MustPrivateKeyFromBase58(in string) PrivateKey { out, err := PrivateKeyFromBase58(in) if err != nil { @@ -50,22 +61,41 @@ func PrivateKeyFromBase58(privkey string) (PrivateKey, error) { if err != nil { return nil, err } + // validate + if _, err := ValidatePrivateKey(res); err != nil { + return nil, err + } return res, nil } +func ValidatePrivateKey(b []byte) (bool, error) { + if len(b) != ed25519.PrivateKeySize { + return false, fmt.Errorf("invalid private key size, expected %v, got %d", ed25519.PrivateKeySize, len(b)) + } + // check if the public key is on the ed25519 curve + pub := ed25519.PrivateKey(b).Public().(ed25519.PublicKey) + if !IsOnCurve(pub) { + return false, errors.New("the corresponding public key is not on the ed25519 curve") + } + return true, 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) + var privateKeyBytes []byte + err = json.Unmarshal(content, &privateKeyBytes) if err != nil { return nil, fmt.Errorf("decode keygen file: %w", err) } - return PrivateKey([]byte(values)), nil + if _, err := ValidatePrivateKey(privateKeyBytes); err != nil { + return nil, fmt.Errorf("invalid private key: %w", err) + } + return PrivateKey(privateKeyBytes), nil } func (k PrivateKey) String() string { @@ -83,6 +113,9 @@ func NewRandomPrivateKey() (PrivateKey, error) { } func (k PrivateKey) Sign(payload []byte) (Signature, error) { + if err := k.Validate(); err != nil { + return Signature{}, err + } p := ed25519.PrivateKey(k) signData, err := p.Sign(crypto_rand.Reader, payload, crypto.Hash(0)) if err != nil { @@ -96,6 +129,10 @@ func (k PrivateKey) Sign(payload []byte) (Signature, error) { } func (k PrivateKey) PublicKey() PublicKey { + if err := k.Validate(); err != nil { + panic(err) + } + p := ed25519.PrivateKey(k) pub := p.Public().(ed25519.PublicKey) @@ -115,18 +152,16 @@ func (p PublicKey) Verify(message []byte, signature Signature) bool { type PublicKey [PublicKeyLength]byte +// PublicKeyFromBytes creates a PublicKey from a byte slice that must be 32 bytes long. +// NOTE: it will accept on- and off-curve pubkeys. func PublicKeyFromBytes(in []byte) (out PublicKey) { byteCount := len(in) - if byteCount == 0 { - return - } - max := PublicKeyLength - if byteCount < max { - max = byteCount + if byteCount != PublicKeyLength { + panic(fmt.Errorf("invalid public key size, expected %v, got %d", PublicKeyLength, byteCount)) } - copy(out[:], in[0:max]) + copy(out[:], in) return } @@ -143,6 +178,8 @@ func MustPublicKeyFromBase58(in string) PublicKey { return out } +// PublicKeyFromBase58 creates a PublicKey from a base58 encoded string. +// NOTE: it will accept on- and off-curve pubkeys. func PublicKeyFromBase58(in string) (out PublicKey, err error) { val, err := base58.Decode(in) if err != nil { @@ -551,15 +588,18 @@ func isNativeProgramID(key PublicKey) bool { } const ( - /// Number of bytes in a pubkey. + // Number of bytes in a pubkey. PublicKeyLength = 32 // Maximum length of derived pubkey seed. MaxSeedLength = 32 // Maximum number of seeds. MaxSeeds = 16 - /// Number of bytes in a signature. + // Number of bytes in a signature. SignatureLength = 64 + // Number of bytes in a private key. + PrivateKeyLength = ed25519.PrivateKeySize + // // Maximum string length of a base58 encoded pubkey. // MaxBase58Length = 44 ) @@ -621,6 +661,9 @@ func CreateProgramAddress(seeds [][]byte, programID PublicKey) (PublicKey, error // Check if the provided `b` is on the ed25519 curve. func IsOnCurve(b []byte) bool { + if len(b) != ed25519.PublicKeySize { + return false + } _, err := new(edwards25519.Point).SetBytes(b) isOnCurve := err == nil return isOnCurve diff --git a/nativetypes.go b/nativetypes.go index 45570d31..9fc04f27 100644 --- a/nativetypes.go +++ b/nativetypes.go @@ -202,6 +202,28 @@ func (p Signature) String() string { return base58.Encode(p[:]) } +type Base64 []byte + +func (t Base64) MarshalJSON() ([]byte, error) { + return json.Marshal(base64.StdEncoding.EncodeToString(t)) +} + +func (t *Base64) UnmarshalJSON(data []byte) (err error) { + var s string + err = json.Unmarshal(data, &s) + if err != nil { + return + } + + if s == "" { + *t = []byte{} + return nil + } + + *t, err = base64.StdEncoding.DecodeString(s) + return +} + type Base58 []byte func (t Base58) MarshalJSON() ([]byte, error) { diff --git a/rpc/getBlock.go b/rpc/getBlock.go index 760656d3..2bbeea05 100644 --- a/rpc/getBlock.go +++ b/rpc/getBlock.go @@ -63,6 +63,11 @@ type GetBlockOpts struct { MaxSupportedTransactionVersion *uint64 } +var ( + MaxSupportedTransactionVersion0 uint64 = 0 + MaxSupportedTransactionVersion1 uint64 = 1 +) + // GetBlock returns identity and transaction information about a confirmed block in the ledger. func (cl *Client) GetBlock( ctx context.Context, @@ -84,7 +89,6 @@ func (cl *Client) GetBlockWithOpts( slot uint64, opts *GetBlockOpts, ) (out *GetBlockResult, err error) { - obj := M{ "encoding": solana.EncodingBase64, } @@ -121,7 +125,6 @@ func (cl *Client) GetBlockWithOpts( params := []interface{}{slot, obj} err = cl.rpcClient.CallForInto(ctx, &out, "getBlock", params) - if err != nil { return nil, err } @@ -167,7 +170,6 @@ func (cl *Client) GetParsedBlockWithOpts( slot uint64, opts *GetBlockOpts, ) (out *GetParsedBlockResult, err error) { - obj := M{ "encoding": solana.EncodingJSONParsed, } @@ -190,7 +192,6 @@ func (cl *Client) GetParsedBlockWithOpts( params := []interface{}{slot, obj} err = cl.rpcClient.CallForInto(ctx, &out, "getBlock", params) - if err != nil { return nil, err } diff --git a/rpc/types.go b/rpc/types.go index 611985bf..d65ad0a9 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -216,24 +216,23 @@ type TransactionMeta struct { LoadedAddresses LoadedAddresses `json:"loadedAddresses"` + ReturnData ReturnData `json:"returnData"` + ComputeUnitsConsumed *uint64 `json:"computeUnitsConsumed"` } +type ReturnData struct { + ProgramId solana.PublicKey `json:"programId"` + Data solana.Data `json:"data"` +} + type InnerInstruction struct { // TODO: == int64 ??? // Index of the transaction instruction from which the inner instruction(s) originated Index uint16 `json:"index"` // Ordered list of inner program instructions that were invoked during a single transaction instruction. - Instructions []CompiledInnerInstruction `json:"instructions"` -} - -type CompiledInnerInstruction struct { - solana.CompiledInstruction - - // Invocation stack height of this instruction. Instruction stack height - // starts at 1 for transaction instructions. - StackHeight uint8 `json:"stackHeight"` + Instructions []solana.CompiledInstruction `json:"instructions"` } // Ok interface{} `json:"Ok"` // Transaction was successful @@ -511,7 +510,7 @@ type ParsedInstruction struct { Parsed *InstructionInfoEnvelope `json:"parsed,omitempty"` Data solana.Base58 `json:"data,omitempty"` Accounts []solana.PublicKey `json:"accounts,omitempty"` - StackHeight int `json:"stackHeight"` + StackHeight int64 `json:"stackHeight"` } type InstructionInfoEnvelope struct { diff --git a/transaction.go b/transaction.go index cab9bffa..32b9937c 100644 --- a/transaction.go +++ b/transaction.go @@ -141,7 +141,7 @@ type CompiledInstruction struct { Data Base58 `json:"data"` // - StackHeight int `json:"stackHeight"` + StackHeight int64 `json:"stackHeight"` } type compiledInstruction struct { @@ -159,11 +159,7 @@ type compiledInstruction struct { Data Base58 `json:"data"` // - StackHeight int `json:"stackHeight"` -} - -func (ci *CompiledInstruction) MarshalJSON() ([]byte, error) { - return json.Marshal(ci) + StackHeight int64 `json:"stackHeight"` } func (ci *CompiledInstruction) UnmarshalJSON(data []byte) error {