neoaccounts

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 37 Imported by: 0

README

AccountPool Marble Service

TEE-secured HD-derived account pool management service running inside MarbleRun enclave.

Overview

The AccountPool Marble service manages a pool of Neo N3 accounts derived from a master key:

  1. Accounts are derived on-demand using HKDF from master key
  2. Other services (Automation, Datafeeds, TxProxy, etc.) can request and lock accounts
  3. Private keys never leave the TEE - signing done internally
  4. Automatic account rotation and pool maintenance

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    MarbleRun Enclave (TEE)                      │
│                                                                 │
│    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐        │
│    │   Handler   │    │   Pool      │    │  Key        │        │
│    │  (REST API) │───>│  Manager    │<──>│  Deriver    │        │
│    └─────────────┘    └──────┬──────┘    └──────┬──────┘        │
│           │                  │                  │               │
│    ┌──────▼──────┐    ┌──────▼──────┐    ┌──────▼──────┐        │
│    │   Signing   │    │  Account    │    │  Master Key │        │
│    │   Service   │    │  Rotation   │    │   (Sealed)  │        │
│    └─────────────┘    └─────────────┘    └─────────────┘        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │    Supabase     │
                    │  (Account Pool) │
                    └─────────────────┘

File Structure

File Purpose
service.go Service initialization, key derivation
pool.go Pool management, request/release
signing.go Transaction signing
masterkey.go Master key handling
attestation.go TEE attestation
handlers.go HTTP request handlers
api.go Route registration
types.go Request/response types

Lifecycle is handled by the shared commonservice.BaseService (start/stop hooks, workers, standard routes).

Key Components

Service Struct
type Service struct {
    *commonservice.BaseService
    mu sync.RWMutex

    // Secrets
    masterKey              []byte
    masterPubKey           []byte
    masterKeyHash          []byte
    masterKeyAttestationID string

    // Service-specific repository
    repo neoaccountssupabase.RepositoryInterface

    // Chain interaction
    chainClient *chain.Client
}
HD Key Derivation

Accounts are derived deterministically from master key:

func (s *Service) deriveAccountKey(accountID string) ([]byte, error) {
    return crypto.DeriveKey(s.masterKey, []byte(accountID), "pool-account", 32)
}

Upgrade Safety: Key derivation uses only:

  • masterKey: From MarbleRun injection (stable across upgrades)
  • accountID: Business identifier (stable)
  • "pool-account": Service context (code constant)

NO enclave identity (MRENCLAVE/MRSIGNER) is used in derivation.

Pool Configuration

Constant Value Description
MinPoolAccounts 200 Minimum pool size
MaxPoolAccounts 10000 Maximum pool size
RotationRate 10% Daily rotation rate
RotationMinAge 24h Minimum age before rotation
LockTimeout 24h Stale lock timeout

API Endpoints

Endpoint Method Description
/health GET Liveness probe
/ready GET Readiness probe
/info GET Standard service info (ID/name/version)
/pool-info GET Pool statistics (accounts + per-token stats)
/master-key GET Master key attestation bundle (cacheable)
/accounts GET List accounts by service
/request POST Request and lock accounts
/release POST Release locked accounts
/sign POST Sign transaction hash
/batch-sign POST Sign multiple transactions
/balance POST Update account balance
/transfer POST Transfer tokens from a pool account

Request/Response Types

Canonical request/response DTOs live in infrastructure/accountpool/types to avoid duplicated/inconsistent API types across server and clients.

RequestAccountsInput
type RequestAccountsInput struct {
    ServiceID string `json:"service_id"` // ID of requesting service
    Count     int    `json:"count"`      // Number of accounts (1-100)
    Purpose   string `json:"purpose"`    // Audit description
}
AccountInfo
type AccountInfo struct {
    ID         string    `json:"id"`
    Address    string    `json:"address"`
    Balances   map[string]TokenBalance `json:"balances"` // key: token_type (e.g. "GAS", "NEO")
    CreatedAt  time.Time `json:"created_at"`
    LastUsedAt time.Time `json:"last_used_at"`
    TxCount    int64     `json:"tx_count"`
    IsRetiring bool      `json:"is_retiring"`
    LockedBy   string    `json:"locked_by,omitempty"`
    LockedAt   time.Time `json:"locked_at,omitempty"`
}
TokenStats
type TokenStats struct {
    TokenType        string `json:"token_type"`
    ScriptHash       string `json:"script_hash"`
    TotalBalance     int64  `json:"total_balance"`
    LockedBalance    int64  `json:"locked_balance"`
    AvailableBalance int64  `json:"available_balance"`
}
SignTransactionInput
type SignTransactionInput struct {
    ServiceID string `json:"service_id"`
    AccountID string `json:"account_id"`
    TxHash    []byte `json:"tx_hash"`
}
SignTransactionResponse
type SignTransactionResponse struct {
    AccountID string `json:"account_id"`
    Signature []byte `json:"signature"`
    PublicKey []byte `json:"public_key"`
}
PoolInfoResponse
type PoolInfoResponse struct {
    TotalAccounts    int   `json:"total_accounts"`
    ActiveAccounts   int   `json:"active_accounts"`
    LockedAccounts   int   `json:"locked_accounts"`
    RetiringAccounts int   `json:"retiring_accounts"`
    TokenStats       map[string]TokenStats `json:"token_stats"` // key: token_type
}

Configuration

type Config struct {
    Marble          *marble.Marble
    DB              database.RepositoryInterface
    NeoAccountsRepo neoaccountssupabase.RepositoryInterface
    ChainClient     *chain.Client
}
Required Secrets
Secret Description
POOL_MASTER_KEY 32-byte HD wallet master key (preferred)
COORD_MASTER_SEED 16+ byte coordinator seed (master key derived)

If neither secret is configured, the service fails fast by default to prevent creating unrecoverable accounts. For local experiments only, you can opt in to ephemeral keys with NEOACCOUNTS_ALLOW_EPHEMERAL_MASTER_KEY=true (accounts are not recoverable after restart).

Security Features

Private Key Protection
  • Master key never leaves MarbleRun TEE
  • Private keys derived on-demand, zeroed after use
  • Signatures computed inside TEE
  • Only public info (address, per-token balances) exposed via API

In strict identity mode (production/SGX/MarbleRun TLS), caller identity is derived from verified mTLS peer identity; inter-service calls should use the MarbleRun-provided mTLS HTTP client.

Account Locking
  • Services must lock accounts before use
  • Only locking service can sign or modify balance
  • Stale locks automatically cleaned up after 24h
Account Rotation
  • 10% of accounts rotated daily
  • Locked accounts NEVER rotated
  • Retiring accounts are kept by default; enable deletion with NEOACCOUNTS_DELETE_RETIRING_ACCOUNTS=true
  • Ensures fresh, unlinkable accounts

Background Workers

Account Rotation Worker

Runs hourly to:

  • Mark old, low-balance accounts as retiring
  • Create new accounts to maintain minimum pool size
  • Delete empty retiring accounts
Lock Cleanup Worker

Runs hourly to:

  • Detect stale locks (>24h)
  • Force-release abandoned accounts

Dependencies

Internal Packages
Package Purpose
infrastructure/chain Neo N3 blockchain interaction
infrastructure/crypto Key derivation, signing
infrastructure/marble MarbleRun TEE utilities
infrastructure/database Base repository
infrastructure/service Base service framework
infrastructure/accountpool/supabase Account repository
External Packages
Package Purpose
github.com/gorilla/mux HTTP router
github.com/google/uuid Account/Lock ID generation

Documentation

Overview

Package neoaccounts provides API routes for the neoaccounts service.

Package neoaccounts provides HTTP handlers for the neoaccounts service.

Package neoaccounts provides pool management for the neoaccounts service.

Package neoaccounts provides a centralized neoaccounts service for other marbles. Private keys never leave this service - other services request accounts and submit transactions for signing.

Package neoaccounts provides transaction signing for the neoaccounts service.

Index

Constants

View Source
const (
	ServiceID   = "neoaccounts"
	ServiceName = "Account Pool Service"
	Version     = "2.0.0" // Updated for multi-token support

	// Pool configuration
	MinPoolAccounts = 1000
	MaxPoolAccounts = 50000
	BatchCreateSize = 100 // Number of accounts to create in each batch
	RotationRate    = 0.1 // 10% of accounts rotated per day
	RotationMinAge  = 24  // Minimum age in hours before rotation

	// Lock timeout - accounts locked longer than this can be force-released
	LockTimeout = 24 * time.Hour
)
View Source
const (
	SecretTEEPrivateKey       = "TEE_PRIVATE_KEY"
	SecretTEEWalletPrivateKey = "TEE_WALLET_PRIVATE_KEY"
	SecretNeoTestnetWIF       = "NEO_TESTNET_WIF"
)

Secret names for TEE wallet keys - these should be defined in MarbleRun manifest

View Source
const (
	TokenTypeNEO = neoaccountstypes.TokenTypeNEO
	TokenTypeGAS = neoaccountstypes.TokenTypeGAS
)

Re-export token constants for convenience

Variables

This section is empty.

Functions

This section is empty.

Types

type AccountInfo

type AccountInfo = neoaccountstypes.AccountInfo

AccountInfo represents public account information returned to clients. Private keys are never exposed. Balances are tracked per-token.

func AccountInfoFromAccount

func AccountInfoFromAccount(acc *neoaccountssupabase.Account) AccountInfo

AccountInfoFromAccount converts Account to AccountInfo with empty balances.

func AccountInfoFromWithBalances

func AccountInfoFromWithBalances(acc *neoaccountssupabase.AccountWithBalances) AccountInfo

AccountInfoFromWithBalances converts AccountWithBalances to AccountInfo.

type BatchSignInput

type BatchSignInput = neoaccountstypes.BatchSignInput

BatchSignInput for signing multiple transactions.

type BatchSignResponse

type BatchSignResponse = neoaccountstypes.BatchSignResponse

BatchSignResponse returns multiple signatures.

type Config

type Config struct {
	Marble          *marble.Marble
	DB              database.RepositoryInterface
	NeoAccountsRepo neoaccountssupabase.RepositoryInterface
	ChainClient     *chain.Client
}

Config holds NeoAccounts service configuration.

type ContractParam

type ContractParam = neoaccountstypes.ContractParam

ContractParam represents a parameter for contract invocation.

type DeployContractInput

type DeployContractInput = neoaccountstypes.DeployContractInput

DeployContractInput deploys a new contract using a pool account.

type DeployContractResponse

type DeployContractResponse = neoaccountstypes.DeployContractResponse

DeployContractResponse returns the deployment result.

type DeployMasterInput

type DeployMasterInput = neoaccountstypes.DeployMasterInput

DeployMasterInput deploys a contract using the master wallet.

type DeployMasterResponse

type DeployMasterResponse = neoaccountstypes.DeployMasterResponse

DeployMasterResponse returns the deployment result using master wallet.

type FundAccountInput

type FundAccountInput = neoaccountstypes.FundAccountInput

FundAccountInput funds a pool account from the master wallet.

type FundAccountResponse

type FundAccountResponse = neoaccountstypes.FundAccountResponse

FundAccountResponse returns the funding result.

type InvokeContractInput

type InvokeContractInput = neoaccountstypes.InvokeContractInput

InvokeContractInput invokes a contract method using a pool account.

type InvokeContractResponse

type InvokeContractResponse = neoaccountstypes.InvokeContractResponse

InvokeContractResponse returns the invocation result.

type InvokeMasterInput

type InvokeMasterInput = neoaccountstypes.InvokeMasterInput

InvokeMasterInput invokes a contract using the master wallet (TEE operations).

type ListAccountsInput

type ListAccountsInput struct {
	ServiceID  string `json:"service_id"`            // Required: only list accounts locked by this service
	Token      string `json:"token,omitempty"`       // Optional: filter by token type
	MinBalance *int64 `json:"min_balance,omitempty"` // Optional: minimum balance for specified token
}

ListAccountsInput for listing accounts with filters.

type ListAccountsResponse

type ListAccountsResponse = neoaccountstypes.ListAccountsResponse

ListAccountsResponse returns filtered accounts.

type MasterKeyAttestation

type MasterKeyAttestation = neoaccountstypes.MasterKeyAttestation

MasterKeyAttestation is a non-sensitive bundle proving the master key hash is bound to enclave report data.

type MasterKeySummary

type MasterKeySummary struct {
	Hash            string `json:"hash"`
	PubKeyHex       string `json:"pubkey,omitempty"`
	AttestationHash string `json:"attestation_hash,omitempty"`
	Source          string `json:"source"`
	RequiresHash    bool   `json:"requires_hash"`
}

MasterKeySummary exposes non-sensitive metadata for off-chain attestation verification and on-chain anchoring without revealing the key material.

type PoolInfoResponse

type PoolInfoResponse = neoaccountstypes.PoolInfoResponse

PoolInfoResponse returns pool statistics with per-token breakdowns.

type ReleaseAccountsInput

type ReleaseAccountsInput = neoaccountstypes.ReleaseAccountsInput

ReleaseAccountsInput for releasing previously requested accounts.

type ReleaseAccountsResponse

type ReleaseAccountsResponse = neoaccountstypes.ReleaseAccountsResponse

ReleaseAccountsResponse confirms release.

type RequestAccountsInput

type RequestAccountsInput = neoaccountstypes.RequestAccountsInput

RequestAccountsInput for requesting accounts from the pool.

type RequestAccountsResponse

type RequestAccountsResponse = neoaccountstypes.RequestAccountsResponse

RequestAccountsResponse returns the requested accounts.

type Service

type Service struct {
	*commonservice.BaseService
	// contains filtered or unexported fields
}

Service implements the NeoAccounts service marble.

func New

func New(cfg Config) (*Service, error)

New creates a new NeoAccounts service.

func (*Service) BatchSign

func (s *Service) BatchSign(ctx context.Context, serviceID string, requests []SignRequest) *BatchSignResponse

BatchSign signs multiple transaction hashes.

func (*Service) DeployContract

func (s *Service) DeployContract(ctx context.Context, serviceID, accountID, nefBase64, manifestJSON string, data any) (*DeployContractResponse, error)

DeployContract deploys a new smart contract using a pool account. All signing happens inside TEE - private keys never leave the enclave.

func (*Service) DeployMaster

func (s *Service) DeployMaster(ctx context.Context, nefBase64, manifestJSON string, data any) (*DeployMasterResponse, error)

DeployMaster deploys a new smart contract using the master wallet (TEE_PRIVATE_KEY). This is used for deploying contracts where the master account needs to be the Admin. All signing happens inside TEE - private keys never leave the enclave.

func (*Service) FundAccount

func (s *Service) FundAccount(ctx context.Context, toAddress string, amount int64, tokenAddress string) (*FundAccountResponse, error)

FundAccount transfers tokens from the master wallet (TEE_PRIVATE_KEY) to a target address. This is used to fund pool accounts with GAS for transaction fees. Unlike Transfer(), this uses the master wallet directly, not a pool account. After successful transfer, updates the database balance for the target account.

func (*Service) GetPoolInfo

func (s *Service) GetPoolInfo(ctx context.Context) (*PoolInfoResponse, error)

GetPoolInfo returns pool statistics with per-token breakdowns.

func (*Service) InvokeContract

func (s *Service) InvokeContract(ctx context.Context, serviceID, accountID, contractAddress, method string, params []ContractParam, scope string) (*InvokeContractResponse, error)

InvokeContract invokes a contract method using a pool account. All signing happens inside TEE - private keys never leave the enclave.

func (*Service) InvokeMaster

func (s *Service) InvokeMaster(ctx context.Context, contractAddress, method string, params []ContractParam, scope string) (*InvokeContractResponse, error)

InvokeMaster invokes a contract method using the master wallet (TEE_PRIVATE_KEY). This is used for TEE operations like PriceFeed and RandomnessLog that require the caller to be a registered TEE signer in AppRegistry. Unlike InvokeContract(), this uses the master wallet directly, not a pool account.

func (*Service) ListAccountsByService

func (s *Service) ListAccountsByService(ctx context.Context, serviceID, tokenType string, minBalance *int64) ([]AccountInfo, error)

ListAccountsByService returns accounts locked by a specific service. DESIGN: Read-only operation, no mutex needed - data comes from DB.

func (*Service) ListLowBalanceAccounts

func (s *Service) ListLowBalanceAccounts(ctx context.Context, tokenType string, maxBalance int64, limit int) ([]AccountInfo, error)

ListLowBalanceAccounts returns accounts with balance below the specified threshold. This is useful for auto top-up workers that need to find accounts requiring funding. DESIGN: Read-only operation, no mutex needed - data comes from DB.

func (*Service) ReleaseAccounts

func (s *Service) ReleaseAccounts(ctx context.Context, serviceID string, accountIDs []string) (int, error)

ReleaseAccounts releases previously locked accounts. DESIGN: Uses atomic DB operations, no mutex needed for concurrent safety.

func (*Service) ReleaseAllByService

func (s *Service) ReleaseAllByService(ctx context.Context, serviceID string) (int, error)

ReleaseAllByService releases all accounts locked by a service. DESIGN: Uses atomic DB operations per account, no global mutex needed.

func (*Service) RequestAccounts

func (s *Service) RequestAccounts(ctx context.Context, serviceID string, count int, purpose string) (accounts []AccountInfo, lockID string, err error)

RequestAccounts locks and returns accounts for a service. DESIGN: Database operations (TryLockAccount) are atomic at DB level. The mutex is only used for in-memory operations to avoid holding locks during I/O.

func (*Service) SignTransaction

func (s *Service) SignTransaction(ctx context.Context, serviceID, accountID string, txHash []byte) (*SignTransactionResponse, error)

SignTransaction signs a transaction hash with an account's private key. The account must be locked by the requesting service.

func (*Service) SimulateContract

func (s *Service) SimulateContract(ctx context.Context, serviceID, accountID, contractAddress, method string, params []ContractParam) (*SimulateContractResponse, error)

SimulateContract simulates a contract invocation without signing or broadcasting.

func (*Service) Transfer

func (s *Service) Transfer(ctx context.Context, serviceID, accountID, toAddress string, amount int64, tokenHash string) (string, error)

Transfer transfers tokens from a pool account to a target address. The account must be locked by the requesting service.

The transfer is executed as an on-chain NEP-17 `transfer(from,to,amount,data)` invocation signed by the pool account's derived private key.

func (*Service) TransferWithData

func (s *Service) TransferWithData(ctx context.Context, serviceID, accountID, toAddress string, amount int64, data string) (string, error)

TransferWithData transfers GAS from a pool account to a target address with optional data. The data parameter is passed to the OnNEP17Payment callback of the receiving contract. This is used for payments to contracts like PaymentHub that need to identify the payment source.

func (*Service) UpdateBalance

func (s *Service) UpdateBalance(ctx context.Context, serviceID, accountID, tokenType string, delta int64, absolute *int64) (oldBalance, newBalance, txCount int64, err error)

UpdateBalance updates an account's token balance. SECURITY FIX: Added integer overflow/underflow protection. Uses atomic DB operations with lock verification to prevent race conditions.

func (*Service) UpdateContract

func (s *Service) UpdateContract(ctx context.Context, serviceID, accountID, contractAddress, nefBase64, manifestJSON string, data any) (*UpdateContractResponse, error)

UpdateContract updates an existing smart contract using a pool account. All signing happens inside TEE - private keys never leave the enclave.

type SignRequest

type SignRequest = neoaccountstypes.SignRequest

SignRequest represents a single signing request within a batch.

type SignTransactionInput

type SignTransactionInput = neoaccountstypes.SignTransactionInput

SignTransactionInput for signing a transaction with an account's private key.

type SignTransactionResponse

type SignTransactionResponse = neoaccountstypes.SignTransactionResponse

SignTransactionResponse returns the signature.

type SimulateContractInput

type SimulateContractInput = neoaccountstypes.SimulateContractInput

SimulateContractInput simulates a contract invocation without signing.

type SimulateContractResponse

type SimulateContractResponse = neoaccountstypes.SimulateContractResponse

SimulateContractResponse returns the simulation result.

type TokenBalance

type TokenBalance = neoaccountstypes.TokenBalance

TokenBalance is the API representation of a token balance.

type TokenStats

type TokenStats = neoaccountstypes.TokenStats

TokenStats represents aggregated statistics for a token type.

type TransferInput

type TransferInput = neoaccountstypes.TransferInput

TransferInput for transferring tokens from a pool account.

type TransferResponse

type TransferResponse = neoaccountstypes.TransferResponse

TransferResponse returns the transfer result.

type TransferWithDataInput

type TransferWithDataInput = neoaccountstypes.TransferWithDataInput

TransferWithDataInput for transferring GAS with data to a contract.

type TransferWithDataResponse

type TransferWithDataResponse = neoaccountstypes.TransferWithDataResponse

TransferWithDataResponse returns the transfer with data result.

type UpdateBalanceInput

type UpdateBalanceInput = neoaccountstypes.UpdateBalanceInput

UpdateBalanceInput for updating an account's token balance.

type UpdateBalanceResponse

type UpdateBalanceResponse = neoaccountstypes.UpdateBalanceResponse

UpdateBalanceResponse confirms balance update.

type UpdateContractInput

type UpdateContractInput = neoaccountstypes.UpdateContractInput

UpdateContractInput updates an existing contract using a pool account.

type UpdateContractResponse

type UpdateContractResponse = neoaccountstypes.UpdateContractResponse

UpdateContractResponse returns the update result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL