secp256k1 extmod: new pubkey_decompress() function

This commit is contained in:
The MMGen Project 2026-08-15 14:13:08 +00:00
commit 75d26b134f
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 38 additions and 8 deletions

View file

@ -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}
};

View file

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

View file

@ -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):