You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++导出ECDSA公钥在Go中解析后Y坐标不符问题排查

ECDSA公钥在Go中解析后Y坐标与同私钥生成公钥不一致的问题分析

我在C中实现了ECDSA函数,签名在C的验证逻辑中完全正常,但移植到Go语言后无法通过全部测试。将C++导出的33字节压缩格式公钥(十六进制字符串)用Go的elliptic.UnmarshalCompressed解析后,发现其X坐标与通过同一私钥调用ScalarBaseMult生成的公钥X坐标一致,但Y坐标完全不同。

Go测试代码

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "encoding/hex"
    "fmt"
    "math/big"
)    
func main() {
    // 从C++导出的压缩公钥十六进制字符串解码
    publicKeyBufferFromCplusplus, err := hex.DecodeString("02d36b0e521ca9a28cd6f2ddc56dc0973215702f6f67ed0670b9bc9a98c28d473b")
    if err != nil {
        fmt.Println("十六进制转字节失败: ", err)
        return
    }
    pk := new(ecdsa.PublicKey)
    pk.Curve = elliptic.P256()
    pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBufferFromCplusplus)

    // 使用C++中的私钥在Go中生成公钥
    expect_sk := new(ecdsa.PrivateKey)
    expect_sk.D, _ = new(big.Int).SetString("50228957095953179898827503463423289296009712707225507368245266147079499081684", 10)
    expect_sk.PublicKey.Curve = elliptic.P256()
    expect_sk.PublicKey.X, expect_sk.PublicKey.Y = expect_sk.PublicKey.Curve.ScalarBaseMult(expect_sk.D.Bytes())
    expect_pk := expect_sk.PublicKey

    // 对比两个公钥
    fmt.Printf("pk_x:\t\t%d\n", pk.X)
    fmt.Printf("expect pk_x:\t%d\n\n", expect_pk.X)
    fmt.Printf("pk_y:\t\t%d\n", pk.Y)
    fmt.Printf("expect pk_y:\t%d\n", expect_pk.Y)
}

终端输出

pk_x:           95627162525183504786576659676808415919520991299985517290103803735976207796027
expect pk_x:    95627162525183504786576659676808415919520991299985517290103803735976207796027

pk_y:           106312815215663533204607583749797836088594130128596587441436180287153537381066
expect pk_y:    9479273994692715558089863199609737441492013286693726754097451021713560472885

问题原因分析

椭圆曲线算法中,对于给定的X坐标,存在两个满足椭圆曲线方程y² = x³ + ax + b mod p的Y坐标,这两个Y坐标互为模p的相反数(即y和p-y)。压缩格式公钥的第一个字节(02或03)用于指定Y坐标的奇偶性:

  • 02表示Y为偶数
  • 03表示Y为奇数

针对P256曲线,其模数p为115792089210356248762697446949407573530086143415290314195533631308867097853951。计算p - expect_pk.Y可得:

115792089210356248762697446949407573530086143415290314195533631308867097853951 - 9479273994692715558089863199609737441492013286693726754097451021713560472885 = 106312815215663533204607583749797836088594130128596587441436180287153537381066

该结果与解析得到的pk.Y完全一致,说明这两个Y坐标是互补的。问题出在C++生成公钥时,选择了与Go中私钥推导公钥相反的Y分支,导致解析后的公钥点与签名生成时使用的公钥点不匹配,从而验证失败。

解决方案

  1. 修正C++公钥导出逻辑:检查C++代码中生成压缩公钥时的前缀字节是否与Y坐标的奇偶性正确对应,确保导出的是私钥对应的那个Y坐标的压缩公钥。
  2. 在Go中调整公钥Y坐标:如果C++的公钥导出逻辑无法修改,可以在Go中解析公钥后,将Y坐标替换为其模p的补数,使其与私钥生成的公钥匹配:
p := elliptic.P256().Params().P
// 计算互补Y坐标
complementY := new(big.Int).Sub(p, pk.Y)
// 将解析后的公钥Y替换为互补值
pk.Y = complementY
// 此时pk与expect_pk完全一致
fmt.Printf("调整后pk_y:\t%d\n", pk.Y)
fmt.Printf("与expect_pk一致: %t\n", pk.Y.Cmp(expect_pk.Y) == 0)

内容的提问来源于stack exchange,提问作者Richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 06:25:21