Skip to content

steemit/steemutil

Repository files navigation

steemutil

This is underconstruction. The methods will be change in the future !!!!

Package steemutil provides Steem blockchain-specific convenience functions and types for Go applications.

Overview

steemutil is a comprehensive Go library that provides low-level utilities for interacting with the Steem blockchain. It includes cryptographic functions, transaction handling, protocol definitions, and RPC authentication capabilities.

Features

  • 🔐 RPC Authentication: SignedCall support for authenticated API requests
  • 🔑 Cryptographic Operations: Key generation, signing, and verification
  • 📦 Transaction Handling: Transaction creation, signing, and serialization
  • 🌐 Protocol Support: Complete Steem protocol operation definitions
  • 🛡️ Security: Built-in replay protection and signature validation
  • ⚡ Performance: Optimized for high-performance applications

Installation

go get github.com/steemit/steemutil

Quick Start

Basic Key Operations

package main

import (
    "fmt"
    "github.com/steemit/steemutil/auth"
)

func main() {
    // Generate private key from account and password
    privateKey, err := auth.ToWif("username", "password", "active")
    if err != nil {
        panic(err)
    }
    
    // Convert to public key
    publicKey, err := auth.WifToPublic(privateKey)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Private Key: %s\n", privateKey)
    fmt.Printf("Public Key: %s\n", publicKey)
}

SignedCall RPC Authentication

package main

import (
    "fmt"
    "github.com/steemit/steemutil/rpc"
)

func main() {
    // Create RPC request
    request := &rpc.RpcRequest{
        Method: "condenser_api.get_accounts",
        Params: []interface{}{[]string{"username"}},
        ID:     1,
    }
    
    // Sign the request
    signedRequest, err := rpc.Sign(request, "username", []string{"private-key-wif"})
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Signed Request: %+v\n", signedRequest)
}

Transaction Operations

package main

import (
    "github.com/steemit/steemutil/protocol"
    "github.com/steemit/steemutil/transaction"
)

func main() {
    // Create a vote operation
    voteOp := &protocol.VoteOperation{
        Voter:    "voter",
        Author:   "author", 
        Permlink: "permlink",
        Weight:   10000,
    }
    
    // Create transaction
    tx := &transaction.Transaction{}
    tx.PushOperation(voteOp)
    
    // Sign transaction
    signedTx := transaction.NewSignedTransaction(tx)
    // ... add signing logic
}

Package Structure

Core Packages

  • auth/ - Authentication and key management utilities
  • rpc/ - RPC authentication and signed call support
  • protocol/ - Steem protocol definitions and operations
  • transaction/ - Transaction creation and signing
  • wif/ - Wallet Import Format key handling
  • encoder/ - Binary serialization utilities

Protocol Support

  • protocol/api/ - API method definitions
  • protocol/broadcast/ - Broadcast operation definitions
  • consts/ - Protocol constants and chain parameters
  • jsonrpc2/ - JSON-RPC client implementation

RPC Authentication (SignedCall)

The rpc package provides full support for Steem's signed RPC calls, compatible with steem-js:

Features

  • 🔒 Cryptographic Signing: ECDSA signature generation and verification
  • 🛡️ Security: Nonce generation, timestamp expiration (60s), replay protection
  • 🔄 Compatibility: 100% compatible with steem-js signedCall format
  • 🔑 Multi-Key Support: Sign with multiple private keys simultaneously
  • ⏰ Time Validation: Automatic signature expiration handling

Security Features

  • Unique Nonces: 8-byte random nonce for each request
  • Timestamp Expiration: Signatures expire after 60 seconds
  • Cross-Protocol Protection: Protocol-specific signing constant K
  • No Key Transmission: Private keys never leave your application

Example Usage

// Sign a request
request := &rpc.RpcRequest{
    Method: "condenser_api.get_account_history",
    Params: []interface{}{"username", -1, 100},
    ID:     1,
}

signedRequest, err := rpc.Sign(request, "username", []string{"active-key-wif"})
if err != nil {
    return err
}

// Validate a signed request
params, err := rpc.Validate(signedRequest, rpc.DefaultVerifyFunc)
if err != nil {
    return err
}

Cryptographic Operations

Key Generation

// Generate keys for multiple roles
roles := []string{"active", "posting", "owner", "memo"}
keys, err := auth.GetPrivateKeys("username", "password", roles)

// Generate single key
activeKey, err := auth.ToWif("username", "password", "active")

Message Signing

// Sign arbitrary messages
privateKey := &wif.PrivateKey{}
privateKey.FromWif("private-key-wif")

message := []byte("Hello, Steem!")
signature, err := privateKey.SignSha256(message)

// Verify signatures
publicKey := &wif.PublicKey{}
publicKey.FromWif("private-key-wif") // Derives public key
isValid := publicKey.VerifySha256(message, signature)

Transaction Handling

Creating Transactions

// Create operations
voteOp := &protocol.VoteOperation{
    Voter:    "voter",
    Author:   "author",
    Permlink: "post-permlink", 
    Weight:   10000,
}

transferOp := &protocol.TransferOperation{
    From:   "sender",
    To:     "receiver",
    Amount: "1.000 STEEM",
    Memo:   "Transfer memo",
}

// Build transaction
tx := &transaction.Transaction{}
tx.PushOperation(voteOp)
tx.PushOperation(transferOp)

// Sign transaction
signedTx := transaction.NewSignedTransaction(tx)
err := signedTx.Sign(privateKeys, transaction.SteemChain)

Supported Operations

The library supports all Steem protocol operations:

  • Content: comment, vote, delete_comment
  • Financial: transfer, transfer_to_savings, claim_reward_balance
  • Account: account_create, account_update, recover_account
  • Witness: witness_update, account_witness_vote
  • Market: limit_order_create, limit_order_cancel
  • Custom: custom_json, custom_binary
  • And many more...

Working with Operation Data

The Operation interface provides a Data() any method that returns the operation data. Understanding how to use this method is important for type-safe operation handling.

Return Types

The Data() method returns different types depending on the operation:

  • Known Operations: Returns the operation struct itself (e.g., *VoteOperation, *TransferOperation)
  • Unknown Operations: Returns *json.RawMessage for operations not recognized by the package

Best Practices

1. Type Assertion Based on Operation Type

Always check the operation type before performing type assertions:

import (
    "encoding/json"
    "github.com/steemit/steemutil/protocol"
)

func processOperation(op protocol.Operation) {
    switch op.Type() {
    case protocol.TypeVote:
        // Type assertion is safe after checking Type()
        voteOp := op.Data().(*protocol.VoteOperation)
        fmt.Printf("Voter: %s, Author: %s\n", voteOp.Voter, voteOp.Author)
        
    case protocol.TypeTransfer:
        transferOp := op.Data().(*protocol.TransferOperation)
        fmt.Printf("From: %s, To: %s, Amount: %s\n", 
            transferOp.From, transferOp.To, transferOp.Amount)
        
    case protocol.TypeComment:
        commentOp := op.Data().(*protocol.CommentOperation)
        fmt.Printf("Author: %s, Title: %s\n", commentOp.Author, commentOp.Title)
        
    default:
        // Handle unknown operations
        if rawJSON, ok := op.Data().(*json.RawMessage); ok {
            fmt.Printf("Unknown operation type: %s\n", op.Type())
            fmt.Printf("Raw JSON: %s\n", string(*rawJSON))
        }
    }
}

2. Safe Type Assertion with Error Handling

Use type assertions with the two-value form for safer code:

func safeProcessVote(op protocol.Operation) error {
    if op.Type() != protocol.TypeVote {
        return fmt.Errorf("expected vote operation, got %s", op.Type())
    }
    
    voteOp, ok := op.Data().(*protocol.VoteOperation)
    if !ok {
        return fmt.Errorf("failed to assert vote operation data")
    }
    
    // Use voteOp safely
    fmt.Printf("Processing vote: %s -> %s/%s\n", 
        voteOp.Voter, voteOp.Author, voteOp.Permlink)
    return nil
}

3. Handling Unknown Operations

For operations not recognized by the package, Data() returns *json.RawMessage:

func handleUnknownOperation(op protocol.Operation) {
    if rawJSON, ok := op.Data().(*json.RawMessage); ok {
        // This is an unknown operation type
        var data map[string]any
        if err := json.Unmarshal(*rawJSON, &data); err == nil {
            fmt.Printf("Unknown operation data: %+v\n", data)
        }
    } else {
        // This is a known operation type
        fmt.Printf("Known operation: %s\n", op.Type())
    }
}

4. Direct Operation Access

For known operations, you can directly use the operation struct without calling Data():

// Instead of:
data := op.Data().(*protocol.VoteOperation)

// You can directly cast the operation:
if voteOp, ok := op.(*protocol.VoteOperation); ok {
    // Use voteOp directly
    fmt.Printf("Voter: %s\n", voteOp.Voter)
}

5. Iterating Over Operations

When processing multiple operations:

func processOperations(ops protocol.Operations) {
    for _, op := range ops {
        switch op.Type() {
        case protocol.TypeVote:
            voteOp := op.Data().(*protocol.VoteOperation)
            processVote(voteOp)
            
        case protocol.TypeTransfer:
            transferOp := op.Data().(*protocol.TransferOperation)
            processTransfer(transferOp)
            
        default:
            // Log or handle unknown operations
            fmt.Printf("Unhandled operation type: %s\n", op.Type())
        }
    }
}

Important Notes

  • Type Safety: Always check op.Type() before performing type assertions
  • Unknown Operations: Use *json.RawMessage type assertion to handle unrecognized operations
  • Performance: Direct operation casting (e.g., op.(*VoteOperation)) is more efficient than using Data() for known types
  • Compatibility: The any return type allows the library to handle both known and unknown operation types flexibly

Testing

Run the test suite:

# Run all tests
go test ./...

# Run specific package tests
go test ./rpc -v
go test ./auth -v
go test ./protocol -v

# Run with coverage
go test ./... -cover

Examples

See the examples directory for complete working examples:

  • SignedCall Authentication: examples/signed_call/
  • Key Generation: examples/generate_keys/
  • Transaction Broadcasting: examples/transfer/
  • Vote Operations: examples/vote_post/

API Reference

Authentication (auth/)

  • ToWif(name, password, role string) (string, error) - Generate WIF from credentials
  • GetPrivateKeys(name, password string, roles []string) (map[string]string, error) - Generate multiple keys
  • WifToPublic(wif string) (string, error) - Convert WIF to public key
  • IsWif(wif string) bool - Validate WIF format
  • Verify(name, password string, auths map[string]interface{}) (bool, error) - Verify credentials

RPC Authentication (rpc/)

  • Sign(request *RpcRequest, account string, keys []string) (*SignedRequest, error) - Sign RPC request
  • Validate(request *SignedRequest, verifyFunc func(...) error) ([]interface{}, error) - Validate signed request
  • SignRequest(method string, params []interface{}, id int, account, key string) (*SignedRequest, error) - Convenience function

Transaction (transaction/)

  • NewSignedTransaction(tx *Transaction) *SignedTransaction - Create signed transaction
  • (tx *SignedTransaction) Sign(keys []*wif.PrivateKey, chain *Chain) error - Sign transaction
  • (tx *SignedTransaction) Digest(chain *Chain) ([]byte, error) - Calculate transaction digest
  • (tx *SignedTransaction) Serialize() ([]byte, error) - Serialize transaction

WIF Operations (wif/)

  • (pk *PrivateKey) FromWif(wif string) error - Import from WIF
  • (pk *PrivateKey) ToWif() string - Export to WIF
  • (pk *PrivateKey) SignSha256(message []byte) ([]byte, error) - Sign message
  • (pk *PublicKey) VerifySha256(message, signature []byte) bool - Verify signature

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Related Projects

  • steemgosdk - High-level Steem Go SDK built on steemutil
  • steem-js - JavaScript Steem library (compatible with our SignedCall)
  • steem - Official Steem blockchain implementation

Support


Note: This library provides low-level utilities. For high-level application development, consider using steemgosdk which provides a more convenient API built on top of steemutil.

About

Provides steem-specific convenience functions and types for go

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages