首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何用golang与ecdsa私钥签名?

如何用golang与ecdsa私钥签名?
EN

Stack Overflow用户
提问于 2022-02-19 16:35:05
回答 1查看 1.2K关注 0票数 2

我试图在go中签署一条消息,该消息是通过hd钱包的私钥使用cosmos生成的。下面是python中的等效实现,它在提交/验证时像预期的那样生成签名消息/签名,但无法正常工作,但无法让它运行wtih Go实现。对于python实现的等效golang版本的任何输入都是非常感谢的。谢谢。

Python使用sha256,ecdsa,但是当使用等效的cyrpto/ecdsa时不会返回有效的签名。

Python

代码语言:javascript
复制
    def test_sign_message(self):
        """ Tests the ability of the signer to sing message """
      
        # Loading up the signer object to use for the operation
        signer: TestSigners = TestSigners.from_mnemonic("blast about old claw current first paste risk involve victory edit current")
        sample_payload_to_sign = "75628d14409a5126e6c882d05422c06f5eccaa192c082a9a5695a8e707109842'
        # print("test".encode("UTF-8").hex())
        s = signer.sign(sample_payload_to_sign)
        print(s)


from typing import List, Tuple, Dict, Union, Any
from hdwallet.hdwallet import HDWallet
from ecdsa.util import sigencode_der
from ecdsa.curves import SECP256k1
from ecdsa.keys import SigningKey
import mnemonic
import hashlib
import ecdsa


class TestSigners():

    HD_WALLET_PARAMS: Dict[str, Tuple[int, bool]] = {
        "purpose": (44, True),
        "coinType": (1022, True),
        "account": (0, True),
        "change": (0, False),
    }

    def __init__(
            self,
            seed: Union[bytes, bytearray, str]
    ) -> None:
        """ Instantiates a new signer object from the seed phrase

        Args:
            seed (Union[bytes, bytearray, str]): The seed phrase used to generate the public and
                private keys.
        """

        self.seed: Union[bytes, bytearray] = seed if isinstance(seed, (bytes, bytearray)) else bytearray.fromhex(seed)

    @classmethod
    def from_mnemonic(
            cls,
            mnemonic_phrase: Union[str, List[str], Tuple[str]]
    ) -> 'Signer':
        """
        Instantiates a new Signer object from the mnemonic phrase passed.

        Args:
            mnemonic_phrase (Union[str, :obj:`list` of :obj:`str`, :obj:`tuple` of :obj:`str`):
                A string, list, or a tuple of the mnemonic phrase. If the argument is passed as an
                iterable, then it will be joined with a space.

        Returns:
            Signer: A new signer initalized through the mnemonic phrase.
        """

        # If the supplied mnemonic phrase is a list then convert it to a string
        if isinstance(mnemonic_phrase, (list, tuple)):
            mnemonic_string: str = " ".join(mnemonic_phrase)
        else:
            mnemonic_string: str = mnemonic_phrase

        mnemonic_string: str = " ".join(mnemonic_phrase) if isinstance(mnemonic_phrase,
                                                                       (list, tuple)) else mnemonic_phrase

        return cls(mnemonic.Mnemonic.to_seed(mnemonic_string))

    def public_key(
            self,
            index: int = 0
    ) -> str:
        """
        Gets the public key for the signer for the specified account index

        Args:
            index (int): The account index to get the public keys for.

        Returns:
            str: A string of the public key for the wallet
        """

        return str(self.hdwallet(index).public_key())

    def private_key(
            self,
            index: int = 0
    ) -> str:
        """
        Gets the private key for the signer for the specified account index

        Args:
            index (int): The account index to get the private keys for.

        Returns:
            str: A string of the private key for the wallet
        """

        return str(self.hdwallet(index).private_key())

    def hdwallet(
            self,
            index: int = 0
    ) -> HDWallet:
        """
        Creates an HDWallet object suitable for the Radix blockchain with the passed account index.

        Args:
            index (int): The account index to create the HDWallet object for.

        Returns:
            HDWallet: An HD wallet object created with the Radix Parameters for a given account
                index.
        """

        hdwallet: HDWallet = HDWallet()
        hdwallet.from_seed(seed=self.seed.hex())
        for _, values_tuple in self.HD_WALLET_PARAMS.items():
            value, hardened = values_tuple
            hdwallet.from_index(value, hardened=hardened)
        hdwallet.from_index(index, True)

        return hdwallet

    def sign(
            self,
            data: str,
            index: int = 0
    ) -> str:
        """
        Signs the given data using the private keys for the account at the specified account index.

        Arguments:
            data (str): A string of the data which we wish to sign.
            index (int): The account index to get the private keys for.

        Returns:
            str: A string of the signed data
        """

        signing_key: SigningKey = ecdsa.SigningKey.from_string(  # type: ignore
            string=bytearray.fromhex(self.private_key(index)),
            curve=SECP256k1,
            hashfunc=hashlib.sha256
        )

        return signing_key.sign_digest(  # type: ignore
            digest=bytearray.fromhex(data),
            sigencode=sigencode_der
        ).hex()

去(不工作)

代码语言:javascript
复制
package main

import (
    "encoding/hex"
    "fmt"
    "log"

    "github.com/cosmos/cosmos-sdk/crypto/hd"
    "github.com/cosmos/go-bip39"
    "github.com/decred/dcrd/bech32"
    "github.com/tendermint/tendermint/crypto/secp256k1"
)

func main() {

   seed := bip39.NewSeed("blast about old claw current first paste risk involve victory edit current", "")
    fmt.Println("Seed: ", hex.EncodeToString(seed)) // Seed:  dd5ffa7088c0fa4c665085bca7096a61e42ba92e7243a8ad7fbc6975a4aeea1845c6b668ebacd024fd2ca215c6cd510be7a9815528016af3a5e6f47d1cca30dd

    master, ch := hd.ComputeMastersFromSeed(seed)
    path := "m/44'/1022'/0'/0/0'"
    priv, err := hd.DerivePrivateKeyForPath(master, ch, path)
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println("Derivation Path: ", path)                 // Derivation Path:  m/44'/118'/0'/0/0'
    fmt.Println("Private Key: ", hex.EncodeToString(priv)) // Private Key:  69668f2378b43009b16b5c6eb5e405d9224ca2a326a65a17919e567105fa4e5a

    var privKey = secp256k1.PrivKey(priv)
    pubKey := privKey.PubKey()
    fmt.Println("Public Key: ", hex.EncodeToString(pubKey.Bytes())) // Public Key:  03de79435cbc8a799efc24cdce7d3b180fb014d5f19949fb8d61de3f21b9f6c1f8

    //str := "test"
    str := "75628d14409a5126e6c882d05422c06f5eccaa192c082a9a5695a8e707109842"
    //hx := hex.EncodeToString([]byte(str))
    //fmt.Println(hx)
    sign, err := privKey.Sign([]byte(str))
    if err != nil {
        return
    }

    fmt.Println(hex.EncodeToString(sign))
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-02-20 14:48:23

这两种代码都返回作为私钥编码的十六进制。

代码语言:javascript
复制
33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56

和压缩的公钥

代码语言:javascript
复制
026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b

由于这两个代码提供相同的密钥,问题必须与签名!

使用用于签名UTF8编码的测试消息test,其SHA256哈希为十六进制编码的9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

注1:如果按照注释中的说明使用双SHA256散列,则test的SHA256哈希将用作测试消息,而不是test。除此之外,进一步的处理也是一样的。

Python和Go代码目前是不兼容的,因为它们在签名和签名格式上有所不同:

  • 关于签名:在Python代码中,哈希消息被传递。这是正确的,因为sign_digest()不散列消息(参见这里),因此哈希消息被签名。 相反,Go代码中的sign()会散列消息(参见这里),因此必须传递消息本身,才能使处理在功能上与Python代码相同。
  • 关于签名格式: Python代码使用ASN.1/DER格式,而Go代码使用IEEE P1363格式。 因此,必须在Go代码中执行从IEEE P1363到ASN.1/DER的转换:

这样,固定的Go代码是:

代码语言:javascript
复制
package main

import (
    "encoding/hex"
    "fmt"

    "math/big"

    "github.com/cosmos/cosmos-sdk/crypto/hd"
    "github.com/cosmos/go-bip39"
    "github.com/tendermint/tendermint/crypto/secp256k1"

    //"github.com/btcsuite/btcd/btcec"
    "golang.org/x/crypto/cryptobyte"
    "golang.org/x/crypto/cryptobyte/asn1"
)

func main() {

    //
    // Derive private and public key (this part works)
    //
    seed := bip39.NewSeed("blast about old claw current first paste risk involve victory edit current", "")
    fmt.Println("Seed: ", hex.EncodeToString(seed)) // Seed:  dd5ffa7088c0fa4c665085bca7096a61e42ba92e7243a8ad7fbc6975a4aeea1845c6b668ebacd024fd2ca215c6cd510be7a9815528016af3a5e6f47d1cca30dd

    master, ch := hd.ComputeMastersFromSeed(seed)
    path := "m/44'/1022'/0'/0/0'"
    priv, _ := hd.DerivePrivateKeyForPath(master, ch, path)
    fmt.Println("Derivation Path: ", path)                 // Derivation Path:  m/44'/1022'/0'/0/0'
    fmt.Println("Private Key: ", hex.EncodeToString(priv)) // Private Key:  33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56

    var privKey = secp256k1.PrivKey(priv)
    pubKey := privKey.PubKey()
    fmt.Println("Public Key: ", hex.EncodeToString(pubKey.Bytes())) // Public Key:  026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b

    //
    // Sign (this part needs to be fixed)
    //
    data := "test"

    signature, _ := privKey.Sign([]byte(data))
    fmt.Println(hex.EncodeToString(signature))

    rVal := new(big.Int)
    rVal.SetBytes(signature[0:32])
    sVal := new(big.Int)
    sVal.SetBytes(signature[32:64])
    var b cryptobyte.Builder
    b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
        b.AddASN1BigInt(rVal)
        b.AddASN1BigInt(sVal)
    })
    signatureDER, _ := b.Bytes()
    fmt.Println("Signature, DER: ", hex.EncodeToString(signatureDER))

    /*
        hash, _ := hex.DecodeString("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")

        // Sign without hashing
        privateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), priv)
        signature, _ := privateKey.Sign(hash[:])

        // Convert to ASN1/DER
        rVal := new(big.Int)
        rVal.SetBytes(signature.R.Bytes())
        sVal := new(big.Int)
        sVal.SetBytes(signature.S.Bytes())
        var b cryptobyte.Builder
        b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
            b.AddASN1BigInt(rVal)
            b.AddASN1BigInt(sVal)
        })
        signatureDER, _ := b.Bytes()
        fmt.Println("Signature, DER: ", hex.EncodeToString(signatureDER))
    */
}

备注2:如果原始消息在Go代码中不可用,但只有散列,则签名需要一个不散列的函数。

tendermint/crypto/secp256k1包不支持这一点,但是tendermint/crypto/secp256k1内部使用btcsuite/btcd/btcec。

这是在注释掉代码中实现的。

产出如下:

代码语言:javascript
复制
Seed:  dd5ffa7088c0fa4c665085bca7096a61e42ba92e7243a8ad7fbc6975a4aeea1845c6b668ebacd024fd2ca215c6cd510be7a9815528016af3a5e6f47d1cca30dd
Derivation Path:  m/44'/1022'/0'/0/0'
Private Key:  33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56
Public Key:  026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b
57624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e5035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b
Signature, DER:  3044022057624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e02205035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b

测试:

由于Python代码生成非确定性签名,通过比较签名进行验证是不可能的。

相反,一个可能的测试是用相同的验证代码检查这两个代码的签名。

为此,在Python代码的方法sign()中,代码行

代码语言:javascript
复制
return signing_key.sign_digest(  # type: ignore
    digest=bytearray.fromhex(data),
    sigencode=sigencode_der
).hex()

可代之以

代码语言:javascript
复制
from ecdsa.util import sigdecode_der 
signature = signing_key.sign_digest(  # from Python Code
    digest=bytearray.fromhex(data),
    sigencode=sigencode_der
)
#signature = bytes.fromhex('3044022057624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e02205035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b') # from Go code    
verifying_key = signing_key.verifying_key
verified = verifying_key.verify_digest(signature, digest=bytearray.fromhex(data), sigdecode=sigdecode_der)
print(verified)
return signature.hex()

测试表明,Python和Go代码签名都得到了成功验证,证明了使用Go代码生成的签名是有效的。

注3: Python代码生成一个不确定的签名,也就是说,即使输入数据相同,签名也是不同的。

相反,Go代码生成确定性签名,即相同输入数据的签名是相同的(参见这里)。

如果Go代码还应该生成非确定性签名,则必须在Go端使用其他库(但这可能实际上并不是必要的,因为非确定性和确定性变体是根据上述测试建立的算法并生成有效的签名)。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71186925

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档