From 75d26b134f5d4f956095253608cd702f63d43bd2 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 15 Aug 2026 14:13:08 +0000 Subject: [PATCH] secp256k1 extmod: new `pubkey_decompress()` function --- extmod/secp256k1mod.c | 28 ++++++++++++++++++++++++++++ mmgen/bip_hd/__init__.py | 10 +++------- test/modtest_d/ecc.py | 8 +++++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/extmod/secp256k1mod.c b/extmod/secp256k1mod.c index bd1a7dc1..013e0104 100755 --- a/extmod/secp256k1mod.c +++ b/extmod/secp256k1mod.c @@ -197,6 +197,28 @@ static PyObject * pubkey_check(PyObject *self, PyObject *args) { return Py_BuildValue("I", 1); } +static PyObject * pubkey_decompress(PyObject *self, PyObject *args) { + const unsigned char * in_pubkey_bytes; + Py_ssize_t in_pubkey_bytes_len; + if (!PyArg_ParseTuple(args, "y#", &in_pubkey_bytes, &in_pubkey_bytes_len)) { + PyErr_SetString(PyExc_ValueError, "Unable to parse extension mod arguments"); + return NULL; + } + secp256k1_context *ctx = create_context(1); + secp256k1_pubkey pubkey; + if (!pubkey_parse_with_check(ctx, &pubkey, in_pubkey_bytes, in_pubkey_bytes_len)) { + return NULL; + } + size_t pubkey_bytes_len = 65; + unsigned char pubkey_bytes[pubkey_bytes_len]; + if (secp256k1_ec_pubkey_serialize(ctx, pubkey_bytes, &pubkey_bytes_len, &pubkey, + SECP256K1_EC_UNCOMPRESSED) != 1) { + PyErr_SetString(PyExc_RuntimeError, "Public key serialization failed"); + return NULL; + } + return Py_BuildValue("y#", pubkey_bytes, pubkey_bytes_len); +} + /* * returns 64-byte serialized signature (r + s) plus integer recovery ID in range 0-3 */ @@ -396,6 +418,12 @@ static PyMethodDef secp256k1_methods[] = { METH_VARARGS, "Recover a serialized pubkey from a recoverable signature plus signed message" }, + { + "pubkey_decompress", + pubkey_decompress, + METH_VARARGS, + "Convert a compressed or uncompressed serialized pubkey into an uncompressed serialized pubkey" + }, {NULL, NULL} }; diff --git a/mmgen/bip_hd/__init__.py b/mmgen/bip_hd/__init__.py index 3f954e12..63dc7218 100755 --- a/mmgen/bip_hd/__init__.py +++ b/mmgen/bip_hd/__init__.py @@ -26,7 +26,7 @@ from ..addr import MMGenAddrType from ..key import PrivKey from ..protocol import CoinProtocol, init_proto from ..proto.btc.common import hash160, b58chk_encode, b58chk_decode -from ..proto.secp256k1.secp256k1 import pubkey_tweak_add, pubkey_check +from ..proto.secp256k1.secp256k1 import pubkey_tweak_add, pubkey_check, pubkey_decompress from . import chainparams chainparams_data = chainparams.parse_data() @@ -56,10 +56,6 @@ def compress_pubkey(pubkey_bytes): # see: proto.secp256k1.keygen.pubkey_format() return (b'\x02', b'\x03')[pubkey_bytes[-1] & 1] + pubkey_bytes[1:33] -def decompress_pubkey(pubkey_bytes): - import ecdsa - return b'\x04' + ecdsa.VerifyingKey.from_string(pubkey_bytes, curve=ecdsa.curves.SECP256k1).to_string() - class Bip32ExtendedKey(Lockable): def __init__(self, key_b58): @@ -73,7 +69,7 @@ class Bip32ExtendedKey(Lockable): # Serialization: # ver_bytes | depth | par_print | idx | chaincode | serialized_key - # 0:4 (4) | 4 (1) | 5:9 (4) | 9:13 (4) | 13:45 (32) | 45(46): 33(32) + # 0:4 (4) | 4 (1) | 5:9 (4) | 9:13 (4) | 13:45 (32) | 45: (33), or 46: (32) ver_hex = key[:4].hex() bipnum, cp_entry = parse_version_bytes(ver_hex) @@ -273,7 +269,7 @@ class BipHDNode(Lockable): def address(self): return self.cfg.ag.to_addr( keygen_public_data( - pubkey = self.key if self.cfg.addr_type.compressed else decompress_pubkey(self.key), + pubkey = self.key if self.cfg.addr_type.compressed else pubkey_decompress(self.key), viewkey_bytes = None, pubkey_type = self.cfg.addr_type.pubkey_type, compressed = self.cfg.addr_type.compressed) diff --git a/test/modtest_d/ecc.py b/test/modtest_d/ecc.py index 2f93a289..46498cb8 100755 --- a/test/modtest_d/ecc.py +++ b/test/modtest_d/ecc.py @@ -8,6 +8,7 @@ from mmgen.proto.secp256k1.secp256k1 import ( pubkey_gen, pubkey_tweak_add, pubkey_check, + pubkey_decompress, sign_msghash, pubkey_recover, verify_sig) @@ -101,7 +102,7 @@ class unit_tests: return True def pubkey_ops(self, name, ut): - vmsg(' Generating pubkey, adding scalar 123456789 to pubkey:') + vmsg(' Generating, checking, decompressing, and adding scalar to pubkey:') pk_addend_bytes = int.to_bytes(123456789, length=32, byteorder='big') for privkey in ( @@ -126,6 +127,11 @@ class unit_tests: assert len(res1) == length assert res1 == res2 + res3 = pubkey_decompress(res2) + pubkey_check(res3) + assert res3[0] == 4 + assert len(res3) == 65 + return True def pubkey_errors(self, name, ut):