Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions bech32.patch
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
diff --git a/ref/c/segwit_addr.c b/ref/c/segwit_addr.c
index 390b3da..d6f100c 100644
index 390b3da..bf199d5 100644
--- a/ref/c/segwit_addr.c
+++ b/ref/c/segwit_addr.c
@@ -67,7 +67,7 @@ int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t dat
chk = bech32_polymod_step(chk) ^ (ch >> 5);
++i;
}
- if (i + 7 + data_len > 90) return 0;
+ if (i + 7 + data_len > BECH32_MAX_LEN) return 0;
chk = bech32_polymod_step(chk);
while (*hrp != 0) {
chk = bech32_polymod_step(chk) ^ (*hrp & 0x1f);
@@ -153,7 +153,7 @@ bech32_encoding bech32_decode(char* hrp, uint8_t *data, size_t *data_len, const
}
}
Expand All @@ -12,10 +21,28 @@ index 390b3da..d6f100c 100644
int bits = 0;
uint32_t maxv = (((uint32_t)1) << outbits) - 1;
diff --git a/ref/c/segwit_addr.h b/ref/c/segwit_addr.h
index 096870f..9a70e41 100644
index 096870f..32c0e7b 100644
--- a/ref/c/segwit_addr.h
+++ b/ref/c/segwit_addr.h
@@ -131,4 +131,14 @@ bech32_encoding bech32_decode(
@@ -93,6 +93,17 @@ int segwit_addr_decode_detailed(
const char* addr
);

+/** Longest bech32/bech32m string this build will produce or accept from
+ * bech32_encode(). BIP-173 caps segwit addresses at 90; BIP-352 silent
+ * payment addresses are ~117.
+ */
+#define BECH32_MAX_LEN 128
+
+/** Output buffer size bech32_encode() requires: strlen(hrp) + data_len + 8.
+ * Size every encode buffer with this so it tracks BECH32_MAX_LEN.
+ */
+#define BECH32_BUF_SIZE (BECH32_MAX_LEN + 8)
+
/** Encode a Bech32 or Bech32m string
*
* Out: output: Pointer to a buffer of size strlen(hrp) + data_len + 8 that
@@ -131,4 +142,14 @@ bech32_encoding bech32_decode(
const char *input
);

Expand Down
71 changes: 69 additions & 2 deletions ngu/codecs.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ STATIC mp_obj_t c_segwit_encode(mp_obj_t hrp_in, mp_obj_t witver_in, mp_obj_t pr
mp_get_buffer_raise(prog_in, &prog, MP_BUFFER_READ);


char tmp[127];
char tmp[BECH32_BUF_SIZE];

int ok = segwit_addr_encode(tmp, hrp, witver, prog.buf, prog.len);
if(!ok) {
Expand Down Expand Up @@ -153,7 +153,7 @@ STATIC mp_obj_t c_nip19_encode(mp_obj_t hrp_in, mp_obj_t prog_in)
size_t datalen = 0;
convert_bits(data, &datalen, 5, prog.buf, prog.len, 8, 1);

char tmp[127];
char tmp[BECH32_BUF_SIZE];
int ok = bech32_encode(tmp, hrp, data, datalen, enc);
if(!ok) {
mp_raise_ValueError(MP_ERROR_TEXT("nip19_encode"));
Expand Down Expand Up @@ -187,6 +187,71 @@ STATIC mp_obj_t c_nip19_decode(mp_obj_t str_in)
STATIC MP_DEFINE_CONST_FUN_OBJ_1(c_nip19_decode_obj, c_nip19_decode);


// BIP-352 Silent Payment address encoding
static mp_obj_t c_bip352_encode(size_t n_args, const mp_obj_t *args)
{
// Args: hrp, scan_key, spend_key, [version]
// version is optional, defaults to 0
const char *hrp = mp_obj_str_get_str(args[0]);

mp_buffer_info_t scan_key, spend_key;
mp_get_buffer_raise(args[1], &scan_key, MP_BUFFER_READ);
mp_get_buffer_raise(args[2], &spend_key, MP_BUFFER_READ);

// Get version (optional, defaults to 0)
int version = 0;
if (n_args >= 4) {
version = mp_obj_get_int_truncated(args[3]);
}

// Validate version per BIP-352
if (version < 0 || version > 31) {
mp_raise_ValueError(MP_ERROR_TEXT("version must be 0-31"));
}

// Validate input sizes
// scan_key: 32 bytes (scan privkey) or 33 bytes (scan compressed pubkey)
// spend_key: 33 bytes (compressed pubkey)
// The 32-byte form is not a BIP-352 address: it carries the scan privkey for
// the watch-only spscan/tspscan export, and is only meaningful under those HRPs.
if ((scan_key.len != 32 && scan_key.len != 33) || spend_key.len != 33) {
mp_raise_ValueError(MP_ERROR_TEXT("scan_key must be 32 or 33 bytes, spend_key 33"));
}

// Concatenate scan || spend (65 or 66 bytes)
size_t payload_len = scan_key.len + spend_key.len;
uint8_t payload[66];
memcpy(payload, scan_key.buf, scan_key.len);
memcpy(payload + scan_key.len, spend_key.buf, spend_key.len);

// Convert 8-bit to 5-bit encoding (max 106 symbols for 66 bytes)
uint8_t converted[106];
size_t converted_len = 0;
int conv_ok = convert_bits(converted, &converted_len, 5, payload, payload_len, 8, 1);
if (!conv_ok) {
mp_raise_ValueError(MP_ERROR_TEXT("convert_bits failed"));
}

// Build final data array with version prepended
// Total: 1 (version) + converted_len 5-bit values
uint8_t data[1 + sizeof(converted)];
data[0] = version;
memcpy(data + 1, converted, converted_len);
size_t datalen = 1 + converted_len;

bech32_encoding enc = BECH32_ENCODING_BECH32M;

char tmp[BECH32_BUF_SIZE];
int ok = bech32_encode(tmp, hrp, data, datalen, enc);
if(!ok) {
mp_raise_ValueError(MP_ERROR_TEXT("bip352_encode"));
}

return mp_obj_new_str(tmp, strlen(tmp));
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(c_bip352_encode_obj, 3, 4, c_bip352_encode);


STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_codecs) },

Expand All @@ -201,6 +266,8 @@ STATIC const mp_rom_map_elem_t globals_table[] = {

{ MP_ROM_QSTR(MP_QSTR_nip19_encode), MP_ROM_PTR(&c_nip19_encode_obj) },
{ MP_ROM_QSTR(MP_QSTR_nip19_decode), MP_ROM_PTR(&c_nip19_decode_obj) },

{ MP_ROM_QSTR(MP_QSTR_bip352_encode), MP_ROM_PTR(&c_bip352_encode_obj) },
};

STATIC MP_DEFINE_CONST_DICT(globals_table_obj, globals_table);
Expand Down
135 changes: 135 additions & 0 deletions ngu/k1.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// - see test_k1.py
//
#include "py/runtime.h"
#include "py/objint.h"
#include "py/objlist.h" // For list-related functions
#include "random.h"
#include <string.h>
Expand Down Expand Up @@ -648,6 +649,99 @@ STATIC mp_obj_t s_keypair_ecdh_multiply(mp_obj_t self_in, mp_obj_t other_point_i
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(s_keypair_ecdh_multiply_obj, s_keypair_ecdh_multiply);

// BIP-352 Silent Payments: Scalar multiplication
// returns scalar * pubkey, as 33-byte compressed bytes
static mp_obj_t s_ec_pubkey_tweak_mul(mp_obj_t pubkey_in, mp_obj_t scalar_in) {
sec_setup_ctx();

// Parse input pubkey
mp_buffer_info_t pubkey_buf;
mp_get_buffer_raise(pubkey_in, &pubkey_buf, MP_BUFFER_READ);

secp256k1_pubkey pubkey;
int ok = secp256k1_ec_pubkey_parse(lib_ctx, &pubkey, pubkey_buf.buf,
pubkey_buf.len);
if (!ok) {
mp_raise_ValueError(MP_ERROR_TEXT("secp256k1_ec_pubkey_parse"));
}

// Get scalar (32 bytes)
mp_buffer_info_t scalar;
mp_get_buffer_raise(scalar_in, &scalar, MP_BUFFER_READ);
if (scalar.len != 32) {
mp_raise_ValueError(MP_ERROR_TEXT("scalar len != 32"));
}

// Multiply: result = scalar * pubkey
ok = secp256k1_ec_pubkey_tweak_mul(lib_ctx, &pubkey, scalar.buf);
if (!ok) {
mp_raise_ValueError(MP_ERROR_TEXT("secp256k1_ec_pubkey_tweak_mul"));
}

// Serialize result as compressed pubkey (33 bytes)
uint8_t output[33];
size_t outlen = sizeof(output);
secp256k1_ec_pubkey_serialize(lib_ctx, output, &outlen, &pubkey,
SECP256K1_EC_COMPRESSED);

return mp_obj_new_bytes(output, outlen);
}
static MP_DEFINE_CONST_FUN_OBJ_2(s_ec_pubkey_tweak_mul_obj, s_ec_pubkey_tweak_mul);

// BIP-352 Silent Payments: N-ary point addition
// returns the sum of a list of points, as 33-byte compressed bytes
static mp_obj_t s_ec_pubkey_combine(mp_obj_t pubkeys_in) {
sec_setup_ctx();

size_t n;
mp_obj_t *items;
mp_obj_get_array(pubkeys_in, &n, &items);
if (n == 0) {
mp_raise_ValueError(MP_ERROR_TEXT("Empty pubkeys list"));
}

// fold pairwise to minimize memory use with large list
secp256k1_pubkey result;
bool result_inf = true; // running sum starts at the point at infinity
for (size_t i = 0; i < n; i++) {
mp_buffer_info_t buf;
mp_get_buffer_raise(items[i], &buf, MP_BUFFER_READ);

secp256k1_pubkey next;
if (!secp256k1_ec_pubkey_parse(lib_ctx, &next, buf.buf, buf.len)) {
mp_raise_ValueError(MP_ERROR_TEXT("secp256k1_ec_pubkey_parse"));
}

if (result_inf) {
result = next; // infinity + next == next
result_inf = false;
} else {
// output must not alias an input: combine() zeroes it before reading
secp256k1_pubkey sum;
const secp256k1_pubkey *pair[2] = { &result, &next };

// a 2-way combine fails only when the sum is the point at infinity;
// track that so only an infinite *total* is an error
if (secp256k1_ec_pubkey_combine(lib_ctx, &sum, pair, 2)) {
result = sum;
} else {
result_inf = true;
}
}
}

if (result_inf) {
mp_raise_ValueError(MP_ERROR_TEXT("secp256k1_ec_pubkey_combine"));
}

uint8_t output[33];
size_t outlen = sizeof(output);
secp256k1_ec_pubkey_serialize(lib_ctx, output, &outlen, &result,
SECP256K1_EC_COMPRESSED);

return mp_obj_new_bytes(output, outlen);
}
static MP_DEFINE_CONST_FUN_OBJ_1(s_ec_pubkey_combine_obj, s_ec_pubkey_combine);

// MuSig2

Expand Down Expand Up @@ -1322,6 +1416,41 @@ STATIC const mp_obj_type_t s_keypair_type = {
.locals_dict = (void *)&s_keypair_locals_dict,
};

// Generator point G (33-byte compressed)
static mp_obj_t s_generator(void) {
// secp256k1 generator point G in compressed format
// 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
static const uint8_t generator_bytes[33] = {
0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb,
0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b,
0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28,
0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17,
0x98
};
return mp_obj_new_bytes(generator_bytes, 33);
}
static MP_DEFINE_CONST_FUN_OBJ_0(s_generator_obj, s_generator);

// Curve order n constant
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
static const uint8_t secp256k1_order_bytes[32] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41
};

// Curve order n (32 bytes, big-endian)
static mp_obj_t s_curve_order(void) {
return mp_obj_new_bytes(secp256k1_order_bytes, 32);
}
static MP_DEFINE_CONST_FUN_OBJ_0(s_curve_order_obj, s_curve_order);

// Curve order n as integer (avoids int.from_bytes conversion in Python)
static mp_obj_t s_curve_order_int(void) {
return mp_obj_int_from_bytes_impl(true, 32, secp256k1_order_bytes);
}
static MP_DEFINE_CONST_FUN_OBJ_0(s_curve_order_int_obj, s_curve_order_int);

STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_secp256k1) },
Expand All @@ -1347,6 +1476,12 @@ STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_musig_nonce_process), MP_ROM_PTR(&s_musig_nonce_process_obj) },
{ MP_ROM_QSTR(MP_QSTR_musig_partial_sign), MP_ROM_PTR(&s_musig_partial_sign_obj) },
{ MP_ROM_QSTR(MP_QSTR_musig_partial_sig_agg), MP_ROM_PTR(&s_musig_partial_sig_agg_obj) },

{ MP_ROM_QSTR(MP_QSTR_ec_pubkey_tweak_mul), MP_ROM_PTR(&s_ec_pubkey_tweak_mul_obj) },
{ MP_ROM_QSTR(MP_QSTR_ec_pubkey_combine), MP_ROM_PTR(&s_ec_pubkey_combine_obj) },
{ MP_ROM_QSTR(MP_QSTR_generator), MP_ROM_PTR(&s_generator_obj) },
{ MP_ROM_QSTR(MP_QSTR_curve_order), MP_ROM_PTR(&s_curve_order_obj) },
{ MP_ROM_QSTR(MP_QSTR_curve_order_int), MP_ROM_PTR(&s_curve_order_int_obj) },
};

STATIC MP_DEFINE_CONST_DICT(globals_table_obj, globals_table);
Expand Down
Loading