Implementing Cross-Language ECDSA Signatures with Secp256k1

Consider this private and public key pair:

privateKeyStr := "55331231652752707450534414287730188629145955228891758798201450763340552825166"
publicKeyStr := "11798277315441798127238305597212517654332197982978466864717364245859439644690363141941628373531488122609933560694747426926865495896421628851963848417303440"

Go Implementation for Signing and Verification

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"math/big"
	"github.com/ethereum/go-ethereum/crypto/secp256k1"
)

func ExecuteSecp256k1SignatureTest() {
	privateDecimal := new(big.Int)
	privateDecimal.SetString(privateKeyStr, 10)
	fmt.Printf("Private key bytes: %v\n", privateDecimal.Bytes())
	fmt.Printf("Private key hex: %x\n", privateDecimal.Bytes())

	hashDigest := sha256.Sum256([]byte("Abby is beautiful and smart!"))
	signatureBytes, _ := secp256k1.Sign(hashDigest[:], privateDecimal.Bytes())
	fmt.Printf("Signature hex: %x\n", signatureBytes)
	fmt.Printf("Signature bytes: %v\n", signatureBytes)

	publicDecimal := new(big.Int)
	publicDecimal.SetString(publicKeyStr, 10)
	publicHex := hex.EncodeToString(publicDecimal.Bytes())
	fmt.Printf("Public key hex: %v\n", publicHex)
	
	uncompressedPublic := "04" + publicHex
	publicDecimal.SetString(uncompressedPublic, 16)
	fmt.Printf("Public key bytes: %v\n", publicDecimal.Bytes())
	fmt.Printf("Public key hex: %x\n", publicDecimal.Bytes())

	if secp256k1.VerifySignature(publicDecimal.Bytes(), hashDigest[:], signatureBytes[:64]) {
		fmt.Println("Verification successful")
	} else {
		fmt.Println("Verification failed")
	}
}

Modular Signing Function

func GenerateSecp256k1Signature(message []byte, privateKeyDecimal string) (string, error) {
	keyValue := new(big.Int)
	keyValue.SetString(privateKeyDecimal, 10)
	fmt.Printf("Private key bytes: %v\n", keyValue.Bytes())
	fmt.Printf("Private key hex: %x\n", keyValue.Bytes())
	
	hashValue := sha256.Sum256(message)
	fmt.Printf("Message hash: %x\n", hashValue)
	
	signature, err := secp256k1.Sign(hashValue[:], keyValue.Bytes())
	fmt.Printf("Signature R component: %v\n", signature[0:32])
	fmt.Printf("Signature S component: %v\n", signature[32:64])
	
	if err != nil {
		return "", err
	}
	
	signatureHex := hex.EncodeToString(signature)
	return signatureHex, nil
}

HTTP Request with Signature Header

func ExecuteSignedRequest(requestData Request, privateKey string) (string, error) {
	serviceURL := fmt.Sprintf("http://%s/weid/api/invoke", config.GlobalConfig.WeIDServiceAddress)
	jsonData, err := json.Marshal(requestData)
	if err != nil {
		return "", err
	}

	httpClient := &http.Client{}
	httpRequest, err := http.NewRequest("POST", serviceURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return "", err
	}
	
	signatureValue, signErr := GenerateSecp256k1Signature(jsonData, privateKey)
	if signatureValue == "" {
		return "", signErr
	}
	
	httpRequest.Header.Add("signature", signatureValue)
	httpRequest.Header.Add("Content-Type", "application/json")

	response, err := httpClient.Do(httpRequest)
	if err != nil {
		return "", err
	}
	defer response.Body.Close()

	responseBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	
	return string(responseBody), nil
}

Java Verification Implementation

public static boolean validateSignature(String publicKeyDecimal, String rawData) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    String signatureHex = request.getHeader("signature");
    
    try {
        BigInteger publicBigInt = new BigInteger(publicKeyDecimal, 10);
        String publicKeyHex = publicBigInt.toString(16);
        String dataHash = computeHash(rawData);
        
        byte[] signatureBytes = Hex.decodeHex(signatureHex);
        RsvSignature signature = new RsvSignature();
        
        byte[] rComponent = new byte[32];
        byte[] sComponent = new byte[32];
        System.arraycopy(signatureBytes, 0, rComponent, 0, 32);
        System.arraycopy(signatureBytes, 32, sComponent, 0, 32);
        
        signature.setR(new Bytes32(rComponent));
        signature.setS(new Bytes32(sComponent));
        signature.setV(new Uint8(signatureBytes[64]));
        
        return DataToolUtils.verifySignature(publicKeyHex, dataHash, signature);
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    return false;
}

Note the byte representation differences between lnaguages:

  • Java uses signed bytes (range -128 to 127)
  • Go uses unsigned bytes (range 0 to 255)
  • Different processro architectures may use different endianness
  • These differences must be handled during cross-language signature verification

Tags: cryptography secp256k1 cross-language signature verification

Posted on Thu, 23 Jul 2026 16:36:23 +0000 by meepokman