Add Cashu blinding helpers and secp256k1 point ops

This commit is contained in:
Your Name
2026-03-19 18:45:17 -04:00
parent cb4ff93906
commit 13fd364ca3
3 changed files with 234 additions and 0 deletions
+153
View File
@@ -10,6 +10,22 @@
#include <stdio.h>
#include <ctype.h>
/* private secp256k1 wrappers from utils.c */
typedef struct {
unsigned char data[64];
} nostr_secp256k1_pubkey;
int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey* pubkey, const unsigned char* input, size_t inputlen);
int nostr_secp256k1_ec_pubkey_create(nostr_secp256k1_pubkey* pubkey, const unsigned char* privkey);
int nostr_secp256k1_ec_pubkey_serialize_compressed(unsigned char* output, const nostr_secp256k1_pubkey* pubkey);
int nostr_secp256k1_ec_seckey_verify(const unsigned char* seckey);
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);
int nostr_secp256k1_ec_pubkey_tweak_mul(nostr_secp256k1_pubkey* pubkey, const unsigned char* tweak32);
int nostr_secp256k1_ec_pubkey_negate(nostr_secp256k1_pubkey* pubkey);
int nostr_secp256k1_ec_pubkey_combine(nostr_secp256k1_pubkey* out,
const nostr_secp256k1_pubkey* const* ins,
size_t n);
#include <curl/curl.h>
typedef struct {
@@ -1270,3 +1286,140 @@ void cashu_free_decoded_token(cashu_decoded_token_t* token) {
free(token->proofs);
memset(token, 0, sizeof(*token));
}
static int cashu_hash_to_curve_pubkey(const char* secret, nostr_secp256k1_pubkey* out_y) {
if (!secret || !out_y) return NOSTR_ERROR_INVALID_INPUT;
unsigned char digest[32] = {0};
if (nostr_sha256((const unsigned char*)secret, strlen(secret), digest) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char candidate[33] = {0};
for (int counter = 0; counter < 1024; counter++) {
unsigned char h[32] = {0};
unsigned char data[36] = {0};
memcpy(data, digest, 32);
data[32] = (unsigned char)((counter >> 24) & 0xff);
data[33] = (unsigned char)((counter >> 16) & 0xff);
data[34] = (unsigned char)((counter >> 8) & 0xff);
data[35] = (unsigned char)(counter & 0xff);
if (nostr_sha256(data, sizeof(data), h) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
candidate[0] = 0x02;
memcpy(candidate + 1, h, 32);
if (nostr_secp256k1_ec_pubkey_parse(out_y, candidate, sizeof(candidate))) {
return NOSTR_SUCCESS;
}
candidate[0] = 0x03;
if (nostr_secp256k1_ec_pubkey_parse(out_y, candidate, sizeof(candidate))) {
return NOSTR_SUCCESS;
}
}
return NOSTR_ERROR_CRYPTO_FAILED;
}
int cashu_blind_message(const char* secret,
const char* mint_pubkey_hex,
char out_B_hex[67],
unsigned char out_r[32]) {
if (!secret || !mint_pubkey_hex || !out_B_hex || !out_r) {
return NOSTR_ERROR_INVALID_INPUT;
}
unsigned char mint_pubkey[33] = {0};
if (strlen(mint_pubkey_hex) != 66 || nostr_hex_to_bytes(mint_pubkey_hex, mint_pubkey, 33) != 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
nostr_secp256k1_pubkey Y;
if (cashu_hash_to_curve_pubkey(secret, &Y) != NOSTR_SUCCESS) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char r[32] = {0};
int tries = 0;
do {
if (!nostr_secp256k1_get_random_bytes(r, sizeof(r))) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
tries++;
if (tries > 64) return NOSTR_ERROR_CRYPTO_FAILED;
} while (!nostr_secp256k1_ec_seckey_verify(r));
nostr_secp256k1_pubkey rG;
if (!nostr_secp256k1_ec_pubkey_create(&rG, r)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
const nostr_secp256k1_pubkey* pts[2] = { &Y, &rG };
nostr_secp256k1_pubkey B;
if (!nostr_secp256k1_ec_pubkey_combine(&B, pts, 2)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char B_bytes[33] = {0};
if (!nostr_secp256k1_ec_pubkey_serialize_compressed(B_bytes, &B)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
nostr_bytes_to_hex(B_bytes, 33, out_B_hex);
memcpy(out_r, r, 32);
return NOSTR_SUCCESS;
}
int cashu_unblind_signature(const char* C_blinded_hex,
const char* mint_pubkey_hex,
const unsigned char r[32],
char out_C_hex[67]) {
if (!C_blinded_hex || !mint_pubkey_hex || !r || !out_C_hex) {
return NOSTR_ERROR_INVALID_INPUT;
}
unsigned char Cb_bytes[33] = {0};
unsigned char M_bytes[33] = {0};
if (strlen(C_blinded_hex) != 66 || nostr_hex_to_bytes(C_blinded_hex, Cb_bytes, 33) != 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
if (strlen(mint_pubkey_hex) != 66 || nostr_hex_to_bytes(mint_pubkey_hex, M_bytes, 33) != 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
if (!nostr_secp256k1_ec_seckey_verify(r)) {
return NOSTR_ERROR_INVALID_INPUT;
}
nostr_secp256k1_pubkey Cb;
nostr_secp256k1_pubkey K;
if (!nostr_secp256k1_ec_pubkey_parse(&Cb, Cb_bytes, 33)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
if (!nostr_secp256k1_ec_pubkey_parse(&K, M_bytes, 33)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
if (!nostr_secp256k1_ec_pubkey_tweak_mul(&K, r)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
if (!nostr_secp256k1_ec_pubkey_negate(&K)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
const nostr_secp256k1_pubkey* pts[2] = { &Cb, &K };
nostr_secp256k1_pubkey C;
if (!nostr_secp256k1_ec_pubkey_combine(&C, pts, 2)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char out_bytes[33] = {0};
if (!nostr_secp256k1_ec_pubkey_serialize_compressed(out_bytes, &C)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
nostr_bytes_to_hex(out_bytes, 33, out_C_hex);
return NOSTR_SUCCESS;
}
+10
View File
@@ -214,6 +214,16 @@ int cashu_decode_token(const char* token_string,
void cashu_free_decoded_token(cashu_decoded_token_t* token);
int cashu_blind_message(const char* secret,
const char* mint_pubkey_hex,
char out_B_hex[67],
unsigned char out_r[32]);
int cashu_unblind_signature(const char* C_blinded_hex,
const char* mint_pubkey_hex,
const unsigned char r[32],
char out_C_hex[67]);
#ifdef __cplusplus
}
#endif
+71
View File
@@ -237,6 +237,77 @@ int nostr_secp256k1_ecdh(unsigned char *result, const nostr_secp256k1_pubkey *pu
return secp256k1_ecdh(g_ctx, result, &internal_pubkey, seckey, hashfp, data);
}
int nostr_secp256k1_ec_pubkey_tweak_mul(nostr_secp256k1_pubkey* pubkey, const unsigned char* tweak32) {
if (g_ctx == NULL || pubkey == NULL || tweak32 == NULL) {
return 0;
}
secp256k1_pubkey internal_pubkey;
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
if (!secp256k1_ec_pubkey_tweak_mul(g_ctx, &internal_pubkey, tweak32)) {
return 0;
}
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
return 1;
}
int nostr_secp256k1_ec_pubkey_negate(nostr_secp256k1_pubkey* pubkey) {
if (g_ctx == NULL || pubkey == NULL) {
return 0;
}
secp256k1_pubkey internal_pubkey;
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
if (!secp256k1_ec_pubkey_negate(g_ctx, &internal_pubkey)) {
return 0;
}
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
return 1;
}
int nostr_secp256k1_ec_pubkey_combine(nostr_secp256k1_pubkey* out,
const nostr_secp256k1_pubkey* const* ins,
size_t n) {
if (g_ctx == NULL || out == NULL || ins == NULL || n == 0) {
return 0;
}
secp256k1_pubkey* parsed = (secp256k1_pubkey*)calloc(n, sizeof(secp256k1_pubkey));
const secp256k1_pubkey** ptrs = (const secp256k1_pubkey**)calloc(n, sizeof(secp256k1_pubkey*));
if (!parsed || !ptrs) {
free(parsed);
free(ptrs);
return 0;
}
for (size_t i = 0; i < n; i++) {
if (!ins[i]) {
free(parsed);
free(ptrs);
return 0;
}
memcpy(&parsed[i], ins[i]->data, sizeof(secp256k1_pubkey));
ptrs[i] = &parsed[i];
}
secp256k1_pubkey combined;
int ok = secp256k1_ec_pubkey_combine(g_ctx, &combined, ptrs, n);
free(parsed);
free(ptrs);
if (!ok) {
return 0;
}
memcpy(out->data, &combined, sizeof(secp256k1_pubkey));
return 1;
}
int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len) {
if (buf == NULL || len == 0) {
return 0;