如何使用Golang使用ECDSA私钥对消息进行签名?

huangapple go评论94阅读模式
英文:

how to sign a message with ecdsa privatekey using golang?

问题

我正在尝试使用Cosmos SDK在Go中使用HD钱包的私钥生成的消息进行签名。下面是在Python中等效的实现,当提交/验证正常工作时,生成了预期的签名消息/签名,但无法在Go实现中使其正常工作。非常感谢提供等效的Go版本的Python实现的任何输入。谢谢。

Python版本使用sha256,ecdsa,但是当使用等效的cyrpto/ecdsa时,无法返回有效的签名。

Python

    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()

Go(不起作用)

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))
}

请注意,我只提供了翻译,没有回答您的问题。

英文:

I am trying to sign a message in go generated via hd wallet's private key using cosmos sdk. Below is the equivalent implementation in python which generates the signed message / signature as expected when submitted/verified is working properly but unable to get it working wtih Go implementation. Any inputs for equivalent golang version of the python implementation is much appreciated. Thank you.

Python version uses sha256 , ecdsa but when using the equivalent cyrpto/ecdsa doesn't return valid signature.

> Python

    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()

> GO ( Not Working )

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))
}

答案1

得分: 3

两个代码都返回十六进制编码的私钥:

33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56

以及压缩的公钥:

026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b

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


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

备注1: 如果使用了双重 SHA256 哈希,如注释中所述,test 的 SHA256 哈希值将作为测试消息使用,而其他处理方式保持不变。

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

  • 关于签名:在 Python 代码中,传递了哈希消息。这是正确的,因为 sign_digest() 不会对消息进行哈希处理(参见这里),因此对哈希消息进行签名。相反,在 Go 代码中,sign() 对消息进行了哈希处理(参见这里),因此必须传递消息本身,以使处理与 Python 代码在功能上相同。

  • 关于签名格式:Python 代码使用 ASN.1/DER 格式,而 Go 代码使用 IEEE P1363 格式。因此,在 Go 代码中需要进行从 IEEE P1363 到 ASN.1/DER 的转换:

修复后的 Go 代码如下:

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"

	"golang.org/x/crypto/cryptobyte"
	"golang.org/x/crypto/cryptobyte/asn1"
)

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, _ := 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

	//
	// 签名(此部分需要修复)
	//
	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,它支持此功能。这在注释掉的代码中实现了。

输出结果为:

Seed:  dd5ffa7088c0fa4c665085bca7096a61e42ba92e7243a8ad7fbc6975a4aeea1845c6b668ebacd024fd2ca215c6cd510be7a9815528016af3a5e6f47d1cca30dd
Derivation Path:  m/44'/1022'/0'/0/0'
Private Key:  33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56
Public Key:  026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b
57624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e5035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b
Signature, DER:  3044022057624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e02205035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b

测试:

由于 Python 代码生成的签名是非确定性的,因此无法通过比较签名来进行验证。相反,可以使用相同的验证代码检查两个代码的签名。为此,在 Python 代码的 sign() 方法中,以下行

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

可以替换为

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 侧使用其他库(但实际上可能并不需要,因为非确定性和确定性变体都是已建立的算法,并且根据上述测试生成有效的签名)。

英文:

Both codes return hex encoded as private key

33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56

and as compressed public key

026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b

Since both codes provide the same keys, the issue must be with signing!


As test message for signing the UTF8 encoding of test is used, whose SHA256 hash is hex encoded 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08.

Remark 1: If a double SHA256 hashing is used as noted in the comment, the SHA256 hash of test is to be used as test message instead of test. Apart from that the further processing is the same.

The Python and Go code are currently incompatible because they differ in signing and signature format:<br>

  • With regard to signing: In the Python code, the hashed message is passed. This is correct because sign_digest() does not hash the message (see here), and thus the hashed message is signed.<br>
    In contrast, sign() in the Go code hashes the message (see here), so the message itself must be passed for the processing to be functionally identical to the Python code.

  • With regard to the signature format: the Python code uses the ASN.1/DER format, while the Go code uses the IEEE P1363 format.<br>
    Therefore, a conversion from IEEE P1363 to ASN.1/DER must be performed in the Go code:

With this, the fixed Go code is:

package main

import (
	&quot;encoding/hex&quot;
	&quot;fmt&quot;

	&quot;math/big&quot;

	&quot;github.com/cosmos/cosmos-sdk/crypto/hd&quot;
	&quot;github.com/cosmos/go-bip39&quot;
	&quot;github.com/tendermint/tendermint/crypto/secp256k1&quot;

	//&quot;github.com/btcsuite/btcd/btcec&quot;
	&quot;golang.org/x/crypto/cryptobyte&quot;
	&quot;golang.org/x/crypto/cryptobyte/asn1&quot;
)

func main() {

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

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

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

	//
	// Sign (this part needs to be fixed)
	//
	data := &quot;test&quot;

	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(&quot;Signature, DER: &quot;, hex.EncodeToString(signatureDER))

	/*
		hash, _ := hex.DecodeString(&quot;9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08&quot;)

		// 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(&quot;Signature, DER: &quot;, hex.EncodeToString(signatureDER))
	*/
}

Remark 2: If the original message is not available in the Go code, but only the hash, a function that does not hash is needed for signing.<br>
The tendermint/crypto/secp256k1 package does not support this, but tendermint/crypto/secp256k1 uses internally btcsuite/btcd/btcec which does.<br>
This is implemented in the commented-out code.

The output is:

Seed:  dd5ffa7088c0fa4c665085bca7096a61e42ba92e7243a8ad7fbc6975a4aeea1845c6b668ebacd024fd2ca215c6cd510be7a9815528016af3a5e6f47d1cca30dd
Derivation Path:  m/44&#39;/1022&#39;/0&#39;/0/0&#39;
Private Key:  33f34dad4bc0ce9dc320863509aed43cab33a93a29752779ae0df6dbbea33e56
Public Key:  026557fe37d5cab1cc8edf474f4baff67dbb2305f1764e42d31b09f83296f5de2b
57624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e5035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b
Signature, DER:  3044022057624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e02205035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b

Test:

Since the Python code generates a non-deterministic signature, verification by comparing the signatures is not possible.<br>
Instead, a possible test is to check the signatures of both codes with the same verification code.<br>
For this purpose, in the method sign() of the Python code the lines

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

can be replaced by

from ecdsa.util import sigdecode_der 
signature = signing_key.sign_digest(  # from Python Code
    digest=bytearray.fromhex(data),
    sigencode=sigencode_der
)
#signature = bytes.fromhex(&#39;3044022057624717f71fae8b5917cde0f82dfe6c2e2104183ba01c6a1c9f0a8e66d3303e02205035b52876d833522aace232c1d231b3aeeff303cf02d1677a240102365ce71b&#39;) # 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()

The test shows that both, the Python and Go code signatures are successfully verified, proving that the signature generated with the Go code is valid.


Remark 3: The Python code generates a non-deterministic signature, i.e. the signature is different even with identical input data.<br>
In contrast, the Go code generates a deterministic signature, i.e. the signature is the same for identical input data (see here).

If the Go code should also generate a non-deterministic signature, other libraries must be used on the Go side (but this might not actually be necessary, since the non-deterministic and the deterministic variant are established algorithms and generate valid signatures in accordance with the above test).

huangapple
  • 本文由 发表于 2022年2月20日 00:35:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/71186925.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定