Add cross-platform support for crypto/p8k package

- Split all p8k files into *_linux.go and *_other.go variants
- Linux files use purego to load libsecp256k1.so dynamically
- Other platforms (darwin, windows, android, js/wasm) use stub that
  forces fallback to pure Go p256k1.mleku.dev implementation
- Add !android to Linux build tags since Android matches linux but
  purego requires CGO on Android
- Extract shared constants to constants.go (no build tags)
- Enables cross-compilation for macOS, Windows, and Android without
  requiring libsecp256k1 or CGO

Build tags:
- Linux: //go:build linux && !android && !purego
- Other: //go:build !linux || android || purego

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-02 11:14:46 +00:00
parent 9cf5f60a4e
commit 511bba3a12
11 changed files with 324 additions and 114 deletions

36
crypto/p8k/constants.go Normal file
View File

@@ -0,0 +1,36 @@
// Package secp provides Go bindings to libsecp256k1 without CGO.
// This file contains constants shared across all platforms.
package secp
// Constants for context flags
const (
ContextNone = 1
ContextVerify = 257 // 1 | (1 << 8)
ContextSign = 513 // 1 | (1 << 9)
ContextDeclassify = 1025 // 1 | (1 << 10)
)
// EC flags
const (
ECCompressed = 258 // SECP256K1_EC_COMPRESSED
ECUncompressed = 2 // SECP256K1_EC_UNCOMPRESSED
)
// Size constants
const (
PublicKeySize = 64
CompressedPublicKeySize = 33
UncompressedPublicKeySize = 65
SignatureSize = 64
CompactSignatureSize = 64
PrivateKeySize = 32
SharedSecretSize = 32
SchnorrSignatureSize = 64
RecoverableSignatureSize = 65
)
// Keypair represents a secp256k1 keypair for Schnorr signatures
type Keypair [96]byte
// XOnlyPublicKey represents a 64-byte x-only public key (internal format)
type XOnlyPublicKey [64]byte

View File

@@ -1,3 +1,5 @@
//go:build linux && !android && !purego
package secp
import (

13
crypto/p8k/ecdh_other.go Normal file
View File

@@ -0,0 +1,13 @@
//go:build !linux || android || purego
package secp
import (
"fmt"
"runtime"
)
// ECDH always returns an error on non-Linux platforms
func (c *Context) ECDH(pubkey []byte, seckey []byte) (output []byte, err error) {
return nil, fmt.Errorf("ECDH not supported on %s - use pure Go implementation", runtime.GOOS)
}

View File

@@ -1,3 +1,5 @@
//go:build linux && !android && !purego
package secp
import (

View File

@@ -0,0 +1,28 @@
//go:build !linux || android || purego
package secp
import (
"fmt"
"runtime"
)
// SignRecoverable always returns an error on non-Linux platforms
func (c *Context) SignRecoverable(msg32 []byte, seckey []byte) (sig []byte, err error) {
return nil, fmt.Errorf("recovery not supported on %s", runtime.GOOS)
}
// SerializeRecoverableSignatureCompact always returns an error on non-Linux platforms
func (c *Context) SerializeRecoverableSignatureCompact(sig []byte) (output64 []byte, recid int32, err error) {
return nil, 0, fmt.Errorf("recovery not supported on %s", runtime.GOOS)
}
// ParseRecoverableSignatureCompact always returns an error on non-Linux platforms
func (c *Context) ParseRecoverableSignatureCompact(input64 []byte, recid int32) (sig []byte, err error) {
return nil, fmt.Errorf("recovery not supported on %s", runtime.GOOS)
}
// Recover always returns an error on non-Linux platforms
func (c *Context) Recover(sig []byte, msg32 []byte) (pubkey []byte, err error) {
return nil, fmt.Errorf("recovery not supported on %s", runtime.GOOS)
}

View File

@@ -1,15 +1,11 @@
//go:build linux && !android && !purego
package secp
import (
"fmt"
)
// Keypair represents a secp256k1 keypair for Schnorr signatures
type Keypair [96]byte
// XOnlyPublicKey represents a 64-byte x-only public key (internal format)
type XOnlyPublicKey [64]byte
// CreateKeypair creates a keypair from a 32-byte secret key
func (c *Context) CreateKeypair(seckey []byte) (keypair Keypair, err error) {
if keypairCreate == nil {

View File

@@ -0,0 +1,48 @@
//go:build !linux || android || purego
package secp
import (
"fmt"
"runtime"
)
// CreateKeypair always returns an error on non-Linux platforms
func (c *Context) CreateKeypair(seckey []byte) (keypair Keypair, err error) {
return keypair, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// KeypairXOnlyPub always returns an error on non-Linux platforms
func (c *Context) KeypairXOnlyPub(keypair Keypair) (xonly XOnlyPublicKey, pkParity int32, err error) {
return xonly, 0, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// KeypairPub always returns an error on non-Linux platforms
func (c *Context) KeypairPub(keypair Keypair) (pubkey []byte, err error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SchnorrSign always returns an error on non-Linux platforms
func (c *Context) SchnorrSign(msg32 []byte, keypair Keypair, auxRand32 []byte) (sig []byte, err error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SchnorrVerify always returns an error on non-Linux platforms
func (c *Context) SchnorrVerify(sig64 []byte, msg []byte, xonlyPubkey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// ParseXOnlyPublicKey always returns an error on non-Linux platforms
func (c *Context) ParseXOnlyPublicKey(input32 []byte) (xonly []byte, err error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SerializeXOnlyPublicKey always returns an error on non-Linux platforms
func (c *Context) SerializeXOnlyPublicKey(xonly []byte) (output32 []byte, err error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// XOnlyPublicKeyFromPublicKey always returns an error on non-Linux platforms
func (c *Context) XOnlyPublicKeyFromPublicKey(pubkey []byte) (xonly []byte, pkParity int32, err error) {
return nil, 0, fmt.Errorf("not supported on %s", runtime.GOOS)
}

View File

@@ -1,5 +1,8 @@
//go:build linux && !android && !purego
// Package secp provides Go bindings to libsecp256k1 without CGO.
// It uses dynamic library loading via purego to call C functions directly.
// This file is only compiled on Linux - other platforms use pure Go implementations.
package secp
import (
@@ -10,7 +13,6 @@ import (
"path/filepath"
"runtime"
"sync"
"unsafe"
"github.com/ebitengine/purego"
)
@@ -18,33 +20,6 @@ import (
//go:embed libsecp256k1.so
var embeddedLibLinux []byte
// Constants for context flags
const (
ContextNone = 1
ContextVerify = 257 // 1 | (1 << 8)
ContextSign = 513 // 1 | (1 << 9)
ContextDeclassify = 1025 // 1 | (1 << 10)
)
// EC flags
const (
ECCompressed = 258 // SECP256K1_EC_COMPRESSED
ECUncompressed = 2 // SECP256K1_EC_UNCOMPRESSED
)
// Size constants
const (
PublicKeySize = 64
CompressedPublicKeySize = 33
UncompressedPublicKeySize = 65
SignatureSize = 64
CompactSignatureSize = 64
PrivateKeySize = 32
SharedSecretSize = 32
SchnorrSignatureSize = 64
RecoverableSignatureSize = 65
)
var (
libHandle uintptr
loadLibOnce sync.Once
@@ -94,22 +69,10 @@ var (
// extractEmbeddedLibrary extracts the embedded library to a temporary location
func extractEmbeddedLibrary() (path string, err error) {
extractLibOnce.Do(func() {
var libData []byte
var filename string
// Select the appropriate embedded library for this platform
switch runtime.GOOS {
case "linux":
if len(embeddedLibLinux) == 0 {
err = fmt.Errorf("no embedded library for linux")
return
}
libData = embeddedLibLinux
filename = "libsecp256k1.so"
default:
err = fmt.Errorf("no embedded library for %s", runtime.GOOS)
return
}
// Create a temporary directory for the library
// Use a deterministic name so we don't create duplicates
@@ -120,15 +83,15 @@ func extractEmbeddedLibrary() (path string, err error) {
}
// Write the library to the temp directory
extractedPath = filepath.Join(tmpDir, filename)
extractedPath = filepath.Join(tmpDir, "libsecp256k1.so")
// Check if file already exists and is valid
if info, e := os.Stat(extractedPath); e == nil && info.Size() == int64(len(libData)) {
if info, e := os.Stat(extractedPath); e == nil && info.Size() == int64(len(embeddedLibLinux)) {
// File exists and has correct size, assume it's valid
return
}
if err = os.WriteFile(extractedPath, libData, 0755); err != nil {
if err = os.WriteFile(extractedPath, embeddedLibLinux, 0755); err != nil {
err = fmt.Errorf("failed to write library to %s: %w", extractedPath, err)
return
}
@@ -160,8 +123,6 @@ func LoadLibrary() (err error) {
// If embedded library failed, fall back to system paths
if err != nil {
switch runtime.GOOS {
case "linux":
// Try common library paths
paths := []string{
"./libsecp256k1.so", // Bundled in repo for linux amd64
@@ -181,42 +142,6 @@ func LoadLibrary() (err error) {
break
}
}
case "darwin":
paths := []string{
"libsecp256k1.2.dylib",
"libsecp256k1.1.dylib",
"libsecp256k1.0.dylib",
"libsecp256k1.dylib",
"/usr/local/lib/libsecp256k1.dylib",
"/opt/homebrew/lib/libsecp256k1.dylib",
}
for _, p := range paths {
libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err == nil {
libPath = p
break
}
}
case "windows":
paths := []string{
"libsecp256k1-2.dll",
"libsecp256k1-1.dll",
"libsecp256k1-0.dll",
"libsecp256k1.dll",
"secp256k1.dll",
}
for _, p := range paths {
libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err == nil {
libPath = p
break
}
}
default:
err = fmt.Errorf("unsupported platform: %s", runtime.GOOS)
loadLibErr = err
return
}
}
if err != nil {
@@ -525,11 +450,3 @@ func (c *Context) NormalizeSignature(sig []byte) (normalized []byte, wasNormaliz
return
}
// Utility function to convert *byte to unsafe.Pointer
func bytesToPtr(b []byte) unsafe.Pointer {
if len(b) == 0 {
return nil
}
return unsafe.Pointer(&b[0])
}

88
crypto/p8k/secp_other.go Normal file
View File

@@ -0,0 +1,88 @@
//go:build !linux || android || purego
// Package secp provides Go bindings to libsecp256k1 without CGO.
// This file is a stub for non-Linux platforms (darwin, windows, ios, android, js/wasm).
// On these platforms, the pure Go implementation via p256k1.mleku.dev is used instead.
package secp
import (
"fmt"
"runtime"
)
// Context represents a secp256k1 context (stub for non-Linux)
type Context struct{}
// NewContext always returns an error on non-Linux platforms,
// forcing the use of the pure Go fallback implementation.
func NewContext(flags uint32) (c *Context, err error) {
return nil, fmt.Errorf("libsecp256k1 not available on %s/%s - use pure Go implementation", runtime.GOOS, runtime.GOARCH)
}
// LoadLibrary always returns an error on non-Linux platforms
func LoadLibrary() error {
return fmt.Errorf("libsecp256k1 dynamic loading not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
}
// Destroy is a no-op on non-Linux platforms
func (c *Context) Destroy() {}
// Randomize always returns an error on non-Linux platforms
func (c *Context) Randomize(seed32 []byte) error {
return fmt.Errorf("not supported on %s", runtime.GOOS)
}
// CreatePublicKey always returns an error on non-Linux platforms
func (c *Context) CreatePublicKey(seckey []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SerializePublicKey always returns an error on non-Linux platforms
func (c *Context) SerializePublicKey(pubkey []byte, compressed bool) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SerializePublicKeyCompressed always returns an error on non-Linux platforms
func (c *Context) SerializePublicKeyCompressed(pubkey []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// ParsePublicKey always returns an error on non-Linux platforms
func (c *Context) ParsePublicKey(input []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// Sign always returns an error on non-Linux platforms
func (c *Context) Sign(msg32 []byte, seckey []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// Verify always returns an error on non-Linux platforms
func (c *Context) Verify(msg32 []byte, sig []byte, pubkey []byte) (bool, error) {
return false, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SerializeSignatureDER always returns an error on non-Linux platforms
func (c *Context) SerializeSignatureDER(sig []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// ParseSignatureDER always returns an error on non-Linux platforms
func (c *Context) ParseSignatureDER(input []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// SerializeSignatureCompact always returns an error on non-Linux platforms
func (c *Context) SerializeSignatureCompact(sig []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// ParseSignatureCompact always returns an error on non-Linux platforms
func (c *Context) ParseSignatureCompact(input64 []byte) ([]byte, error) {
return nil, fmt.Errorf("not supported on %s", runtime.GOOS)
}
// NormalizeSignature always returns an error on non-Linux platforms
func (c *Context) NormalizeSignature(sig []byte) ([]byte, bool, error) {
return nil, false, fmt.Errorf("not supported on %s", runtime.GOOS)
}

View File

@@ -1,3 +1,5 @@
//go:build linux && !android && !purego
package secp
import (

78
crypto/p8k/utils_other.go Normal file
View File

@@ -0,0 +1,78 @@
//go:build !linux || android || purego
package secp
import (
"fmt"
"runtime"
)
// GeneratePrivateKey always returns an error on non-Linux platforms
func GeneratePrivateKey() (privKey []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// PublicKeyFromPrivate always returns an error on non-Linux platforms
func PublicKeyFromPrivate(privKey []byte, compressed bool) (pubKey []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// SignMessage always returns an error on non-Linux platforms
func SignMessage(msgHash []byte, privKey []byte) (sig []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// VerifyMessage always returns an error on non-Linux platforms
func VerifyMessage(msgHash []byte, compactSig []byte, serializedPubKey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// SignMessageDER always returns an error on non-Linux platforms
func SignMessageDER(msgHash []byte, privKey []byte) (derSig []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// VerifyMessageDER always returns an error on non-Linux platforms
func VerifyMessageDER(msgHash []byte, derSig []byte, serializedPubKey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// SchnorrSign always returns an error on non-Linux platforms
func SchnorrSign(msgHash []byte, privKey []byte, auxRand []byte) (sig []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// SchnorrVerifyWithPubKey always returns an error on non-Linux platforms
func SchnorrVerifyWithPubKey(msgHash []byte, sig []byte, xonlyPubKey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// XOnlyPubKeyFromPrivate always returns an error on non-Linux platforms
func XOnlyPubKeyFromPrivate(privKey []byte) (xonly []byte, pkParity int32, err error) {
return nil, 0, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// ComputeECDH always returns an error on non-Linux platforms
func ComputeECDH(serializedPubKey []byte, privKey []byte) (secret []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// SignRecoverableCompact always returns an error on non-Linux platforms
func SignRecoverableCompact(msgHash []byte, privKey []byte) (sig []byte, recID int32, err error) {
return nil, 0, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// RecoverPubKey always returns an error on non-Linux platforms
func RecoverPubKey(msgHash []byte, compactSig []byte, recID int32, compressed bool) (pubKey []byte, err error) {
return nil, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// ValidatePrivateKey always returns an error on non-Linux platforms
func ValidatePrivateKey(privKey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}
// IsPublicKeyValid always returns an error on non-Linux platforms
func IsPublicKeyValid(serializedPubKey []byte) (valid bool, err error) {
return false, fmt.Errorf("not supported on %s - use pure Go implementation", runtime.GOOS)
}