Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/shannon-sdk.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ require (
github.com/cosmos/cosmos-sdk v0.53.0
github.com/cosmos/gogoproto v1.7.0
github.com/pokt-network/poktroll v0.1.30-0.20250926212324-1588b0a53acb
github.com/pokt-network/ring-go v0.1.1-0.20250925213458-782cc69bc1ec
// Use ring-go with SignerContext optimization (perf/sign-precompute branch)
github.com/pokt-network/ring-go v0.1.1-0.20251219190013-b576b71e4648
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -972,8 +972,8 @@ github.com/pokt-network/go-dleq v0.0.0-20250925202155-488f42ad642a h1:zgSFcOX8m9
github.com/pokt-network/go-dleq v0.0.0-20250925202155-488f42ad642a/go.mod h1:KVsT8HO2EXHwMY1wnyYhR/lSZFNFIit8dJ+aDb1dSkA=
github.com/pokt-network/poktroll v0.1.30-0.20250926212324-1588b0a53acb h1:6pCcbr8wOiKDxCkHtOMDsXqILpJGCbGWe8jCVHNb3jU=
github.com/pokt-network/poktroll v0.1.30-0.20250926212324-1588b0a53acb/go.mod h1:8A8+i05sggfn79YQCX6B0cLEn0ihbi01nck1/qNoznM=
github.com/pokt-network/ring-go v0.1.1-0.20250925213458-782cc69bc1ec h1:ItogqaNYkfmQp7fJV1CfP9cTdhIEkjEtMDmlYrPvbjg=
github.com/pokt-network/ring-go v0.1.1-0.20250925213458-782cc69bc1ec/go.mod h1:jpllhLLlmtz+/0KYwhFjtO/Qpdy+UUDPkgQKSQJRc7Q=
github.com/pokt-network/ring-go v0.1.1-0.20251219190013-b576b71e4648 h1:0jrITIlVjMz69Iwlhr5Tlisvxj+cUoWmY4yttkRzT/w=
github.com/pokt-network/ring-go v0.1.1-0.20251219190013-b576b71e4648/go.mod h1:jpllhLLlmtz+/0KYwhFjtO/Qpdy+UUDPkgQKSQJRc7Q=
github.com/pokt-network/smt v0.14.1 h1:q8pZCo01RY+kzGurRcHArSGGEV3UhBywUZnbVn9nDM8=
github.com/pokt-network/smt v0.14.1/go.mod h1:TehzlxITd3EqLzo428VY0QID7Ajdn7QJP4ZzdPiYbZE=
github.com/pokt-network/smt/kvstore/pebble v0.0.0-20240822175047-21ea8639c188 h1:QK1WmFKQ/OzNVob/br55Brh+EFbWhcdq41WGC8UMihM=
Expand Down
93 changes: 84 additions & 9 deletions signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"fmt"
"sync"

servicetypes "github.com/pokt-network/poktroll/x/service/types"
"github.com/pokt-network/ring-go"
Expand All @@ -20,6 +21,11 @@ type Signer struct {
PrivateKeyHex string
// privateKeyBytes caches the 32-byte private key to avoid repeated hex decoding.
privateKeyBytes []byte

// signerContextCache caches SignerContext per ring to avoid redundant
// cryptographic operations (public key computation, hash-to-curve, key image).
// Key is the ring's memory address as a unique identifier.
signerContextCache sync.Map // map[*ring.Ring]*ring.SignerContext
}

// NewSignerFromHex creates a new Signer instance from a hex-encoded private key.
Expand All @@ -37,10 +43,35 @@ func NewSignerFromHex(privateKeyHex string) (*Signer, error) {
}, nil
}

// getOrCreateSignerContext returns a cached SignerContext for the given ring,
// or creates and caches a new one if not present.
func (s *Signer) getOrCreateSignerContext(sessionRing *ring.Ring) (*ring.SignerContext, error) {
// Check cache first
if cached, ok := s.signerContextCache.Load(sessionRing); ok {
return cached.(*ring.SignerContext), nil
}

// Decode private key to scalar
privKey, err := ring.Secp256k1().DecodeToScalar(s.privateKeyBytes)
if err != nil {
return nil, fmt.Errorf("getOrCreateSignerContext: error decoding private key: %w", err)
}

// Create new SignerContext with pre-computed values
ctx, err := sessionRing.NewSignerContext(privKey)
if err != nil {
return nil, fmt.Errorf("getOrCreateSignerContext: error creating signer context: %w", err)
}

// Cache it (use LoadOrStore to handle concurrent creation)
actual, _ := s.signerContextCache.LoadOrStore(sessionRing, ctx)
return actual.(*ring.SignerContext), nil
}

// Sign signs the given relay request using the signer's private key and the application's ring.
//
// This method delegates to the pluggable crypto backend for optimal performance.
// The backend choice provides different performance characteristics:
// This method uses SignerContext caching for optimal performance when signing
// multiple requests with the same ring (which is common within a session).
//
// Returns a pointer instead of directly setting the signature on the input relay request to avoid implicit output.
func (s *Signer) Sign(
Expand All @@ -64,16 +95,14 @@ func (s *Signer) Sign(
return nil, fmt.Errorf("Sign: error getting signable bytes hash from the relay request: %w", err)
}

// Sign the request using the session ring and signer's private key
// TODO_OPTIMIZE: Pre-cache the decoded scalar to avoid per-call DecodeToScalar
// once ring-go exposes a stable public scalar type for long-lived reuse.
// Decode private key bytes to scalar (fast, but still cheaper than hex decoding each call)
signerPrivKey, err := ring.Secp256k1().DecodeToScalar(s.privateKeyBytes)
// Get or create cached SignerContext for this ring
signerCtx, err := s.getOrCreateSignerContext(sessionRing)
if err != nil {
return nil, fmt.Errorf("Sign: error decoding private key to scalar: %w", err)
return nil, fmt.Errorf("Sign: error getting signer context: %w", err)
}

ringSig, err := sessionRing.Sign(signableBz, signerPrivKey)
// Sign using the cached context (avoids redundant ScalarBaseMul, hashToCurve, etc.)
ringSig, err := sessionRing.SignWithContext(signableBz, signerCtx)
if err != nil {
return nil, fmt.Errorf(
"Sign: error signing using the ring of application with address %s: %w",
Expand All @@ -96,3 +125,49 @@ func (s *Signer) Sign(
relayRequest.Meta.Signature = signature
return relayRequest, nil
}

// SignWithRing signs the given relay request using the signer's private key and a pre-built ring.
//
// This method is useful when the caller caches the ring externally (e.g., by session)
// to ensure the same ring pointer is reused, enabling SignerContext cache hits.
//
// Returns a pointer instead of directly setting the signature on the input relay request to avoid implicit output.
func (s *Signer) SignWithRing(
ctx context.Context,
relayRequest *servicetypes.RelayRequest,
sessionRing *ring.Ring,
) (*servicetypes.RelayRequest, error) {
// Get the signable bytes hash from the relay request
signableBz, err := relayRequest.GetSignableBytesHash()
if err != nil {
return nil, fmt.Errorf("SignWithRing: error getting signable bytes hash from the relay request: %w", err)
}

// Get or create cached SignerContext for this ring
signerCtx, err := s.getOrCreateSignerContext(sessionRing)
if err != nil {
return nil, fmt.Errorf("SignWithRing: error getting signer context: %w", err)
}

// Sign using the cached context (avoids redundant ScalarBaseMul, hashToCurve, etc.)
ringSig, err := sessionRing.SignWithContext(signableBz, signerCtx)
if err != nil {
return nil, fmt.Errorf("SignWithRing: error signing relay request: %w", err)
}

// Serialize the signature
signature, err := ringSig.Serialize()
if err != nil {
return nil, fmt.Errorf("SignWithRing: error serializing the signature: %w", err)
}

// Set the signature on the relay request
relayRequest.Meta.Signature = signature
return relayRequest, nil
}

// ClearSignerContextCache clears the cached SignerContexts.
// This should be called when sessions roll over and old rings are no longer needed.
func (s *Signer) ClearSignerContextCache() {
s.signerContextCache = sync.Map{}
}