Add Cashu blinding helpers and secp256k1 point ops
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user