UltrafastSecp256k1 3.50.0
Ultra high-performance secp256k1 elliptic curve cryptography library
Loading...
Searching...
No Matches
ufsecp_impl.cpp
Go to the documentation of this file.
1/* ============================================================================
2 * UltrafastSecp256k1 -- ufsecp C ABI Implementation
3 * ============================================================================
4 * Wraps the C++ UltrafastSecp256k1 library behind the opaque ufsecp_ctx and
5 * the ufsecp_* function surface. All conversions between opaque byte arrays
6 * and internal C++ types happen here -- nothing leaks.
7 *
8 * Build with: -DUFSECP_BUILDING (sets dllexport on Windows)
9 * ============================================================================ */
10
11#ifndef UFSECP_BUILDING
12#define UFSECP_BUILDING
13#endif
14
15#include "ufsecp.h"
16
17#include <cstring>
18#include <cstdint>
19#include <cstdlib>
20#include <algorithm>
21#include <array>
22#include <limits>
23#include <string>
24#include <new>
25#include <vector>
26
27/* -- UltrafastSecp256k1 C++ headers ---------------------------------------- */
28#include "secp256k1/scalar.hpp"
29#include "secp256k1/point.hpp"
30#include "secp256k1/field.hpp"
31#include "secp256k1/ecdsa.hpp"
32#include "secp256k1/schnorr.hpp"
33#include "secp256k1/ecdh.hpp"
35#include "secp256k1/ct/sign.hpp"
39#include "secp256k1/sha256.hpp"
40#include "secp256k1/address.hpp"
41#include "secp256k1/bip32.hpp"
42#include "secp256k1/taproot.hpp"
43#include "secp256k1/bip143.hpp"
44#include "secp256k1/bip144.hpp"
45#include "secp256k1/segwit.hpp"
46#include "secp256k1/init.hpp"
47#include "secp256k1/bip39.hpp"
49#include "secp256k1/musig2.hpp"
50#include "secp256k1/frost.hpp"
51#include "secp256k1/adaptor.hpp"
53#include "secp256k1/zk.hpp"
54#include "secp256k1/sha512.hpp"
58#include "secp256k1/ecies.hpp"
61
62#if defined(SECP256K1_BIP324)
64#include "secp256k1/hkdf.hpp"
66#include "secp256k1/bip324.hpp"
67#endif
68
69#if defined(SECP256K1_BUILD_ETHEREUM)
73#endif
74
78
79/* ===========================================================================
80 * Context definition (opaque to callers)
81 * =========================================================================== */
82
88
89static void ctx_clear_err(ufsecp_ctx* ctx) {
90 ctx->last_err = UFSECP_OK;
91 ctx->last_msg[0] = '\0';
92}
93
94static ufsecp_error_t ctx_set_err(ufsecp_ctx* ctx, ufsecp_error_t err, const char* msg) {
95 ctx->last_err = err;
96 if (msg) {
97 /* Portable safe copy without MSVC deprecation warning */
98 size_t i = 0;
99 /* cppcheck-suppress arrayIndexOutOfBoundsCond ; i bounded by sizeof(last_msg)-1 */
100 for (; i < sizeof(ctx->last_msg) - 1 && msg[i]; ++i) {
101 ctx->last_msg[i] = msg[i];
102}
103 ctx->last_msg[i] = '\0';
104 } else {
105 ctx->last_msg[0] = '\0';
106 }
107 return err;
108}
109
110/* ===========================================================================
111 * Internal helpers (same pattern as existing c_api, but with error model)
112 * =========================================================================== */
113
114// All scalar parsing uses the strict variants below.
115// Message hashes (32-byte) are handled as raw byte arrays (no scalar reduction).
116
117// Strict parser for secret keys: rejects 0, values >= n. No reduction.
118static inline bool scalar_parse_strict_nonzero(const uint8_t b[32], Scalar& out) {
119 std::array<uint8_t, 32> arr;
120 std::memcpy(arr.data(), b, 32);
121 return Scalar::parse_bytes_strict_nonzero(arr, out);
122}
123
124// Strict parser for tweaks: rejects values >= n, allows 0. No reduction.
125static inline bool scalar_parse_strict(const uint8_t b[32], Scalar& out) {
126 std::array<uint8_t, 32> arr;
127 std::memcpy(arr.data(), b, 32);
128 return Scalar::parse_bytes_strict(arr, out);
129}
130
131static inline void scalar_to_bytes(const Scalar& s, uint8_t out[32]) {
132 auto arr = s.to_bytes();
133 std::memcpy(out, arr.data(), 32);
134}
135
136static inline Point point_from_compressed(const uint8_t pub[33]);
137
138namespace {
139
140constexpr std::size_t kMuSig2KeyAggHeaderLen = 38;
141constexpr std::size_t kMuSig2KeyAggCoeffLen = 32;
142constexpr std::size_t kMuSig2SessionSerializedLen = 98;
143constexpr std::size_t kMuSig2SessionCountOffset = kMuSig2SessionSerializedLen;
144constexpr std::size_t kMuSig2SessionCountLen = 4;
145constexpr uint32_t kMuSig2MinParticipants = 2;
146constexpr uint32_t kMuSig2MaxKeyAggParticipants =
147 static_cast<uint32_t>((UFSECP_MUSIG2_KEYAGG_LEN - kMuSig2KeyAggHeaderLen) / kMuSig2KeyAggCoeffLen);
148
149static_assert(kMuSig2MaxKeyAggParticipants >= kMuSig2MinParticipants,
150 "MuSig2 keyagg blob must encode at least two participants");
151static_assert(kMuSig2SessionCountOffset + kMuSig2SessionCountLen <= UFSECP_MUSIG2_SESSION_LEN,
152 "MuSig2 session blob must have room for participant count metadata");
153
154static ufsecp_error_t parse_musig2_keyagg(ufsecp_ctx* ctx,
155 const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN],
157 uint32_t nk = 0;
158 std::memcpy(&nk, keyagg, 4);
159 if (nk < kMuSig2MinParticipants || nk > kMuSig2MaxKeyAggParticipants) {
160 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid keyagg participant count");
161 }
162
163 out.Q_negated = (keyagg[4] != 0);
164 out.Q = point_from_compressed(keyagg + 5);
165 if (out.Q.is_infinity()) {
166 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid aggregated key");
167 }
168
169 auto qc = out.Q.to_compressed();
170 std::memcpy(out.Q_x.data(), qc.data() + 1, 32);
171 out.key_coefficients.clear();
172 out.key_coefficients.reserve(nk);
173 for (uint32_t i = 0; i < nk; ++i) {
174 Scalar coefficient;
175 if (!scalar_parse_strict(keyagg + kMuSig2KeyAggHeaderLen + static_cast<std::size_t>(i) * kMuSig2KeyAggCoeffLen,
176 coefficient)) {
177 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid key coefficient in keyagg");
178 }
179 out.key_coefficients.push_back(coefficient);
180 }
181 return UFSECP_OK;
182}
183
184static ufsecp_error_t parse_musig2_session(ufsecp_ctx* ctx,
185 const uint8_t session[UFSECP_MUSIG2_SESSION_LEN],
187 uint32_t& participant_count_out) {
188 out.R = point_from_compressed(session);
189 if (out.R.is_infinity()) {
190 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid session R point");
191 }
192 if (!scalar_parse_strict(session + 33, out.b)) {
193 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid session scalar b");
194 }
195 if (!scalar_parse_strict(session + 65, out.e)) {
196 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid session scalar e");
197 }
198 out.R_negated = (session[97] != 0);
199
200 std::memcpy(&participant_count_out, session + kMuSig2SessionCountOffset, sizeof(participant_count_out));
201 if (participant_count_out < kMuSig2MinParticipants || participant_count_out > kMuSig2MaxKeyAggParticipants) {
202 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid session participant count");
203 }
204 return UFSECP_OK;
205}
206
207static bool checked_mul_size(std::size_t left, std::size_t right, std::size_t& out) {
208 if (left != 0 && right > std::numeric_limits<std::size_t>::max() / left) {
209 return false;
210 }
211 out = left * right;
212 return true;
213}
214
215static bool checked_add_size(std::size_t left, std::size_t right, std::size_t& out) {
216 if (right > std::numeric_limits<std::size_t>::max() - left) {
217 return false;
218 }
219 out = left + right;
220 return true;
221}
222
223/* Hard upper bound on user-supplied batch/array counts.
224 1 << 20 (~1M) is generous for any legitimate use case and prevents
225 hostile callers from triggering multi-GB allocations. */
226static constexpr std::size_t kMaxBatchN = std::size_t{1} << 20;
227
228} // namespace
229
230/* ---------------------------------------------------------------------------
231 * Exception-safety macro for extern "C" functions.
232 *
233 * C++ exceptions propagating through an extern "C" boundary are undefined
234 * behaviour. Every function that touches STL containers, std::string, or
235 * any other throwing code must be wrapped.
236 * ---------------------------------------------------------------------------*/
237#define UFSECP_CATCH_RETURN(ctx_ptr) \
238 catch (const std::bad_alloc&) { \
239 return (ctx_ptr) ? ctx_set_err(ctx_ptr, UFSECP_ERR_INTERNAL, \
240 "allocation failed") \
241 : UFSECP_ERR_INTERNAL; \
242 } catch (...) { \
243 return (ctx_ptr) ? ctx_set_err(ctx_ptr, UFSECP_ERR_INTERNAL, \
244 "internal error") \
245 : UFSECP_ERR_INTERNAL; \
246 }
247
248static inline Point point_from_compressed(const uint8_t pub[33]) {
249 // Strict: only accept 0x02/0x03 prefix, reject x >= p
250 if (pub[0] != 0x02 && pub[0] != 0x03) return Point::infinity();
251 FE x;
252 if (!FE::parse_bytes_strict(pub + 1, x)) return Point::infinity();
253
254 /* y^2 = x^3 + 7 */
255 auto x2 = x * x;
256 auto x3 = x2 * x;
257 auto y2 = x3 + FE::from_uint64(7);
258
259 /* sqrt via addition chain for (p+1)/4 */
260 auto t = y2;
261 auto a = t.square() * t;
262 auto b = a.square() * t;
263 auto c = b.square().square().square() * b;
264 auto d = c.square().square().square() * b;
265 auto e = d.square().square() * a;
266 auto f = e;
267 for (int i = 0; i < 11; ++i) f = f.square();
268 f = f * e;
269 auto g = f;
270 for (int i = 0; i < 22; ++i) g = g.square();
271 g = g * f;
272 auto h = g;
273 for (int i = 0; i < 44; ++i) h = h.square();
274 h = h * g;
275 auto j = h;
276 for (int i = 0; i < 88; ++i) j = j.square();
277 j = j * h;
278 auto k = j;
279 for (int i = 0; i < 44; ++i) k = k.square();
280 k = k * g;
281 auto m = k.square().square().square() * b;
282 auto y = m;
283 for (int i = 0; i < 23; ++i) y = y.square();
284 y = y * f;
285 for (int i = 0; i < 6; ++i) y = y.square();
286 y = y * a;
287 y = y.square().square();
288
289 // Verify sqrt: y^2 must equal y2 (reject if x has no valid y on curve)
290 if (y * y != y2) return Point::infinity();
291
292 auto y_bytes = y.to_bytes();
293 bool const y_is_odd = (y_bytes[31] & 1) != 0;
294 bool const want_odd = (pub[0] == 0x03);
295 if (y_is_odd != want_odd) {
296 y = FE::from_uint64(0) - y;
297}
298
299 return Point::from_affine(x, y);
300}
301
302static inline void point_to_compressed(const Point& p, uint8_t out[33]) {
303 auto comp = p.to_compressed();
304 std::memcpy(out, comp.data(), 33);
305}
306
307template <typename T>
309public:
310 explicit SecureEraseGuard(T* value) noexcept : value_(value) {}
313
315 if (value_ != nullptr) {
316 secp256k1::detail::secure_erase(value_, sizeof(T));
317 }
318 }
319
320private:
321 T* value_;
322};
323
324static inline void secure_erase_scalar_vector(std::vector<Scalar>& values) {
325 for (auto& value : values) {
326 secp256k1::detail::secure_erase(&value, sizeof(value));
327 }
328}
329
334
335/* ===========================================================================
336 * Version / error (stateless, no ctx needed)
337 * =========================================================================== */
338
339unsigned int ufsecp_version(void) {
341}
342
343unsigned int ufsecp_abi_version(void) {
344 return UFSECP_ABI_VERSION;
345}
346
347const char* ufsecp_version_string(void) {
349}
350
352 switch (err) {
353 case UFSECP_OK: return "OK";
354 case UFSECP_ERR_NULL_ARG: return "NULL argument";
355 case UFSECP_ERR_BAD_KEY: return "invalid private key";
356 case UFSECP_ERR_BAD_PUBKEY: return "invalid public key";
357 case UFSECP_ERR_BAD_SIG: return "invalid signature";
358 case UFSECP_ERR_BAD_INPUT: return "malformed input";
359 case UFSECP_ERR_VERIFY_FAIL: return "verification failed";
360 case UFSECP_ERR_ARITH: return "arithmetic error";
361 case UFSECP_ERR_SELFTEST: return "self-test failed";
362 case UFSECP_ERR_INTERNAL: return "internal error";
363 case UFSECP_ERR_BUF_TOO_SMALL: return "buffer too small";
364 default: return "unknown error";
365 }
366}
367
368/* ===========================================================================
369 * Context lifecycle
370 * =========================================================================== */
371
373 if (!ctx_out) return UFSECP_ERR_NULL_ARG;
374 *ctx_out = nullptr;
375
376 auto* ctx = static_cast<ufsecp_ctx*>(std::calloc(1, sizeof(ufsecp_ctx)));
377 if (!ctx) return UFSECP_ERR_INTERNAL;
378
379 ctx->last_err = UFSECP_OK;
380 ctx->last_msg[0] = '\0';
381
382 /* Run selftest once (cached globally by ensure_library_integrity) */
384 if (!ctx->selftest_ok) {
385 std::free(ctx);
386 return UFSECP_ERR_SELFTEST;
387 }
388
389 *ctx_out = ctx;
390 return UFSECP_OK;
391}
392
394 if (!src || !ctx_out) return UFSECP_ERR_NULL_ARG;
395 *ctx_out = nullptr;
396
397 auto* dst = static_cast<ufsecp_ctx*>(std::malloc(sizeof(ufsecp_ctx)));
398 if (!dst) return UFSECP_ERR_INTERNAL;
399
400 std::memcpy(dst, src, sizeof(ufsecp_ctx));
401 ctx_clear_err(dst);
402
403 *ctx_out = dst;
404 return UFSECP_OK;
405}
406
408 std::free(ctx); // free(NULL) is a no-op per C standard
409}
410
412 return ctx ? ctx->last_err : UFSECP_ERR_NULL_ARG;
413}
414
415const char* ufsecp_last_error_msg(const ufsecp_ctx* ctx) {
416 if (!ctx) return "NULL context";
417 return ctx->last_msg[0] ? ctx->last_msg : ufsecp_error_str(ctx->last_err);
418}
419
420size_t ufsecp_ctx_size(void) {
421 return sizeof(ufsecp_ctx);
422}
423
424/* ===========================================================================
425 * Private key utilities
426 * =========================================================================== */
427
429 const uint8_t privkey[32]) {
430 if (!ctx || !privkey) return UFSECP_ERR_NULL_ARG;
431 // BIP-340 strict: reject if privkey == 0 or privkey >= n (no reduction)
432 Scalar sk;
433 if (!Scalar::parse_bytes_strict_nonzero(privkey, sk)) {
434 return UFSECP_ERR_BAD_KEY;
435 }
436 secp256k1::detail::secure_erase(&sk, sizeof(sk));
437 return UFSECP_OK;
438}
439
441 if (!ctx || !privkey) return UFSECP_ERR_NULL_ARG;
442 ctx_clear_err(ctx);
443 Scalar sk;
444 if (!scalar_parse_strict_nonzero(privkey, sk)) {
445 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
446 }
447 auto neg = sk.negate();
448 secp256k1::detail::secure_erase(&sk, sizeof(sk));
449 // negate of valid nonzero scalar is always nonzero
450 scalar_to_bytes(neg, privkey);
451 secp256k1::detail::secure_erase(&neg, sizeof(neg));
452 return UFSECP_OK;
453}
454
456 const uint8_t tweak[32]) {
457 if (!ctx || !privkey || !tweak) return UFSECP_ERR_NULL_ARG;
458 ctx_clear_err(ctx);
459 Scalar sk;
460 if (!scalar_parse_strict_nonzero(privkey, sk)) {
461 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
462 }
463 Scalar tw;
464 if (!scalar_parse_strict(tweak, tw)) {
465 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "tweak >= n");
466 }
467 auto result = sk + tw;
468 secp256k1::detail::secure_erase(&sk, sizeof(sk));
469 secp256k1::detail::secure_erase(&tw, sizeof(tw));
470 if (result.is_zero()) {
471 secp256k1::detail::secure_erase(&result, sizeof(result));
472 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "tweak_add resulted in zero");
473 }
474 scalar_to_bytes(result, privkey);
475 secp256k1::detail::secure_erase(&result, sizeof(result));
476 return UFSECP_OK;
477}
478
480 const uint8_t tweak[32]) {
481 if (!ctx || !privkey || !tweak) return UFSECP_ERR_NULL_ARG;
482 ctx_clear_err(ctx);
483 Scalar sk;
484 if (!scalar_parse_strict_nonzero(privkey, sk)) {
485 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
486 }
487 Scalar tw;
488 // tweak_mul: reject tweak==0 (result would be zero) and tweak >= n
489 if (!scalar_parse_strict_nonzero(tweak, tw)) {
490 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "tweak is zero or >= n");
491 }
492 auto result = sk * tw;
493 secp256k1::detail::secure_erase(&sk, sizeof(sk));
494 secp256k1::detail::secure_erase(&tw, sizeof(tw));
495 if (result.is_zero()) {
496 secp256k1::detail::secure_erase(&result, sizeof(result));
497 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "tweak_mul resulted in zero");
498 }
499 scalar_to_bytes(result, privkey);
500 secp256k1::detail::secure_erase(&result, sizeof(result));
501 return UFSECP_OK;
502}
503
504/* ===========================================================================
505 * Public key
506 * =========================================================================== */
507
509 const uint8_t privkey[32],
510 Point& pk_out) {
511 Scalar sk;
512 if (!scalar_parse_strict_nonzero(privkey, sk)) {
513 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
514 }
515 pk_out = secp256k1::ct::generator_mul(sk);
516 secp256k1::detail::secure_erase(&sk, sizeof(sk));
517 if (pk_out.is_infinity()) {
518 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "pubkey at infinity");
519 }
520 return UFSECP_OK;
521}
522
524 const uint8_t privkey[32],
525 uint8_t pubkey33_out[33]) {
526 if (!ctx || !privkey || !pubkey33_out) return UFSECP_ERR_NULL_ARG;
527 ctx_clear_err(ctx);
528 Point pk;
529 const ufsecp_error_t err = pubkey_create_core(ctx, privkey, pk);
530 if (err != UFSECP_OK) return err;
531 point_to_compressed(pk, pubkey33_out);
532 return UFSECP_OK;
533}
534
536 const uint8_t privkey[32],
537 uint8_t pubkey65_out[65]) {
538 if (!ctx || !privkey || !pubkey65_out) return UFSECP_ERR_NULL_ARG;
539 ctx_clear_err(ctx);
540 Point pk;
541 const ufsecp_error_t err = pubkey_create_core(ctx, privkey, pk);
542 if (err != UFSECP_OK) return err;
543 auto uncomp = pk.to_uncompressed();
544 std::memcpy(pubkey65_out, uncomp.data(), 65);
545 return UFSECP_OK;
546}
547
549 const uint8_t* input, size_t input_len,
550 uint8_t pubkey33_out[33]) {
551 if (!ctx || !input || !pubkey33_out) return UFSECP_ERR_NULL_ARG;
552 ctx_clear_err(ctx);
553
554 if (input_len == 33 && (input[0] == 0x02 || input[0] == 0x03)) {
555 auto p = point_from_compressed(input);
556 if (p.is_infinity()) {
557 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "decompression failed");
558}
559 point_to_compressed(p, pubkey33_out);
560 return UFSECP_OK;
561 }
562 if (input_len == 65 && input[0] == 0x04) {
563 std::array<uint8_t, 32> x_bytes, y_bytes;
564 std::memcpy(x_bytes.data(), input + 1, 32);
565 std::memcpy(y_bytes.data(), input + 33, 32);
566 // Strict: reject x >= p or y >= p
567 FE x, y;
568 if (!FE::parse_bytes_strict(x_bytes, x) ||
569 !FE::parse_bytes_strict(y_bytes, y)) {
570 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "coordinate >= p");
571 }
572 // On-curve check: y^2 == x^3 + 7
573 auto lhs = y * y;
574 auto rhs = x * x * x + FE::from_uint64(7);
575 if (lhs != rhs) {
576 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "point not on curve");
577 }
578 auto p = Point::from_affine(x, y);
579 if (p.is_infinity()) {
580 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "point at infinity");
581}
582 point_to_compressed(p, pubkey33_out);
583 return UFSECP_OK;
584 }
585 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "expected 33 or 65 byte pubkey");
586}
587
589 const uint8_t privkey[32],
590 uint8_t xonly32_out[32]) {
591 if (!ctx || !privkey || !xonly32_out) return UFSECP_ERR_NULL_ARG;
592 ctx_clear_err(ctx);
593
594 Scalar sk;
595 if (!scalar_parse_strict_nonzero(privkey, sk)) {
596 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
597 }
598
599 auto xonly = secp256k1::schnorr_pubkey(sk);
600 secp256k1::detail::secure_erase(&sk, sizeof(sk));
601 std::memcpy(xonly32_out, xonly.data(), 32);
602 return UFSECP_OK;
603}
604
605/* ===========================================================================
606 * ECDSA
607 * =========================================================================== */
608
610 const uint8_t msg32[32],
611 const uint8_t privkey[32],
612 uint8_t sig64_out[64]) {
613 if (!ctx || !msg32 || !privkey || !sig64_out) return UFSECP_ERR_NULL_ARG;
614 ctx_clear_err(ctx);
615
616 std::array<uint8_t, 32> msg;
617 std::memcpy(msg.data(), msg32, 32);
618 Scalar sk;
619 if (!scalar_parse_strict_nonzero(privkey, sk)) {
620 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
621 }
622
623 auto sig = secp256k1::ct::ecdsa_sign(msg, sk);
624 secp256k1::detail::secure_erase(&sk, sizeof(sk));
625 // CT path returns already-normalized (low-S) signature
626 auto compact = sig.to_compact();
627 std::memcpy(sig64_out, compact.data(), 64);
628 return UFSECP_OK;
629}
630
632 const uint8_t msg32[32],
633 const uint8_t privkey[32],
634 uint8_t sig64_out[64]) {
635 if (!ctx || !msg32 || !privkey || !sig64_out) return UFSECP_ERR_NULL_ARG;
636 ctx_clear_err(ctx);
637
638 std::array<uint8_t, 32> msg;
639 std::memcpy(msg.data(), msg32, 32);
640 Scalar sk;
641 if (!scalar_parse_strict_nonzero(privkey, sk)) {
642 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
643 }
644
645 auto sig = secp256k1::ct::ecdsa_sign_verified(msg, sk);
646 secp256k1::detail::secure_erase(&sk, sizeof(sk));
647 auto compact = sig.to_compact();
648 std::memcpy(sig64_out, compact.data(), 64);
649 return UFSECP_OK;
650}
651
653 const uint8_t msg32[32],
654 const uint8_t sig64[64],
655 const uint8_t pubkey33[33]) {
656 if (!ctx || !msg32 || !sig64 || !pubkey33) return UFSECP_ERR_NULL_ARG;
657 ctx_clear_err(ctx);
658
659 std::array<uint8_t, 32> msg;
660 std::memcpy(msg.data(), msg32, 32);
661 std::array<uint8_t, 64> compact;
662 std::memcpy(compact.data(), sig64, 64);
663
665 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, ecdsasig)) {
666 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "non-canonical compact sig");
667 }
668 auto pk = point_from_compressed(pubkey33);
669 if (pk.is_infinity()) {
670 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid public key");
671 }
672
673 if (!secp256k1::ecdsa_verify(msg, pk, ecdsasig)) {
674 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "ECDSA verify failed");
675 }
676
677 return UFSECP_OK;
678}
679
681 const uint8_t sig64[64],
682 uint8_t* der_out, size_t* der_len) {
683 if (!ctx || !sig64 || !der_out || !der_len) return UFSECP_ERR_NULL_ARG;
684 ctx_clear_err(ctx);
685
686 std::array<uint8_t, 64> compact;
687 std::memcpy(compact.data(), sig64, 64);
688
690 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, ecdsasig)) {
691 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "non-canonical compact sig");
692 }
693
694 auto [der, actual_len] = ecdsasig.to_der();
695 if (*der_len < actual_len) {
696 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "DER buffer too small");
697}
698
699 std::memcpy(der_out, der.data(), actual_len);
700 *der_len = actual_len;
701 return UFSECP_OK;
702}
703
705 const uint8_t* der, size_t der_len,
706 uint8_t sig64_out[64]) {
707 if (!ctx || !der || !sig64_out) return UFSECP_ERR_NULL_ARG;
708 ctx_clear_err(ctx);
709
710 /* Strict DER parser for ECDSA secp256k1 signatures.
711 * Format: 0x30 <total_len> 0x02 <r_len> <r_bytes...> 0x02 <s_len> <s_bytes...>
712 *
713 * Enforces:
714 * - Single-byte length encoding only (no long form)
715 * - No negative integers (high bit of first data byte must be 0)
716 * - No unnecessary leading zero padding
717 * - Exact total length (no trailing bytes)
718 * - r, s must be in [1, n-1] (canonical, nonzero)
719 * - Max total DER length: 72 bytes */
720
721 /* Max DER ECDSA sig: 2 + 2 + 33 + 2 + 33 = 72 */
722 if (der_len < 8 || der_len > 72 || der[0] != 0x30) {
723 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: missing/oversized SEQUENCE");
724 }
725
726 /* Reject long-form length encoding (bit 7 set = multi-byte length) */
727 if (der[1] & 0x80) {
728 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: long-form length");
729 }
730
731 size_t const seq_len = der[1];
732 if (seq_len + 2 != der_len) {
733 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: length mismatch");
734 }
735
736 size_t pos = 2;
737
738 /* --- Helper lambda: parse one INTEGER component strictly --- */
739 auto parse_int = [&](const char* name, const uint8_t*& out_ptr,
740 size_t& out_len) -> ufsecp_error_t {
741 if (pos >= der_len || der[pos] != 0x02) {
742 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: missing INTEGER");
743 }
744 pos++;
745 if (pos >= der_len) {
746 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: truncated");
747 }
748 /* Reject long-form length for component */
749 if (der[pos] & 0x80) {
750 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: long-form int length");
751 }
752 size_t const int_len = der[pos++];
753 if (int_len == 0 || pos + int_len > der_len) {
754 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: int length out of bounds");
755 }
756 /* Reject negative: high bit set on first data byte means negative in DER */
757 if (der[pos] & 0x80) {
758 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: negative integer");
759 }
760 /* Reject unnecessary leading zero: 0x00 prefix only valid when next byte
761 * has high bit set (positive number needs padding to stay positive).
762 * If next byte has high bit clear, the 0x00 is superfluous padding. */
763 if (int_len > 1 && der[pos] == 0x00 && !(der[pos + 1] & 0x80)) {
764 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: unnecessary leading zero");
765 }
766
767 out_ptr = der + pos;
768 out_len = int_len;
769 /* Strip valid leading zero pad (high bit of next byte is set) */
770 if (out_len > 0 && out_ptr[0] == 0x00) { out_ptr++; out_len--; }
771 pos += int_len;
772 (void)name;
773 return UFSECP_OK;
774 };
775
776 /* Read R */
777 const uint8_t* r_ptr = nullptr;
778 size_t r_data_len = 0;
779 {
780 auto rc = parse_int("R", r_ptr, r_data_len);
781 if (rc != UFSECP_OK) return rc;
782 }
783
784 /* Read S */
785 const uint8_t* s_ptr = nullptr;
786 size_t s_data_len = 0;
787 {
788 auto rc = parse_int("S", s_ptr, s_data_len);
789 if (rc != UFSECP_OK) return rc;
790 }
791
792 /* Reject trailing bytes after S (must consume entire SEQUENCE) */
793 if (pos != der_len) {
794 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: trailing bytes");
795 }
796
797 if (r_data_len > 32 || s_data_len > 32) {
798 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: component > 32 bytes");
799 }
800
801 /* Build compact sig64 (big-endian, right-aligned in 32-byte slots) */
802 std::memset(sig64_out, 0, 64);
803 /* Explicit null checks for static analyzer (r_ptr/s_ptr guaranteed non-null
804 * when *_data_len > 0 by parse_int() success, but SonarCloud can't track it) */
805 if (r_data_len > 0 && r_ptr) {
806 std::memcpy(sig64_out + (32 - r_data_len), r_ptr, r_data_len);
807 }
808 if (s_data_len > 0 && s_ptr) {
809 std::memcpy(sig64_out + 32 + (32 - s_data_len), s_ptr, s_data_len);
810 }
811
812 /* Range check: r and s must be in [1, n-1] (strict nonzero, no reduce) */
813 Scalar r_sc, s_sc;
814 if (!Scalar::parse_bytes_strict_nonzero(sig64_out, r_sc) ||
815 !Scalar::parse_bytes_strict_nonzero(sig64_out + 32, s_sc)) {
816 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "bad DER: r or s out of range [1,n-1]");
817 }
818
819 return UFSECP_OK;
820}
821
822/* -- ECDSA Recovery -------------------------------------------------------- */
823
825 const uint8_t msg32[32],
826 const uint8_t privkey[32],
827 uint8_t sig64_out[64],
828 int* recid_out) {
829 if (!ctx || !msg32 || !privkey || !sig64_out || !recid_out) {
830 return UFSECP_ERR_NULL_ARG;
831}
832 ctx_clear_err(ctx);
833
834 std::array<uint8_t, 32> msg;
835 std::memcpy(msg.data(), msg32, 32);
836 Scalar sk;
837 if (!scalar_parse_strict_nonzero(privkey, sk)) {
838 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
839 }
840
841 // ARCH DECISION: No ct::ecdsa_sign_recoverable exists because recovery signing is
842 // inherently non-constant-time — the recid value (0..3) depends on the R point's x
843 // coordinate, leaking timing. We use the FAST path (secp256k1::ecdsa_sign_recoverable)
844 // with explicit zeroization of the private-key scalar immediately after use.
845 // If a future CT recovery path is needed, it must accept a fixed recid hint from the
846 // caller and branch only on public data.
847 auto rsig = secp256k1::ecdsa_sign_recoverable(msg, sk);
848 secp256k1::detail::secure_erase(&sk, sizeof(sk));
849 auto normalized = rsig.sig.normalize();
850 auto compact = normalized.to_compact();
851 std::memcpy(sig64_out, compact.data(), 64);
852 *recid_out = rsig.recid;
853 return UFSECP_OK;
854}
855
857 const uint8_t msg32[32],
858 const uint8_t sig64[64],
859 int recid,
860 uint8_t pubkey33_out[33]) {
861 if (!ctx || !msg32 || !sig64 || !pubkey33_out) return UFSECP_ERR_NULL_ARG;
862 ctx_clear_err(ctx);
863
864 if (recid < 0 || recid > 3) {
865 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "recid must be 0..3");
866 }
867
868 std::array<uint8_t, 32> msg;
869 std::memcpy(msg.data(), msg32, 32);
870 std::array<uint8_t, 64> compact;
871 std::memcpy(compact.data(), sig64, 64);
872
874 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, ecdsasig)) {
875 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "non-canonical compact sig");
876 }
877
878 auto [point, ok] = secp256k1::ecdsa_recover(msg, ecdsasig, recid);
879 if (!ok) {
880 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "recovery failed");
881 }
882
883 point_to_compressed(point, pubkey33_out);
884 return UFSECP_OK;
885}
886
887/* ===========================================================================
888 * Schnorr (BIP-340)
889 * =========================================================================== */
890
892 const uint8_t msg32[32],
893 const uint8_t privkey[32],
894 const uint8_t aux_rand[32],
895 uint8_t sig64_out[64]) {
896 if (!ctx || !msg32 || !privkey || !aux_rand || !sig64_out) {
897 return UFSECP_ERR_NULL_ARG;
898}
899 ctx_clear_err(ctx);
900
901 Scalar sk;
902 if (!scalar_parse_strict_nonzero(privkey, sk)) {
903 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
904 }
905
906 std::array<uint8_t, 32> msg_arr, aux_arr;
907 std::memcpy(msg_arr.data(), msg32, 32);
908 std::memcpy(aux_arr.data(), aux_rand, 32);
909
911 auto sig = secp256k1::ct::schnorr_sign(kp, msg_arr, aux_arr);
912 secp256k1::detail::secure_erase(&sk, sizeof(sk));
913 secp256k1::detail::secure_erase(&kp.d, sizeof(kp.d));
914 auto bytes = sig.to_bytes();
915 std::memcpy(sig64_out, bytes.data(), 64);
916 return UFSECP_OK;
917}
918
920 const uint8_t msg32[32],
921 const uint8_t privkey[32],
922 const uint8_t aux_rand[32],
923 uint8_t sig64_out[64]) {
924 if (!ctx || !msg32 || !privkey || !aux_rand || !sig64_out) {
925 return UFSECP_ERR_NULL_ARG;
926 }
927 ctx_clear_err(ctx);
928
929 Scalar sk;
930 if (!scalar_parse_strict_nonzero(privkey, sk)) {
931 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
932 }
933
934 std::array<uint8_t, 32> msg_arr, aux_arr;
935 std::memcpy(msg_arr.data(), msg32, 32);
936 std::memcpy(aux_arr.data(), aux_rand, 32);
937
939 auto sig = secp256k1::ct::schnorr_sign_verified(kp, msg_arr, aux_arr);
940 secp256k1::detail::secure_erase(&sk, sizeof(sk));
941 secp256k1::detail::secure_erase(&kp.d, sizeof(kp.d));
942 auto bytes = sig.to_bytes();
943 std::memcpy(sig64_out, bytes.data(), 64);
944 return UFSECP_OK;
945}
946
948 ufsecp_ctx* ctx,
949 size_t count,
950 const uint8_t* msgs32,
951 const uint8_t* privkeys32,
952 uint8_t* sigs64_out)
953{
954 if (!ctx || !msgs32 || !privkeys32 || !sigs64_out) return UFSECP_ERR_NULL_ARG;
955 ctx_clear_err(ctx);
956 if (count > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
957 std::size_t total_msg_bytes, total_sig_bytes;
958 if (!checked_mul_size(count, std::size_t{32}, total_msg_bytes)
959 || !checked_mul_size(count, std::size_t{64}, total_sig_bytes))
960 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch size overflow");
961 for (size_t i = 0; i < count; ++i) {
962 std::array<uint8_t, 32> msg;
963 std::memcpy(msg.data(), msgs32 + i * 32, 32);
964 Scalar sk;
965 if (!scalar_parse_strict_nonzero(privkeys32 + i * 32, sk)) {
966 secp256k1::detail::secure_erase(&sk, sizeof(sk));
968 "privkey[i] is zero or >= n");
969 }
970 auto sig = secp256k1::ct::ecdsa_sign(msg, sk);
971 secp256k1::detail::secure_erase(&sk, sizeof(sk));
972 auto compact = sig.to_compact();
973 std::memcpy(sigs64_out + i * 64, compact.data(), 64);
974 }
975 return UFSECP_OK;
976}
977
979 ufsecp_ctx* ctx,
980 size_t count,
981 const uint8_t* msgs32,
982 const uint8_t* privkeys32,
983 const uint8_t* aux_rands32,
984 uint8_t* sigs64_out)
985{
986 if (!ctx || !msgs32 || !privkeys32 || !sigs64_out) return UFSECP_ERR_NULL_ARG;
987 ctx_clear_err(ctx);
988 if (count > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
989 std::size_t total_msg_bytes, total_sig_bytes;
990 if (!checked_mul_size(count, std::size_t{32}, total_msg_bytes)
991 || !checked_mul_size(count, std::size_t{64}, total_sig_bytes))
992 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch size overflow");
993
994 static constexpr uint8_t kZeroAux[32] = {};
995
996 for (size_t i = 0; i < count; ++i) {
997 Scalar sk;
998 if (!scalar_parse_strict_nonzero(privkeys32 + i * 32, sk)) {
999 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1000 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY,
1001 "privkey[i] is zero or >= n");
1002 }
1003
1004 std::array<uint8_t, 32> msg_arr, aux_arr;
1005 std::memcpy(msg_arr.data(), msgs32 + i * 32, 32);
1006 const uint8_t* aux_src = aux_rands32 ? aux_rands32 + i * 32 : kZeroAux;
1007 std::memcpy(aux_arr.data(), aux_src, 32);
1008
1010 auto sig = secp256k1::ct::schnorr_sign(kp, msg_arr, aux_arr);
1011 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1012 secp256k1::detail::secure_erase(&kp.d, sizeof(kp.d));
1013
1014 auto sig_bytes = sig.to_bytes();
1015 std::memcpy(sigs64_out + i * 64, sig_bytes.data(), 64);
1016 }
1017 return UFSECP_OK;
1018}
1019
1021 const uint8_t msg32[32],
1022 const uint8_t sig64[64],
1023 const uint8_t pubkey_x[32]) {
1024 if (!ctx || !msg32 || !sig64 || !pubkey_x) return UFSECP_ERR_NULL_ARG;
1025 ctx_clear_err(ctx);
1026
1027 // BIP-340 strict parse: reject non-canonical r >= p, s >= n, or s == 0
1028 secp256k1::SchnorrSignature schnorr_sig;
1029 if (!secp256k1::SchnorrSignature::parse_strict(sig64, schnorr_sig)) {
1030 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "Non-canonical Schnorr sig (r>=p or s>=n)");
1031 }
1032
1033 // BIP-340 strict: reject pubkey x >= p
1034 FE pk_fe;
1035 if (!FE::parse_bytes_strict(pubkey_x, pk_fe)) {
1036 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "Non-canonical pubkey (x>=p)");
1037 }
1038
1039 std::array<uint8_t, 32> pk_arr, msg_arr;
1040 std::memcpy(pk_arr.data(), pubkey_x, 32);
1041 std::memcpy(msg_arr.data(), msg32, 32);
1042
1043 if (!secp256k1::schnorr_verify(pk_arr, msg_arr, schnorr_sig)) {
1044 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "Schnorr verify failed");
1045}
1046
1047 return UFSECP_OK;
1048}
1049
1050/* ===========================================================================
1051 * ECDH
1052 * =========================================================================== */
1053
1055 const uint8_t privkey[32],
1056 const uint8_t pubkey33[33],
1057 Scalar& sk, Point& pk) {
1058 if (!scalar_parse_strict_nonzero(privkey, sk)) {
1059 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
1060 }
1061 pk = point_from_compressed(pubkey33);
1062 if (pk.is_infinity()) {
1063 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1064 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid or infinity pubkey");
1065 }
1066 return UFSECP_OK;
1067}
1068
1070 const uint8_t privkey[32],
1071 const uint8_t pubkey33[33],
1072 uint8_t secret32_out[32]) {
1073 if (!ctx || !privkey || !pubkey33 || !secret32_out) return UFSECP_ERR_NULL_ARG;
1074 ctx_clear_err(ctx);
1075 Scalar sk; Point pk;
1076 const ufsecp_error_t err = ecdh_parse_args(ctx, privkey, pubkey33, sk, pk);
1077 if (err != UFSECP_OK) return err;
1078 auto secret = secp256k1::ecdh_compute(sk, pk);
1079 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1080 std::memcpy(secret32_out, secret.data(), 32);
1081 secp256k1::detail::secure_erase(secret.data(), 32);
1082 return UFSECP_OK;
1083}
1084
1086 const uint8_t privkey[32],
1087 const uint8_t pubkey33[33],
1088 uint8_t secret32_out[32]) {
1089 if (!ctx || !privkey || !pubkey33 || !secret32_out) return UFSECP_ERR_NULL_ARG;
1090 ctx_clear_err(ctx);
1091 Scalar sk; Point pk;
1092 const ufsecp_error_t err = ecdh_parse_args(ctx, privkey, pubkey33, sk, pk);
1093 if (err != UFSECP_OK) return err;
1094 auto secret = secp256k1::ecdh_compute_xonly(sk, pk);
1095 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1096 std::memcpy(secret32_out, secret.data(), 32);
1097 secp256k1::detail::secure_erase(secret.data(), 32);
1098 return UFSECP_OK;
1099}
1100
1102 const uint8_t privkey[32],
1103 const uint8_t pubkey33[33],
1104 uint8_t secret32_out[32]) {
1105 if (!ctx || !privkey || !pubkey33 || !secret32_out) return UFSECP_ERR_NULL_ARG;
1106 ctx_clear_err(ctx);
1107 Scalar sk; Point pk;
1108 const ufsecp_error_t err = ecdh_parse_args(ctx, privkey, pubkey33, sk, pk);
1109 if (err != UFSECP_OK) return err;
1110 auto secret = secp256k1::ecdh_compute_raw(sk, pk);
1111 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1112 std::memcpy(secret32_out, secret.data(), 32);
1113 secp256k1::detail::secure_erase(secret.data(), 32);
1114 return UFSECP_OK;
1115}
1116
1117/* ===========================================================================
1118 * Hashing (stateless -- no ctx required, but returns error_t for consistency)
1119 * =========================================================================== */
1120
1121ufsecp_error_t ufsecp_sha256(const uint8_t* data, size_t len,
1122 uint8_t digest32_out[32]) {
1123 if (!data || !digest32_out) return UFSECP_ERR_NULL_ARG;
1124 secp256k1::SHA256 hasher;
1125 hasher.update(data, len);
1126 auto digest = hasher.finalize();
1127 std::memcpy(digest32_out, digest.data(), 32);
1128 return UFSECP_OK;
1129}
1130
1131ufsecp_error_t ufsecp_hash160(const uint8_t* data, size_t len,
1132 uint8_t digest20_out[20]) {
1133 if (!data || !digest20_out) return UFSECP_ERR_NULL_ARG;
1134 auto h = secp256k1::hash160(data, len);
1135 std::memcpy(digest20_out, h.data(), 20);
1136 return UFSECP_OK;
1137}
1138
1140 const uint8_t* data, size_t len,
1141 uint8_t digest32_out[32]) {
1142 if (!tag || !data || !digest32_out) return UFSECP_ERR_NULL_ARG;
1143 auto h = secp256k1::tagged_hash(tag, data, len);
1144 std::memcpy(digest32_out, h.data(), 32);
1145 return UFSECP_OK;
1146}
1147
1148/* ===========================================================================
1149 * Bitcoin addresses
1150 * =========================================================================== */
1151
1153 const uint8_t pubkey33[33], int network,
1154 char* addr_out, size_t* addr_len) {
1155 if (!ctx || !pubkey33 || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
1156 ctx_clear_err(ctx);
1157
1158 auto pk = point_from_compressed(pubkey33);
1159 if (pk.is_infinity()) {
1160 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
1161 }
1162 try {
1163 auto addr = secp256k1::address_p2pkh(pk, to_network(network));
1164 if (addr.empty()) {
1165 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "P2PKH generation failed");
1166}
1167 if (*addr_len < addr.size() + 1) {
1168 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "P2PKH buffer too small");
1169}
1170 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
1171 *addr_len = addr.size();
1172 return UFSECP_OK;
1173 } UFSECP_CATCH_RETURN(ctx)
1174}
1175
1177 const uint8_t pubkey33[33], int network,
1178 char* addr_out, size_t* addr_len) {
1179 if (!ctx || !pubkey33 || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
1180 ctx_clear_err(ctx);
1181
1182 auto pk = point_from_compressed(pubkey33);
1183 if (pk.is_infinity()) {
1184 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
1185 }
1186 try {
1187 auto addr = secp256k1::address_p2wpkh(pk, to_network(network));
1188 if (addr.empty()) {
1189 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "P2WPKH generation failed");
1190}
1191 if (*addr_len < addr.size() + 1) {
1192 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "P2WPKH buffer too small");
1193}
1194 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
1195 *addr_len = addr.size();
1196 return UFSECP_OK;
1197 } UFSECP_CATCH_RETURN(ctx)
1198}
1199
1201 const uint8_t internal_key_x[32], int network,
1202 char* addr_out, size_t* addr_len) {
1203 if (!ctx || !internal_key_x || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
1204 ctx_clear_err(ctx);
1205
1206 // Reject all-zero x-only key (not a valid curve point)
1207 {
1208 uint8_t acc = 0;
1209 for (int i = 0; i < 32; ++i) acc |= internal_key_x[i];
1210 if (acc == 0) return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "zero x-only key");
1211 }
1212
1213 std::array<uint8_t, 32> key_x;
1214 std::memcpy(key_x.data(), internal_key_x, 32);
1215 try {
1216 auto addr = secp256k1::address_p2tr_raw(key_x, to_network(network));
1217 if (addr.empty()) {
1218 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "P2TR generation failed");
1219}
1220 if (*addr_len < addr.size() + 1) {
1221 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "P2TR buffer too small");
1222}
1223 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
1224 *addr_len = addr.size();
1225 return UFSECP_OK;
1226 } UFSECP_CATCH_RETURN(ctx)
1227}
1228
1230 const uint8_t* redeem_script, size_t redeem_script_len,
1231 int network,
1232 char* addr_out, size_t* addr_len) {
1233 if (!redeem_script && redeem_script_len > 0) return UFSECP_ERR_NULL_ARG;
1234 if (!addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
1235
1236 try {
1237 // hash160 of redeem_script
1238 auto script_hash = secp256k1::hash160(redeem_script, redeem_script_len);
1239 secp256k1::Network const net = (network == UFSECP_NET_MAINNET)
1241 auto addr = secp256k1::address_p2sh(script_hash, net);
1242 if (addr.empty()) return UFSECP_ERR_INTERNAL;
1243 if (*addr_len < addr.size() + 1) return UFSECP_ERR_BUF_TOO_SMALL;
1244 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
1245 *addr_len = addr.size();
1246 return UFSECP_OK;
1247 } catch (...) { return UFSECP_ERR_INTERNAL; }
1248}
1249
1251 ufsecp_ctx* ctx,
1252 const uint8_t pubkey33[33],
1253 int network,
1254 char* addr_out, size_t* addr_len) {
1255 if (!ctx || !pubkey33 || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
1256 ctx_clear_err(ctx);
1257
1258 auto pk = point_from_compressed(pubkey33);
1259 if (pk.is_infinity()) {
1260 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
1261 }
1262 try {
1263 auto addr = secp256k1::address_p2sh_p2wpkh(pk, to_network(network));
1264 if (addr.empty()) {
1265 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "P2SH-P2WPKH generation failed");
1266 }
1267 if (*addr_len < addr.size() + 1) {
1268 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "P2SH-P2WPKH buffer too small");
1269 }
1270 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
1271 *addr_len = addr.size();
1272 return UFSECP_OK;
1273 } UFSECP_CATCH_RETURN(ctx)
1274}
1275
1276/* ===========================================================================
1277 * WIF
1278 * =========================================================================== */
1279
1281 const uint8_t privkey[32],
1282 int compressed, int network,
1283 char* wif_out, size_t* wif_len) {
1284 if (!ctx || !privkey || !wif_out || !wif_len) return UFSECP_ERR_NULL_ARG;
1285 ctx_clear_err(ctx);
1286
1287 Scalar sk;
1288 if (!scalar_parse_strict_nonzero(privkey, sk)) {
1289 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
1290 }
1291 try {
1292 auto wif = secp256k1::wif_encode(sk, compressed != 0, to_network(network));
1293 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1294 if (wif.empty()) {
1295 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "WIF encode failed");
1296}
1297 if (*wif_len < wif.size() + 1) {
1298 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "WIF buffer too small");
1299}
1300 std::memcpy(wif_out, wif.c_str(), wif.size() + 1);
1301 *wif_len = wif.size();
1302 return UFSECP_OK;
1303 } UFSECP_CATCH_RETURN(ctx)
1304}
1305
1307 const char* wif,
1308 uint8_t privkey32_out[32],
1309 int* compressed_out,
1310 int* network_out) {
1311 if (!ctx || !wif || !privkey32_out || !compressed_out || !network_out) {
1312 return UFSECP_ERR_NULL_ARG;
1313}
1314 ctx_clear_err(ctx);
1315
1316 try {
1317 auto result = secp256k1::wif_decode(std::string(wif));
1318 if (!result.valid) {
1319 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid WIF string");
1320}
1321
1322 scalar_to_bytes(result.key, privkey32_out);
1323 secp256k1::detail::secure_erase(&result.key, sizeof(result.key));
1324 *compressed_out = result.compressed ? 1 : 0;
1325 *network_out = result.network == secp256k1::Network::Testnet
1327 return UFSECP_OK;
1328 } UFSECP_CATCH_RETURN(ctx)
1329}
1330
1331/* ===========================================================================
1332 * BIP-32
1333 * =========================================================================== */
1334
1336 auto serialized = ek.serialize();
1337 std::memcpy(out->data, serialized.data(), 78);
1338 out->is_private = ek.is_private ? 1 : 0;
1339 std::memset(out->_pad, 0, sizeof(out->_pad));
1340}
1341
1344 ek.depth = k->data[4];
1345 std::memcpy(ek.parent_fingerprint.data(), k->data + 5, 4);
1346 ek.child_number = (uint32_t(k->data[9]) << 24) | (uint32_t(k->data[10]) << 16) |
1347 (uint32_t(k->data[11]) << 8) | uint32_t(k->data[12]);
1348 std::memcpy(ek.chain_code.data(), k->data + 13, 32);
1349 std::memcpy(ek.key.data(), k->data + 46, 32);
1350 if (k->is_private) {
1351 ek.is_private = true;
1352 } else {
1353 ek.is_private = false;
1354 ek.pub_prefix = k->data[45];
1355 }
1356 return ek;
1357}
1358
1360 const ufsecp_bip32_key* key,
1362 if (key->is_private > 1) {
1363 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid BIP-32 key kind");
1364 }
1365 if (key->_pad[0] != 0 || key->_pad[1] != 0 || key->_pad[2] != 0) {
1366 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid BIP-32 reserved bytes");
1367 }
1368
1369 const uint32_t version = (uint32_t(key->data[0]) << 24) |
1370 (uint32_t(key->data[1]) << 16) |
1371 (uint32_t(key->data[2]) << 8) |
1372 uint32_t(key->data[3]);
1373 const uint32_t expected_version = key->is_private ? 0x0488ADE4u : 0x0488B21Eu;
1374 if (version != expected_version) {
1375 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid BIP-32 version");
1376 }
1377
1378 if (key->is_private != 0) {
1379 if (key->data[45] != 0x00) {
1380 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid BIP-32 private marker");
1381 }
1382 Scalar sk;
1383 if (!scalar_parse_strict_nonzero(key->data + 46, sk)) {
1384 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid BIP-32 private key");
1385 }
1386 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1387 } else {
1388 if (key->data[45] != 0x02 && key->data[45] != 0x03) {
1389 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid BIP-32 public key prefix");
1390 }
1391 auto pk = point_from_compressed(key->data + 45);
1392 if (pk.is_infinity()) {
1393 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid BIP-32 public key");
1394 }
1395 }
1396
1397 out = extkey_from_uf(key);
1398 return UFSECP_OK;
1399}
1400
1402 const uint8_t* seed, size_t seed_len,
1403 ufsecp_bip32_key* key_out) {
1404 if (!ctx || !seed || !key_out) return UFSECP_ERR_NULL_ARG;
1405 ctx_clear_err(ctx);
1406
1407 if (seed_len < 16 || seed_len > 64) {
1408 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "seed must be 16-64 bytes");
1409}
1410
1411 auto [ek, ok] = secp256k1::bip32_master_key(seed, seed_len);
1412 if (!ok) {
1413 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "BIP-32 master key failed");
1414}
1415
1416 extkey_to_uf(ek, key_out);
1417 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1418 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1419 return UFSECP_OK;
1420}
1421
1423 const ufsecp_bip32_key* parent,
1424 uint32_t index,
1425 ufsecp_bip32_key* child_out) {
1426 if (!ctx || !parent || !child_out) return UFSECP_ERR_NULL_ARG;
1427 ctx_clear_err(ctx);
1428
1430 ufsecp_error_t const parse_rc = parse_bip32_key(ctx, parent, ek);
1431 if (parse_rc != UFSECP_OK) {
1432 return parse_rc;
1433 }
1434 auto [child, ok] = ek.derive_child(index);
1435 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1436 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1437 if (!ok) {
1438 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "BIP-32 derivation failed");
1439}
1440
1441 extkey_to_uf(child, child_out);
1442 secp256k1::detail::secure_erase(child.key.data(), child.key.size());
1443 secp256k1::detail::secure_erase(child.chain_code.data(), child.chain_code.size());
1444 return UFSECP_OK;
1445}
1446
1448 const ufsecp_bip32_key* master,
1449 const char* path,
1450 ufsecp_bip32_key* key_out) {
1451 if (!ctx || !master || !path || !key_out) return UFSECP_ERR_NULL_ARG;
1452 ctx_clear_err(ctx);
1453
1455 ufsecp_error_t const parse_rc = parse_bip32_key(ctx, master, ek);
1456 if (parse_rc != UFSECP_OK) {
1457 return parse_rc;
1458 }
1459 try {
1460 auto [derived, ok] = secp256k1::bip32_derive_path(ek, std::string(path));
1461 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1462 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1463 if (!ok) {
1464 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid BIP-32 path");
1465}
1466
1467 extkey_to_uf(derived, key_out);
1468 secp256k1::detail::secure_erase(derived.key.data(), derived.key.size());
1469 secp256k1::detail::secure_erase(derived.chain_code.data(), derived.chain_code.size());
1470 return UFSECP_OK;
1471 } UFSECP_CATCH_RETURN(ctx)
1472}
1473
1475 const ufsecp_bip32_key* key,
1476 uint8_t privkey32_out[32]) {
1477 if (!ctx || !key || !privkey32_out) return UFSECP_ERR_NULL_ARG;
1478 ctx_clear_err(ctx);
1479
1480 if (!key->is_private) {
1481 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "key is public, not private");
1482}
1483
1485 ufsecp_error_t const parse_rc = parse_bip32_key(ctx, key, ek);
1486 if (parse_rc != UFSECP_OK) {
1487 return parse_rc;
1488 }
1489 auto sk = ek.private_key();
1490 scalar_to_bytes(sk, privkey32_out);
1491 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1492 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1493 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1494 return UFSECP_OK;
1495}
1496
1498 const ufsecp_bip32_key* key,
1499 uint8_t pubkey33_out[33]) {
1500 if (!ctx || !key || !pubkey33_out) return UFSECP_ERR_NULL_ARG;
1501 ctx_clear_err(ctx);
1502
1504 ufsecp_error_t const parse_rc = parse_bip32_key(ctx, key, ek);
1505 if (parse_rc != UFSECP_OK) {
1506 return parse_rc;
1507 }
1508 auto pk = ek.public_key();
1509 if (pk.is_infinity()) {
1510 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1511 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1512 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid BIP-32 public key");
1513 }
1514 point_to_compressed(pk, pubkey33_out);
1515 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
1516 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
1517 return UFSECP_OK;
1518}
1519
1520/* ===========================================================================
1521 * Taproot (BIP-341)
1522 * =========================================================================== */
1523
1525 const uint8_t internal_x[32],
1526 const uint8_t* merkle_root,
1527 uint8_t output_x_out[32],
1528 int* parity_out) {
1529 if (!ctx || !internal_x || !output_x_out || !parity_out) {
1530 return UFSECP_ERR_NULL_ARG;
1531}
1532 ctx_clear_err(ctx);
1533
1534 // Reject all-zero x-only key (not a valid curve point)
1535 {
1536 uint8_t acc = 0;
1537 for (int i = 0; i < 32; ++i) acc |= internal_x[i];
1538 if (acc == 0) return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "zero x-only key");
1539 }
1540
1541 std::array<uint8_t, 32> ik;
1542 std::memcpy(ik.data(), internal_x, 32);
1543 size_t const mr_len = merkle_root ? 32 : 0;
1544
1545 auto [ok_x, parity] = secp256k1::taproot_output_key(ik, merkle_root, mr_len);
1546 std::memcpy(output_x_out, ok_x.data(), 32);
1547 *parity_out = parity;
1548 return UFSECP_OK;
1549}
1550
1552 const uint8_t privkey[32],
1553 const uint8_t* merkle_root,
1554 uint8_t tweaked32_out[32]) {
1555 if (!ctx || !privkey || !tweaked32_out) return UFSECP_ERR_NULL_ARG;
1556 ctx_clear_err(ctx);
1557
1558 Scalar sk;
1559 if (!scalar_parse_strict_nonzero(privkey, sk)) {
1560 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
1561 }
1562 size_t const mr_len = merkle_root ? 32 : 0;
1563
1564 auto tweaked = secp256k1::taproot_tweak_privkey(sk, merkle_root, mr_len);
1565 secp256k1::detail::secure_erase(&sk, sizeof(sk));
1566 if (tweaked.is_zero()) {
1567 secp256k1::detail::secure_erase(&tweaked, sizeof(tweaked));
1568 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "taproot tweak resulted in zero");
1569}
1570
1571 scalar_to_bytes(tweaked, tweaked32_out);
1572 secp256k1::detail::secure_erase(&tweaked, sizeof(tweaked));
1573 return UFSECP_OK;
1574}
1575
1577 const uint8_t output_x[32], int output_parity,
1578 const uint8_t internal_x[32],
1579 const uint8_t* merkle_root, size_t merkle_root_len) {
1580 if (!ctx || !output_x || !internal_x) return UFSECP_ERR_NULL_ARG;
1581 if (!merkle_root && merkle_root_len > 0) return UFSECP_ERR_NULL_ARG;
1582 ctx_clear_err(ctx);
1583
1584 std::array<uint8_t, 32> ok_x, ik_x;
1585 std::memcpy(ok_x.data(), output_x, 32);
1586 std::memcpy(ik_x.data(), internal_x, 32);
1587
1588 if (!secp256k1::taproot_verify_commitment(ok_x, output_parity, ik_x,
1589 merkle_root, merkle_root_len)) {
1590 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "taproot commitment invalid");
1591}
1592
1593 return UFSECP_OK;
1594}
1595
1596/* ===========================================================================
1597 * BIP-143: SegWit v0 Sighash
1598 * =========================================================================== */
1599
1601 ufsecp_ctx* ctx,
1602 uint32_t version,
1603 const uint8_t hash_prevouts[32],
1604 const uint8_t hash_sequence[32],
1605 const uint8_t outpoint_txid[32], uint32_t outpoint_vout,
1606 const uint8_t* script_code, size_t script_code_len,
1607 uint64_t value,
1608 uint32_t sequence,
1609 const uint8_t hash_outputs[32],
1610 uint32_t locktime,
1611 uint32_t sighash_type,
1612 uint8_t sighash_out[32]) {
1613 if (!ctx || !hash_prevouts || !hash_sequence || !outpoint_txid ||
1614 !script_code || !hash_outputs || !sighash_out)
1615 return UFSECP_ERR_NULL_ARG;
1616 ctx_clear_err(ctx);
1617
1619 pre.version = version;
1620 std::memcpy(pre.hash_prevouts.data(), hash_prevouts, 32);
1621 std::memcpy(pre.hash_sequence.data(), hash_sequence, 32);
1622 std::memcpy(pre.hash_outputs.data(), hash_outputs, 32);
1623 pre.locktime = locktime;
1624
1626 std::memcpy(op.txid.data(), outpoint_txid, 32);
1627 op.vout = outpoint_vout;
1628
1629 auto h = secp256k1::bip143_sighash(pre, op, script_code, script_code_len,
1630 value, sequence, sighash_type);
1631 std::memcpy(sighash_out, h.data(), 32);
1632 return UFSECP_OK;
1633}
1634
1636 const uint8_t pubkey_hash[20],
1637 uint8_t script_code_out[25]) {
1638 if (!pubkey_hash || !script_code_out) return UFSECP_ERR_NULL_ARG;
1639
1640 auto sc = secp256k1::bip143_p2wpkh_script_code(pubkey_hash);
1641 std::memcpy(script_code_out, sc.data(), 25);
1642 return UFSECP_OK;
1643}
1644
1645/* ===========================================================================
1646 * BIP-144: Witness Transaction Serialization
1647 * =========================================================================== */
1648
1649// Helper: read Bitcoin CompactSize from buffer; returns 0 on overflow
1650static size_t read_compact_size(const uint8_t* buf, size_t len,
1651 size_t& offset, uint64_t& val) {
1652 if (offset >= len) return 0;
1653 uint8_t first = buf[offset++];
1654 if (first < 0xFD) { val = first; return 1; }
1655 if (first == 0xFD) {
1656 if (offset + 2 > len) return 0;
1657 val = uint64_t(buf[offset]) | (uint64_t(buf[offset+1]) << 8);
1658 offset += 2; return 3;
1659 }
1660 if (first == 0xFE) {
1661 if (offset + 4 > len) return 0;
1662 val = uint64_t(buf[offset]) | (uint64_t(buf[offset+1]) << 8) |
1663 (uint64_t(buf[offset+2]) << 16) | (uint64_t(buf[offset+3]) << 24);
1664 offset += 4; return 5;
1665 }
1666 // 0xFF
1667 if (offset + 8 > len) return 0;
1668 val = 0;
1669 for (int i = 0; i < 8; ++i) val |= uint64_t(buf[offset+i]) << (8*i);
1670 offset += 8; return 9;
1671}
1672
1673// Helper: skip CompactSize-prefixed blob (e.g. scriptSig or scriptPubKey)
1674static bool skip_compact_bytes(const uint8_t* buf, size_t len, size_t& offset) {
1675 uint64_t sz = 0;
1676 if (!read_compact_size(buf, len, offset, sz)) return false;
1677 if (offset + sz > len) return false;
1678 offset += static_cast<size_t>(sz);
1679 return true;
1680}
1681
1683 ufsecp_ctx* ctx,
1684 const uint8_t* raw_tx, size_t raw_tx_len,
1685 uint8_t txid_out[32]) {
1686 if (!ctx || !raw_tx || !txid_out) return UFSECP_ERR_NULL_ARG;
1687 if (raw_tx_len < 10) return UFSECP_ERR_BAD_INPUT;
1688
1689 // Detect witness flag: version(4) + marker(0x00) + flag(0x01)
1690 bool has_witness = (raw_tx_len > 6 && raw_tx[4] == 0x00 && raw_tx[5] == 0x01);
1691
1692 if (!has_witness) {
1693 // Legacy tx: txid = double-SHA256 of the entire raw bytes
1694 auto h = secp256k1::SHA256::hash256(raw_tx, raw_tx_len);
1695 std::memcpy(txid_out, h.data(), 32);
1696 return UFSECP_OK;
1697 }
1698
1699 // Witness tx: strip marker+flag and witness data
1700 // Legacy = version(4) | inputs | outputs | locktime(4)
1702 // version
1703 h1.update(raw_tx, 4);
1704
1705 // Skip marker+flag, parse inputs+outputs from offset 6
1706 size_t off = 6;
1707 uint64_t n_in = 0;
1708 size_t cs_start = off;
1709 if (!read_compact_size(raw_tx, raw_tx_len, off, n_in)) return UFSECP_ERR_BAD_INPUT;
1710
1711 // Record start of vin count for hashing
1712 size_t io_start = cs_start;
1713
1714 // Skip all inputs (each: txid(32) + vout(4) + scriptSig + sequence(4))
1715 for (uint64_t i = 0; i < n_in; ++i) {
1716 if (off + 36 > raw_tx_len) return UFSECP_ERR_BAD_INPUT;
1717 off += 36; // txid + vout
1718 if (!skip_compact_bytes(raw_tx, raw_tx_len, off)) return UFSECP_ERR_BAD_INPUT;
1719 if (off + 4 > raw_tx_len) return UFSECP_ERR_BAD_INPUT;
1720 off += 4; // sequence
1721 }
1722
1723 // Parse outputs count
1724 uint64_t n_out = 0;
1725 if (!read_compact_size(raw_tx, raw_tx_len, off, n_out)) return UFSECP_ERR_BAD_INPUT;
1726
1727 // Skip all outputs (each: value(8) + scriptPubKey)
1728 for (uint64_t i = 0; i < n_out; ++i) {
1729 if (off + 8 > raw_tx_len) return UFSECP_ERR_BAD_INPUT;
1730 off += 8; // value
1731 if (!skip_compact_bytes(raw_tx, raw_tx_len, off)) return UFSECP_ERR_BAD_INPUT;
1732 }
1733 size_t io_end = off;
1734
1735 // Hash inputs+outputs section (cs_start..io_end)
1736 h1.update(raw_tx + io_start, io_end - io_start);
1737
1738 // locktime = last 4 bytes
1739 if (raw_tx_len < 4) return UFSECP_ERR_BAD_INPUT;
1740 h1.update(raw_tx + raw_tx_len - 4, 4);
1741
1742 auto first = h1.finalize();
1744 h2.update(first.data(), 32);
1745 auto txid = h2.finalize();
1746 std::memcpy(txid_out, txid.data(), 32);
1747 return UFSECP_OK;
1748}
1749
1751 ufsecp_ctx* ctx,
1752 const uint8_t* raw_tx, size_t raw_tx_len,
1753 uint8_t wtxid_out[32]) {
1754 if (!ctx || !raw_tx || !wtxid_out) return UFSECP_ERR_NULL_ARG;
1755 if (raw_tx_len < 10) return UFSECP_ERR_BAD_INPUT;
1756
1757 // wtxid = double-SHA256 of the full witness-serialized tx
1758 auto h = secp256k1::SHA256::hash256(raw_tx, raw_tx_len);
1759 std::memcpy(wtxid_out, h.data(), 32);
1760 return UFSECP_OK;
1761}
1762
1764 const uint8_t witness_root[32],
1765 const uint8_t witness_nonce[32],
1766 uint8_t commitment_out[32]) {
1767 if (!witness_root || !witness_nonce || !commitment_out)
1768 return UFSECP_ERR_NULL_ARG;
1769
1770 std::array<uint8_t, 32> wr, wn;
1771 std::memcpy(wr.data(), witness_root, 32);
1772 std::memcpy(wn.data(), witness_nonce, 32);
1773
1774 auto c = secp256k1::witness_commitment(wr, wn);
1775 std::memcpy(commitment_out, c.data(), 32);
1776 return UFSECP_OK;
1777}
1778
1779/* ===========================================================================
1780 * BIP-141: Segregated Witness — Witness Programs
1781 * =========================================================================== */
1782
1784 const uint8_t* script, size_t script_len) {
1785 if (!script) return 0;
1786 return secp256k1::is_witness_program(script, script_len) ? 1 : 0;
1787}
1788
1790 const uint8_t* script, size_t script_len,
1791 int* version_out,
1792 uint8_t* program_out, size_t* program_len_out) {
1793 if (!script || !version_out || !program_out || !program_len_out)
1794 return UFSECP_ERR_NULL_ARG;
1795
1796 auto wp = secp256k1::parse_witness_program(script, script_len);
1797 if (wp.version < 0) {
1798 *version_out = -1;
1799 *program_len_out = 0;
1800 return UFSECP_ERR_BAD_INPUT;
1801 }
1802 if (wp.program.size() > 40) return UFSECP_ERR_INTERNAL; /* BIP-141 cap */
1803 *version_out = wp.version;
1804 *program_len_out = wp.program.size();
1805 std::memcpy(program_out, wp.program.data(), wp.program.size());
1806 return UFSECP_OK;
1807}
1808
1810 const uint8_t pubkey_hash[20],
1811 uint8_t spk_out[22]) {
1812 if (!pubkey_hash || !spk_out) return UFSECP_ERR_NULL_ARG;
1813
1814 auto spk = secp256k1::segwit_scriptpubkey_p2wpkh(pubkey_hash);
1815 std::memcpy(spk_out, spk.data(), 22);
1816 return UFSECP_OK;
1817}
1818
1820 const uint8_t script_hash[32],
1821 uint8_t spk_out[34]) {
1822 if (!script_hash || !spk_out) return UFSECP_ERR_NULL_ARG;
1823
1824 auto spk = secp256k1::segwit_scriptpubkey_p2wsh(script_hash);
1825 std::memcpy(spk_out, spk.data(), 34);
1826 return UFSECP_OK;
1827}
1828
1830 const uint8_t output_key[32],
1831 uint8_t spk_out[34]) {
1832 if (!output_key || !spk_out) return UFSECP_ERR_NULL_ARG;
1833
1834 auto spk = secp256k1::segwit_scriptpubkey_p2tr(output_key);
1835 std::memcpy(spk_out, spk.data(), 34);
1836 return UFSECP_OK;
1837}
1838
1840 const uint8_t* script, size_t script_len,
1841 uint8_t hash_out[32]) {
1842 // Allow (nullptr, 0) as a valid empty-script input; only reject null when len > 0
1843 if ((!script && script_len > 0) || !hash_out) return UFSECP_ERR_NULL_ARG;
1844
1845 auto h = secp256k1::witness_script_hash(script, script_len);
1846 std::memcpy(hash_out, h.data(), 32);
1847 return UFSECP_OK;
1848}
1849
1850/* ===========================================================================
1851 * BIP-342: Tapscript Sighash
1852 * =========================================================================== */
1853
1854// Helper: build TapSighashTxData from flat arrays,
1855// converting flattened prevout_txids to array-of-array.
1857 uint32_t version, uint32_t locktime,
1858 size_t input_count,
1859 const uint8_t* prevout_txids_flat,
1860 const uint32_t* prevout_vouts,
1861 const uint64_t* input_amounts,
1862 const uint32_t* input_sequences,
1863 const uint8_t* const* input_spks,
1864 const size_t* input_spk_lens,
1865 size_t output_count,
1866 const uint64_t* output_values,
1867 const uint8_t* const* output_spks,
1868 const size_t* output_spk_lens,
1869 std::vector<std::array<uint8_t, 32>>& txid_storage) {
1870
1871 // Convert flat txid array to array-of-array
1872 txid_storage.resize(input_count);
1873 for (size_t i = 0; i < input_count; ++i) {
1874 std::memcpy(txid_storage[i].data(), prevout_txids_flat + i * 32, 32);
1875 }
1876
1878 td.version = version;
1879 td.locktime = locktime;
1880 td.input_count = input_count;
1881 td.prevout_txids = txid_storage.data();
1882 td.prevout_vouts = prevout_vouts;
1883 td.input_amounts = input_amounts;
1884 td.input_sequences = input_sequences;
1885 td.input_scriptpubkeys = input_spks;
1886 td.input_scriptpubkey_lens = input_spk_lens;
1887 td.output_count = output_count;
1888 td.output_values = output_values;
1889 td.output_scriptpubkeys = output_spks;
1890 td.output_scriptpubkey_lens = output_spk_lens;
1891 return td;
1892}
1893
1895 ufsecp_ctx* ctx,
1896 uint32_t version, uint32_t locktime,
1897 size_t input_count,
1898 const uint8_t* prevout_txids,
1899 const uint32_t* prevout_vouts,
1900 const uint64_t* input_amounts,
1901 const uint32_t* input_sequences,
1902 const uint8_t* const* input_spks,
1903 const size_t* input_spk_lens,
1904 size_t output_count,
1905 const uint64_t* output_values,
1906 const uint8_t* const* output_spks,
1907 const size_t* output_spk_lens,
1908 size_t input_index,
1909 uint8_t hash_type,
1910 const uint8_t* annex, size_t annex_len,
1911 uint8_t sighash_out[32]) {
1912 if (!ctx || !prevout_txids || !prevout_vouts || !input_amounts ||
1913 !input_sequences || !input_spks || !input_spk_lens ||
1914 !output_values || !output_spks || !output_spk_lens || !sighash_out)
1915 return UFSECP_ERR_NULL_ARG;
1916 if (input_index >= input_count)
1917 return UFSECP_ERR_BAD_INPUT;
1918 ctx_clear_err(ctx);
1919
1920 try {
1921 std::vector<std::array<uint8_t, 32>> txid_storage;
1922 auto td = build_tap_tx_data(version, locktime, input_count,
1923 prevout_txids, prevout_vouts, input_amounts, input_sequences,
1924 input_spks, input_spk_lens, output_count, output_values,
1925 output_spks, output_spk_lens, txid_storage);
1926
1927 auto h = secp256k1::taproot_keypath_sighash(td, input_index, hash_type,
1928 annex, annex_len);
1929 std::memcpy(sighash_out, h.data(), 32);
1930 return UFSECP_OK;
1931 } UFSECP_CATCH_RETURN(ctx)
1932}
1933
1935 ufsecp_ctx* ctx,
1936 uint32_t version, uint32_t locktime,
1937 size_t input_count,
1938 const uint8_t* prevout_txids,
1939 const uint32_t* prevout_vouts,
1940 const uint64_t* input_amounts,
1941 const uint32_t* input_sequences,
1942 const uint8_t* const* input_spks,
1943 const size_t* input_spk_lens,
1944 size_t output_count,
1945 const uint64_t* output_values,
1946 const uint8_t* const* output_spks,
1947 const size_t* output_spk_lens,
1948 size_t input_index,
1949 uint8_t hash_type,
1950 const uint8_t tapleaf_hash[32],
1951 uint8_t key_version,
1952 uint32_t code_separator_pos,
1953 const uint8_t* annex, size_t annex_len,
1954 uint8_t sighash_out[32]) {
1955 if (!ctx || !prevout_txids || !prevout_vouts || !input_amounts ||
1956 !input_sequences || !input_spks || !input_spk_lens ||
1957 !output_values || !output_spks || !output_spk_lens ||
1958 !tapleaf_hash || !sighash_out)
1959 return UFSECP_ERR_NULL_ARG;
1960 if (input_index >= input_count)
1961 return UFSECP_ERR_BAD_INPUT;
1962 ctx_clear_err(ctx);
1963
1964 try {
1965 std::vector<std::array<uint8_t, 32>> txid_storage;
1966 auto td = build_tap_tx_data(version, locktime, input_count,
1967 prevout_txids, prevout_vouts, input_amounts, input_sequences,
1968 input_spks, input_spk_lens, output_count, output_values,
1969 output_spks, output_spk_lens, txid_storage);
1970
1971 std::array<uint8_t, 32> tlh;
1972 std::memcpy(tlh.data(), tapleaf_hash, 32);
1973
1974 auto h = secp256k1::tapscript_sighash(td, input_index, hash_type,
1975 tlh, key_version, code_separator_pos,
1976 annex, annex_len);
1977 std::memcpy(sighash_out, h.data(), 32);
1978 return UFSECP_OK;
1979 } UFSECP_CATCH_RETURN(ctx)
1980}
1981
1982/* ===========================================================================
1983 * Public key arithmetic
1984 * =========================================================================== */
1985
1987 const uint8_t a33[33],
1988 const uint8_t b33[33],
1989 uint8_t out33[33]) {
1990 if (!ctx || !a33 || !b33 || !out33) return UFSECP_ERR_NULL_ARG;
1991 ctx_clear_err(ctx);
1992 auto pa = point_from_compressed(a33);
1993 if (pa.is_infinity()) {
1994 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey a");
1995 }
1996 auto pb = point_from_compressed(b33);
1997 if (pb.is_infinity()) {
1998 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey b");
1999 }
2000 auto sum = pa.add(pb);
2001 if (sum.is_infinity()) {
2002 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "sum is point at infinity");
2003 }
2004 point_to_compressed(sum, out33);
2005 return UFSECP_OK;
2006}
2007
2009 const uint8_t pubkey33[33],
2010 uint8_t out33[33]) {
2011 if (!ctx || !pubkey33 || !out33) return UFSECP_ERR_NULL_ARG;
2012 ctx_clear_err(ctx);
2013 auto p = point_from_compressed(pubkey33);
2014 if (p.is_infinity()) {
2015 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
2016 }
2017 auto neg = p.negate();
2018 point_to_compressed(neg, out33);
2019 return UFSECP_OK;
2020}
2021
2023 const uint8_t pubkey33[33],
2024 const uint8_t tweak[32],
2025 uint8_t out33[33]) {
2026 if (!ctx || !pubkey33 || !tweak || !out33) return UFSECP_ERR_NULL_ARG;
2027 ctx_clear_err(ctx);
2028 auto p = point_from_compressed(pubkey33);
2029 if (p.is_infinity()) {
2030 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
2031 }
2032 Scalar tw;
2033 if (!scalar_parse_strict(tweak, tw)) {
2034 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "tweak >= n");
2035 }
2036 auto tG = Point::generator().scalar_mul(tw);
2037 auto result = p.add(tG);
2038 if (result.is_infinity()) {
2039 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "tweak_add resulted in infinity");
2040 }
2041 point_to_compressed(result, out33);
2042 return UFSECP_OK;
2043}
2044
2046 const uint8_t pubkey33[33],
2047 const uint8_t tweak[32],
2048 uint8_t out33[33]) {
2049 if (!ctx || !pubkey33 || !tweak || !out33) return UFSECP_ERR_NULL_ARG;
2050 ctx_clear_err(ctx);
2051 auto p = point_from_compressed(pubkey33);
2052 if (p.is_infinity()) {
2053 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
2054 }
2055 Scalar tw;
2056 if (!scalar_parse_strict_nonzero(tweak, tw)) {
2057 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "tweak is zero or >= n");
2058 }
2059 auto result = p.scalar_mul(tw);
2060 if (result.is_infinity()) {
2061 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "tweak_mul resulted in infinity");
2062 }
2063 point_to_compressed(result, out33);
2064 return UFSECP_OK;
2065}
2066
2068 const uint8_t* pubkeys,
2069 size_t n,
2070 uint8_t out33[33]) {
2071 if (!ctx || !pubkeys || !out33) return UFSECP_ERR_NULL_ARG;
2072 if (n == 0) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "need >= 1 pubkey");
2073 ctx_clear_err(ctx);
2074 std::size_t total_pubkey_bytes = 0;
2075 if (!checked_mul_size(n, static_cast<std::size_t>(33), total_pubkey_bytes)) {
2076 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "pubkey array length too large");
2077 }
2078 auto acc = point_from_compressed(pubkeys);
2079 if (acc.is_infinity()) {
2080 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey[0]");
2081 }
2082 for (size_t i = 1; i < n; ++i) {
2083 auto pi = point_from_compressed(pubkeys + i * 33);
2084 if (pi.is_infinity()) {
2085 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey in array");
2086 }
2087 acc = acc.add(pi);
2088 }
2089 if (acc.is_infinity()) {
2090 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "combined pubkey is infinity");
2091 }
2092 point_to_compressed(acc, out33);
2093 return UFSECP_OK;
2094}
2095
2096/* ===========================================================================
2097 * BIP-39
2098 * =========================================================================== */
2099
2101 size_t entropy_bytes,
2102 const uint8_t* entropy_in,
2103 char* mnemonic_out,
2104 size_t* mnemonic_len) {
2105 if (!ctx || !mnemonic_out || !mnemonic_len) return UFSECP_ERR_NULL_ARG;
2106 ctx_clear_err(ctx);
2107 if (entropy_bytes != 16 && entropy_bytes != 20 && entropy_bytes != 24 &&
2108 entropy_bytes != 28 && entropy_bytes != 32) {
2109 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "entropy must be 16/20/24/28/32");
2110 }
2111 try {
2112 auto [mnemonic, ok] = secp256k1::bip39_generate(entropy_bytes, entropy_in);
2113 if (!ok) {
2114 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "BIP-39 generation failed");
2115 }
2116 if (*mnemonic_len < mnemonic.size() + 1) {
2117 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "mnemonic buffer too small");
2118 }
2119 std::memcpy(mnemonic_out, mnemonic.c_str(), mnemonic.size() + 1);
2120 *mnemonic_len = mnemonic.size();
2121 return UFSECP_OK;
2122 } UFSECP_CATCH_RETURN(ctx)
2123}
2124
2126 const char* mnemonic) {
2127 if (!ctx || !mnemonic) return UFSECP_ERR_NULL_ARG;
2128 try {
2129 if (!secp256k1::bip39_validate(std::string(mnemonic))) {
2130 return UFSECP_ERR_BAD_INPUT;
2131 }
2132 return UFSECP_OK;
2133 } catch (...) { return UFSECP_ERR_INTERNAL; }
2134}
2135
2137 const char* mnemonic,
2138 const char* passphrase,
2139 uint8_t seed64_out[64]) {
2140 if (!ctx || !mnemonic || !seed64_out) return UFSECP_ERR_NULL_ARG;
2141 ctx_clear_err(ctx);
2142 try {
2143 const std::string pass = passphrase ? passphrase : "";
2144 auto [seed, ok] = secp256k1::bip39_mnemonic_to_seed(std::string(mnemonic), pass);
2145 if (!ok) {
2146 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid mnemonic");
2147 }
2148 std::memcpy(seed64_out, seed.data(), 64);
2149 secp256k1::detail::secure_erase(seed.data(), seed.size());
2150 return UFSECP_OK;
2151 } UFSECP_CATCH_RETURN(ctx)
2152}
2153
2155 const char* mnemonic,
2156 uint8_t* entropy_out,
2157 size_t* entropy_len) {
2158 if (!ctx || !mnemonic || !entropy_out || !entropy_len) return UFSECP_ERR_NULL_ARG;
2159 ctx_clear_err(ctx);
2160 try {
2161 auto [ent, ok] = secp256k1::bip39_mnemonic_to_entropy(std::string(mnemonic));
2162 if (!ok) {
2163 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid mnemonic");
2164 }
2165 if (*entropy_len < ent.length) {
2166 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "entropy buffer too small");
2167 }
2168 std::memcpy(entropy_out, ent.data.data(), ent.length);
2169 *entropy_len = ent.length;
2170 return UFSECP_OK;
2171 } UFSECP_CATCH_RETURN(ctx)
2172}
2173
2174/* ===========================================================================
2175 * Batch verification
2176 * =========================================================================== */
2177
2179 const uint8_t* entries, size_t n) {
2180 if (!ctx || !entries) return UFSECP_ERR_NULL_ARG;
2181 if (n == 0) return UFSECP_OK;
2182 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
2183 ctx_clear_err(ctx);
2184 std::size_t total_bytes = 0;
2185 if (!checked_mul_size(n, std::size_t{128}, total_bytes))
2186 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch size overflow");
2187 try {
2188 /* Each entry: 32-byte xonly pubkey | 32-byte msg | 64-byte sig = 128 bytes */
2189 std::vector<secp256k1::SchnorrBatchEntry> batch(n);
2190 for (size_t i = 0; i < n; ++i) {
2191 const uint8_t* e = entries + i * 128;
2192 // Strict: reject x-only pubkey >= p at ABI gate
2193 FE pk_fe;
2194 if (!FE::parse_bytes_strict(e, pk_fe)) {
2195 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "non-canonical pubkey (x>=p) in batch");
2196 }
2197 std::memcpy(batch[i].pubkey_x.data(), e, 32);
2198 std::memcpy(batch[i].message.data(), e + 32, 32);
2199 if (!secp256k1::SchnorrSignature::parse_strict(e + 64, batch[i].signature)) {
2200 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid Schnorr sig in batch");
2201 }
2202 }
2203 if (!secp256k1::schnorr_batch_verify(batch)) {
2204 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "batch verify failed");
2205 }
2206 return UFSECP_OK;
2207 } UFSECP_CATCH_RETURN(ctx)
2208}
2209
2211 const uint8_t* entries, size_t n) {
2212 if (!ctx || !entries) return UFSECP_ERR_NULL_ARG;
2213 if (n == 0) return UFSECP_OK;
2214 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
2215 ctx_clear_err(ctx);
2216 std::size_t total_bytes = 0;
2217 if (!checked_mul_size(n, std::size_t{129}, total_bytes))
2218 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch size overflow");
2219 try {
2220 /* Each entry: 32-byte msg | 33-byte pubkey | 64-byte sig = 129 bytes */
2221 std::vector<secp256k1::ECDSABatchEntry> batch(n);
2222 for (size_t i = 0; i < n; ++i) {
2223 const uint8_t* e = entries + i * 129;
2224 std::memcpy(batch[i].msg_hash.data(), e, 32);
2225 batch[i].public_key = point_from_compressed(e + 32);
2226 if (batch[i].public_key.is_infinity()) {
2227 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey in batch");
2228 }
2229 std::array<uint8_t, 64> compact;
2230 std::memcpy(compact.data(), e + 65, 64);
2231 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, batch[i].signature)) {
2232 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid ECDSA sig in batch");
2233 }
2234 }
2235 if (!secp256k1::ecdsa_batch_verify(batch)) {
2236 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "batch verify failed");
2237 }
2238 return UFSECP_OK;
2239 } UFSECP_CATCH_RETURN(ctx)
2240}
2241
2243 ufsecp_ctx* ctx, const uint8_t* entries, size_t n,
2244 size_t* invalid_out, size_t* invalid_count) {
2245 if (!ctx || !entries || !invalid_out || !invalid_count) return UFSECP_ERR_NULL_ARG;
2246 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
2247 ctx_clear_err(ctx);
2248 try {
2249 std::vector<secp256k1::SchnorrBatchEntry> batch(n);
2250 for (size_t i = 0; i < n; ++i) {
2251 const uint8_t* e = entries + i * 128;
2252 FE pk_fe;
2253 if (!FE::parse_bytes_strict(e, pk_fe)) {
2254 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "non-canonical pubkey (x>=p) in batch");
2255 }
2256 std::memcpy(batch[i].pubkey_x.data(), e, 32);
2257 std::memcpy(batch[i].message.data(), e + 32, 32);
2258 if (!secp256k1::SchnorrSignature::parse_strict(e + 64, batch[i].signature)) {
2259 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid Schnorr sig in batch");
2260 }
2261 }
2262 auto invalids = secp256k1::schnorr_batch_identify_invalid(batch.data(), n);
2263 size_t const capacity = *invalid_count;
2264 size_t const count = invalids.size() < capacity ? invalids.size() : capacity;
2265 *invalid_count = invalids.size();
2266 for (size_t i = 0; i < count; ++i) {
2267 invalid_out[i] = invalids[i];
2268 }
2269 return UFSECP_OK;
2270 } UFSECP_CATCH_RETURN(ctx)
2271}
2272
2274 ufsecp_ctx* ctx, const uint8_t* entries, size_t n,
2275 size_t* invalid_out, size_t* invalid_count) {
2276 if (!ctx || !entries || !invalid_out || !invalid_count) return UFSECP_ERR_NULL_ARG;
2277 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "batch count too large");
2278 ctx_clear_err(ctx);
2279 try {
2280 std::vector<secp256k1::ECDSABatchEntry> batch(n);
2281 for (size_t i = 0; i < n; ++i) {
2282 const uint8_t* e = entries + i * 129;
2283 std::memcpy(batch[i].msg_hash.data(), e, 32);
2284 batch[i].public_key = point_from_compressed(e + 32);
2285 if (batch[i].public_key.is_infinity()) {
2286 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey in batch");
2287 }
2288 std::array<uint8_t, 64> compact;
2289 std::memcpy(compact.data(), e + 65, 64);
2290 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, batch[i].signature)) {
2291 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid ECDSA sig in batch");
2292 }
2293 }
2294 auto invalids = secp256k1::ecdsa_batch_identify_invalid(batch.data(), n);
2295 size_t const capacity = *invalid_count;
2296 size_t const count = invalids.size() < capacity ? invalids.size() : capacity;
2297 *invalid_count = invalids.size();
2298 for (size_t i = 0; i < count; ++i) {
2299 invalid_out[i] = invalids[i];
2300 }
2301 return UFSECP_OK;
2302 } UFSECP_CATCH_RETURN(ctx)
2303}
2304
2305/* ===========================================================================
2306 * SHA-512
2307 * =========================================================================== */
2308
2309ufsecp_error_t ufsecp_sha512(const uint8_t* data, size_t len,
2310 uint8_t digest64_out[64]) {
2311 if (!data || !digest64_out) return UFSECP_ERR_NULL_ARG;
2312 auto hash = secp256k1::SHA512::hash(data, len);
2313 std::memcpy(digest64_out, hash.data(), 64);
2314 return UFSECP_OK;
2315}
2316
2317/* ===========================================================================
2318 * Multi-scalar multiplication
2319 * =========================================================================== */
2320
2322 const uint8_t a[32], const uint8_t P33[33],
2323 const uint8_t b[32], const uint8_t Q33[33],
2324 uint8_t out33[33]) {
2325 if (!ctx || !a || !P33 || !b || !Q33 || !out33) return UFSECP_ERR_NULL_ARG;
2326 ctx_clear_err(ctx);
2327 Scalar sa, sb;
2328 if (!scalar_parse_strict(a, sa)) {
2329 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "scalar a >= n");
2330 }
2331 if (!scalar_parse_strict(b, sb)) {
2332 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "scalar b >= n");
2333 }
2334 auto P = point_from_compressed(P33);
2335 if (P.is_infinity()) {
2336 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid point P");
2337 }
2338 auto Q = point_from_compressed(Q33);
2339 if (Q.is_infinity()) {
2340 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid point Q");
2341 }
2342 auto result = secp256k1::shamir_trick(sa, P, sb, Q);
2343 if (result.is_infinity()) {
2344 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "result is infinity");
2345 }
2346 point_to_compressed(result, out33);
2347 return UFSECP_OK;
2348}
2349
2351 const uint8_t* scalars,
2352 const uint8_t* points,
2353 size_t n,
2354 uint8_t out33[33]) {
2355 if (!ctx || !scalars || !points || !out33) return UFSECP_ERR_NULL_ARG;
2356 if (n == 0) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n must be >= 1");
2357 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n too large");
2358 ctx_clear_err(ctx);
2359 std::size_t total_scalar_bytes = 0;
2360 std::size_t total_point_bytes = 0;
2361 if (!checked_mul_size(n, static_cast<std::size_t>(32), total_scalar_bytes)
2362 || !checked_mul_size(n, static_cast<std::size_t>(33), total_point_bytes)) {
2363 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "scalar/point array length too large");
2364 }
2365 try {
2366 std::vector<Scalar> svec(n);
2367 std::vector<Point> pvec(n);
2368 for (size_t i = 0; i < n; ++i) {
2369 if (!scalar_parse_strict(scalars + i * 32, svec[i])) {
2370 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "scalar >= n");
2371 }
2372 pvec[i] = point_from_compressed(points + i * 33);
2373 if (pvec[i].is_infinity()) {
2374 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid point in array");
2375 }
2376 }
2377 auto result = secp256k1::multi_scalar_mul(svec, pvec);
2378 if (result.is_infinity()) {
2379 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "MSM result is infinity");
2380 }
2381 point_to_compressed(result, out33);
2382 return UFSECP_OK;
2383 } UFSECP_CATCH_RETURN(ctx)
2384}
2385
2386/* ===========================================================================
2387 * MuSig2 (BIP-327)
2388 * =========================================================================== */
2389
2391 const uint8_t* pubkeys, size_t n,
2392 uint8_t keyagg_out[UFSECP_MUSIG2_KEYAGG_LEN],
2393 uint8_t agg_pubkey32_out[32]) {
2394 if (!ctx || !pubkeys || !keyagg_out || !agg_pubkey32_out) return UFSECP_ERR_NULL_ARG;
2395 std::memset(keyagg_out, 0, UFSECP_MUSIG2_KEYAGG_LEN);
2396 std::memset(agg_pubkey32_out, 0, 32);
2397 if (n < 2) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "need >= 2 pubkeys");
2398 if (n > kMuSig2MaxKeyAggParticipants) {
2399 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "too many pubkeys for keyagg blob");
2400 }
2401 ctx_clear_err(ctx);
2402 try {
2403 std::vector<std::array<uint8_t, 32>> pks(n);
2404 for (size_t i = 0; i < n; ++i) {
2405 std::memcpy(pks[i].data(), pubkeys + i * 32, 32);
2406 }
2407 auto kagg = secp256k1::musig2_key_agg(pks);
2408 std::memcpy(agg_pubkey32_out, kagg.Q_x.data(), 32);
2409 /* Serialize key agg ctx: n(4) | Q_negated(1) | Q_compressed(33) | coefficients(n*32) */
2410 std::memset(keyagg_out, 0, UFSECP_MUSIG2_KEYAGG_LEN);
2411 const auto nk = static_cast<uint32_t>(kagg.key_coefficients.size());
2412 std::memcpy(keyagg_out, &nk, 4);
2413 keyagg_out[4] = kagg.Q_negated ? 1 : 0;
2414 point_to_compressed(kagg.Q, keyagg_out + 5);
2415 for (uint32_t i = 0; i < nk && (38u + (i+1)*32u <= UFSECP_MUSIG2_KEYAGG_LEN); ++i) {
2416 scalar_to_bytes(kagg.key_coefficients[i], keyagg_out + 38 + static_cast<size_t>(i) * 32);
2417 }
2418 return UFSECP_OK;
2419 } UFSECP_CATCH_RETURN(ctx)
2420}
2421
2423 const uint8_t privkey[32],
2424 const uint8_t pubkey32[32],
2425 const uint8_t agg_pubkey32[32],
2426 const uint8_t msg32[32],
2427 const uint8_t extra_in[32],
2428 uint8_t secnonce_out[UFSECP_MUSIG2_SECNONCE_LEN],
2429 uint8_t pubnonce_out[UFSECP_MUSIG2_PUBNONCE_LEN]) {
2430 if (!ctx || !privkey || !pubkey32 || !agg_pubkey32 || !msg32 ||
2431 !secnonce_out || !pubnonce_out) return UFSECP_ERR_NULL_ARG;
2432 ctx_clear_err(ctx);
2433 Scalar sk;
2434 if (!scalar_parse_strict_nonzero(privkey, sk)) {
2435 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
2436 }
2437 std::array<uint8_t, 32> pk_arr, agg_arr, msg_arr;
2438 std::memcpy(pk_arr.data(), pubkey32, 32);
2439 std::memcpy(agg_arr.data(), agg_pubkey32, 32);
2440 std::memcpy(msg_arr.data(), msg32, 32);
2441 auto [sec, pub] = secp256k1::musig2_nonce_gen(sk, pk_arr, agg_arr, msg_arr, extra_in);
2442 secp256k1::detail::secure_erase(&sk, sizeof(sk));
2443 /* Secret nonce: k1 || k2 */
2444 auto k1_bytes = sec.k1.to_bytes();
2445 auto k2_bytes = sec.k2.to_bytes();
2446 std::memcpy(secnonce_out, k1_bytes.data(), 32);
2447 std::memcpy(secnonce_out + 32, k2_bytes.data(), 32);
2448 secp256k1::detail::secure_erase(k1_bytes.data(), k1_bytes.size());
2449 secp256k1::detail::secure_erase(k2_bytes.data(), k2_bytes.size());
2450 /* Public nonce: R1(33) || R2(33) */
2451 auto pn = pub.serialize();
2452 std::memcpy(pubnonce_out, pn.data(), 66);
2453 secp256k1::detail::secure_erase(&sec, sizeof(sec));
2454 return UFSECP_OK;
2455}
2456
2458 const uint8_t* pubnonces, size_t n,
2459 uint8_t aggnonce_out[UFSECP_MUSIG2_AGGNONCE_LEN]) {
2460 if (!ctx || !pubnonces || !aggnonce_out) return UFSECP_ERR_NULL_ARG;
2461 std::memset(aggnonce_out, 0, UFSECP_MUSIG2_AGGNONCE_LEN);
2462 if (n < 2) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "need >= 2 nonces");
2463 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "nonce count too large");
2464 ctx_clear_err(ctx);
2465 std::size_t total_bytes = 0;
2466 if (!checked_mul_size(n, std::size_t{66}, total_bytes))
2467 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "nonce array size overflow");
2468 try {
2469 std::vector<secp256k1::MuSig2PubNonce> pns(n);
2470 for (size_t i = 0; i < n; ++i) {
2471 if (point_from_compressed(pubnonces + i * 66).is_infinity()) {
2472 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid pubnonce R1");
2473 }
2474 if (point_from_compressed(pubnonces + i * 66 + 33).is_infinity()) {
2475 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid pubnonce R2");
2476 }
2477 std::array<uint8_t, 66> buf;
2478 std::memcpy(buf.data(), pubnonces + i * 66, 66);
2480 }
2481 auto agg = secp256k1::musig2_nonce_agg(pns);
2482 /* Serialize: R1(33) || R2(33) */
2483 auto r1 = agg.R1.to_compressed();
2484 auto r2 = agg.R2.to_compressed();
2485 std::memcpy(aggnonce_out, r1.data(), 33);
2486 std::memcpy(aggnonce_out + 33, r2.data(), 33);
2487 return UFSECP_OK;
2488 } UFSECP_CATCH_RETURN(ctx)
2489}
2490
2492 ufsecp_ctx* ctx,
2493 const uint8_t aggnonce[UFSECP_MUSIG2_AGGNONCE_LEN],
2494 const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN],
2495 const uint8_t msg32[32],
2496 uint8_t session_out[UFSECP_MUSIG2_SESSION_LEN]) {
2497 if (!ctx || !aggnonce || !keyagg || !msg32 || !session_out) return UFSECP_ERR_NULL_ARG;
2498 std::memset(session_out, 0, UFSECP_MUSIG2_SESSION_LEN);
2499 ctx_clear_err(ctx);
2500 /* Deserialize agg nonce */
2502 an.R1 = point_from_compressed(aggnonce);
2503 if (an.R1.is_infinity()) {
2504 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid agg nonce R1");
2505 }
2506 an.R2 = point_from_compressed(aggnonce + 33);
2507 if (an.R2.is_infinity()) {
2508 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid agg nonce R2");
2509 }
2510 /* Deserialize key agg context */
2512 {
2513 const ufsecp_error_t rc = parse_musig2_keyagg(ctx, keyagg, kagg);
2514 if (rc != UFSECP_OK) {
2515 return rc;
2516 }
2517 }
2518 std::array<uint8_t, 32> msg_arr;
2519 std::memcpy(msg_arr.data(), msg32, 32);
2520 auto sess = secp256k1::musig2_start_sign_session(an, kagg, msg_arr);
2521 /* Serialize session: R(33) | b(32) | e(32) | R_negated(1) = 98 bytes */
2522 std::memset(session_out, 0, UFSECP_MUSIG2_SESSION_LEN);
2523 point_to_compressed(sess.R, session_out);
2524 scalar_to_bytes(sess.b, session_out + 33);
2525 scalar_to_bytes(sess.e, session_out + 65);
2526 session_out[97] = sess.R_negated ? 1 : 0;
2527 const uint32_t participant_count = static_cast<uint32_t>(kagg.key_coefficients.size());
2528 std::memcpy(session_out + kMuSig2SessionCountOffset, &participant_count, sizeof(participant_count));
2529 return UFSECP_OK;
2530}
2531
2533 ufsecp_ctx* ctx,
2534 uint8_t secnonce[UFSECP_MUSIG2_SECNONCE_LEN],
2535 const uint8_t privkey[32],
2536 const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN],
2537 const uint8_t session[UFSECP_MUSIG2_SESSION_LEN],
2538 size_t signer_index,
2539 uint8_t partial_sig32_out[32]) {
2540 if (!ctx || !secnonce || !privkey || !keyagg || !session || !partial_sig32_out) {
2541 return UFSECP_ERR_NULL_ARG;
2542 }
2543 ctx_clear_err(ctx);
2544 Scalar sk;
2545 if (!scalar_parse_strict_nonzero(privkey, sk)) {
2546 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
2547 }
2549 Scalar k1, k2;
2550 if (!scalar_parse_strict_nonzero(secnonce, k1)) {
2551 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid secnonce k1");
2552 }
2553 if (!scalar_parse_strict_nonzero(secnonce + 32, k2)) {
2554 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid secnonce k2");
2555 }
2556 sn.k1 = k1;
2557 sn.k2 = k2;
2559 {
2560 const ufsecp_error_t rc = parse_musig2_keyagg(ctx, keyagg, kagg);
2561 if (rc != UFSECP_OK) {
2562 return rc;
2563 }
2564 }
2565 if (signer_index >= kagg.key_coefficients.size()) {
2566 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "signer_index out of range");
2567 }
2569 uint32_t session_participant_count = 0;
2570 {
2571 const ufsecp_error_t rc = parse_musig2_session(ctx, session, sess, session_participant_count);
2572 if (rc != UFSECP_OK) {
2573 return rc;
2574 }
2575 }
2576 if (session_participant_count != kagg.key_coefficients.size()) {
2577 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "session participant count does not match keyagg");
2578 }
2579 auto psig = secp256k1::musig2_partial_sign(sn, sk, kagg, sess, signer_index);
2580 secp256k1::detail::secure_erase(&sk, sizeof(sk));
2581 secp256k1::detail::secure_erase(&sn, sizeof(sn));
2582 // Consume caller's secnonce to prevent catastrophic nonce reuse
2584 scalar_to_bytes(psig, partial_sig32_out);
2585 return UFSECP_OK;
2586}
2587
2589 ufsecp_ctx* ctx,
2590 const uint8_t partial_sig32[32],
2591 const uint8_t pubnonce[UFSECP_MUSIG2_PUBNONCE_LEN],
2592 const uint8_t pubkey32[32],
2593 const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN],
2594 const uint8_t session[UFSECP_MUSIG2_SESSION_LEN],
2595 size_t signer_index) {
2596 if (!ctx || !partial_sig32 || !pubnonce || !pubkey32 || !keyagg || !session) {
2597 return UFSECP_ERR_NULL_ARG;
2598 }
2599 ctx_clear_err(ctx);
2600 Scalar psig;
2601 if (!scalar_parse_strict(partial_sig32, psig)) {
2602 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "partial sig >= n");
2603 }
2604 std::array<uint8_t, 66> pn_buf;
2605 std::memcpy(pn_buf.data(), pubnonce, 66);
2607 std::array<uint8_t, 32> pk_arr;
2608 std::memcpy(pk_arr.data(), pubkey32, 32);
2610 {
2611 const ufsecp_error_t rc = parse_musig2_keyagg(ctx, keyagg, kagg);
2612 if (rc != UFSECP_OK) {
2613 return rc;
2614 }
2615 }
2616 if (signer_index >= kagg.key_coefficients.size()) {
2617 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "signer_index out of range");
2618 }
2620 uint32_t session_participant_count = 0;
2621 {
2622 const ufsecp_error_t rc = parse_musig2_session(ctx, session, sess, session_participant_count);
2623 if (rc != UFSECP_OK) {
2624 return rc;
2625 }
2626 }
2627 if (session_participant_count != kagg.key_coefficients.size()) {
2628 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "session participant count does not match keyagg");
2629 }
2630 if (!secp256k1::musig2_partial_verify(psig, pn, pk_arr, kagg, sess, signer_index)) {
2631 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "partial sig verify failed");
2632 }
2633 return UFSECP_OK;
2634}
2635
2637 ufsecp_ctx* ctx,
2638 const uint8_t* partial_sigs, size_t n,
2639 const uint8_t session[UFSECP_MUSIG2_SESSION_LEN],
2640 uint8_t sig64_out[64]) {
2641 if (!ctx || !partial_sigs || !session || !sig64_out) return UFSECP_ERR_NULL_ARG;
2642 ctx_clear_err(ctx);
2643 if (n == 0) {
2644 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial_sigs must be non-empty");
2645 }
2646 if (n > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial sig count too large");
2647 std::size_t total_bytes = 0;
2648 if (!checked_mul_size(n, std::size_t{32}, total_bytes))
2649 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial sig size overflow");
2650 try {
2651 std::vector<Scalar> psigs(n);
2652 for (size_t i = 0; i < n; ++i) {
2653 if (!scalar_parse_strict(partial_sigs + i * 32, psigs[i])) {
2654 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "partial sig >= n");
2655 }
2656 }
2658 uint32_t session_participant_count = 0;
2659 {
2660 const ufsecp_error_t rc = parse_musig2_session(ctx, session, sess, session_participant_count);
2661 if (rc != UFSECP_OK) {
2662 return rc;
2663 }
2664 }
2665 if (n != session_participant_count) {
2666 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial_sigs count does not match session participant count");
2667 }
2668 auto final_sig = secp256k1::musig2_partial_sig_agg(psigs, sess);
2669 std::memcpy(sig64_out, final_sig.data(), 64);
2670 return UFSECP_OK;
2671 } UFSECP_CATCH_RETURN(ctx)
2672}
2673
2674/* ===========================================================================
2675 * FROST (threshold signatures)
2676 * =========================================================================== */
2677
2679 ufsecp_ctx* ctx,
2680 uint32_t participant_id, uint32_t threshold, uint32_t num_participants,
2681 const uint8_t seed[32],
2682 uint8_t* commits_out, size_t* commits_len,
2683 uint8_t* shares_out, size_t* shares_len) {
2684 if (!ctx || !seed || !commits_out || !commits_len || !shares_out || !shares_len) {
2685 return UFSECP_ERR_NULL_ARG;
2686 }
2687 ctx_clear_err(ctx);
2688 if (threshold < 2 || threshold > num_participants) {
2689 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid threshold");
2690 }
2691 if (participant_id == 0 || participant_id > num_participants) {
2692 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid participant_id");
2693 }
2694 if (num_participants > kMaxBatchN) {
2695 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "num_participants too large");
2696 }
2697 try {
2698 std::size_t required_commit_coeff_bytes = 0;
2699 std::size_t required_commits = 0;
2700 std::size_t required_shares = 0;
2701 if (!checked_mul_size(static_cast<std::size_t>(threshold), static_cast<std::size_t>(33), required_commit_coeff_bytes)
2702 || !checked_add_size(static_cast<std::size_t>(8), required_commit_coeff_bytes, required_commits)
2703 || !checked_mul_size(static_cast<std::size_t>(num_participants), static_cast<std::size_t>(UFSECP_FROST_SHARE_LEN), required_shares)) {
2704 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "FROST cardinality too large");
2705 }
2706 if (*commits_len < required_commits) {
2707 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "commits buffer too small");
2708 }
2709 if (*shares_len < required_shares) {
2710 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "shares buffer too small");
2711 }
2712 std::array<uint8_t, 32> seed_arr;
2713 std::memcpy(seed_arr.data(), seed, 32);
2714 auto [commit, shares] = secp256k1::frost_keygen_begin(
2715 participant_id, threshold, num_participants, seed_arr);
2716 secp256k1::detail::secure_erase(seed_arr.data(), 32);
2717 auto erase_shares = [&]() {
2718 for (auto& share : shares) {
2719 secp256k1::detail::secure_erase(&share.value, sizeof(share.value));
2720 }
2721 };
2722 /* Serialize commitment: coeff count(4) + from(4) + coeffs(33 each) */
2723 const size_t coeff_count = commit.coeffs.size();
2724 const size_t needed_commits = 8 + coeff_count * 33;
2725 if (*commits_len < needed_commits) {
2726 erase_shares();
2727 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "commits buffer too small");
2728 }
2729 const auto cc32 = static_cast<uint32_t>(coeff_count);
2730 std::memcpy(commits_out, &cc32, 4);
2731 std::memcpy(commits_out + 4, &commit.from, 4);
2732 for (size_t i = 0; i < coeff_count; ++i) {
2733 point_to_compressed(commit.coeffs[i], commits_out + 8 + i * 33);
2734
2735 }
2736 *commits_len = 8 + coeff_count * 33;
2737 /* Serialize shares */
2738 const size_t needed_shares = shares.size() * UFSECP_FROST_SHARE_LEN;
2739 if (*shares_len < needed_shares) {
2740 erase_shares();
2741 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "shares buffer too small");
2742 }
2743 for (size_t i = 0; i < shares.size(); ++i) {
2744 uint8_t* s = shares_out + i * UFSECP_FROST_SHARE_LEN;
2745 std::memcpy(s, &shares[i].from, 4);
2746 scalar_to_bytes(shares[i].value, s + 4);
2747 }
2748 *shares_len = needed_shares;
2749 erase_shares();
2750 return UFSECP_OK;
2751 } UFSECP_CATCH_RETURN(ctx)
2752}
2753
2755 ufsecp_ctx* ctx,
2756 uint32_t participant_id,
2757 const uint8_t* all_commits, size_t commits_len,
2758 const uint8_t* received_shares, size_t shares_len,
2759 uint32_t threshold, uint32_t num_participants,
2760 uint8_t keypkg_out[UFSECP_FROST_KEYPKG_LEN]) {
2761 if (!ctx || !all_commits || !received_shares || !keypkg_out) return UFSECP_ERR_NULL_ARG;
2762 ctx_clear_err(ctx);
2763 if (threshold < 2 || threshold > num_participants) {
2764 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid threshold");
2765 }
2766 if (participant_id == 0 || participant_id > num_participants) {
2767 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid participant_id");
2768 }
2769 if (num_participants > kMaxBatchN) {
2770 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "num_participants too large");
2771 }
2772 try {
2773 std::size_t expected_commit_coeff_bytes = 0;
2774 std::size_t expected_commit_record_len = 0;
2775 std::size_t expected_commits_len = 0;
2776 std::size_t expected_shares_len = 0;
2777 if (!checked_mul_size(static_cast<std::size_t>(threshold), static_cast<std::size_t>(33), expected_commit_coeff_bytes)
2778 || !checked_add_size(static_cast<std::size_t>(8), expected_commit_coeff_bytes, expected_commit_record_len)
2779 || !checked_mul_size(static_cast<std::size_t>(num_participants), expected_commit_record_len, expected_commits_len)
2780 || !checked_mul_size(static_cast<std::size_t>(num_participants), static_cast<std::size_t>(UFSECP_FROST_SHARE_LEN), expected_shares_len)) {
2781 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "FROST cardinality too large");
2782 }
2783 if (commits_len != expected_commits_len) {
2784 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "all_commits length does not match threshold and num_participants");
2785 }
2786 if (shares_len != expected_shares_len) {
2787 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "received_shares length does not match num_participants");
2788 }
2789 /* Deserialize commitments */
2790 std::vector<secp256k1::FrostCommitment> commits;
2791 std::vector<uint8_t> seen_commit_from(static_cast<size_t>(num_participants) + 1, 0);
2792 size_t pos = 0;
2793 while (pos < commits_len) {
2795 uint32_t cc = 0;
2796 if (pos + 8 > commits_len) {
2797 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "truncated commit header");
2798 }
2799 std::memcpy(&cc, all_commits + pos, 4); pos += 4;
2800 std::memcpy(&fc.from, all_commits + pos, 4); pos += 4;
2801 if (cc != threshold) {
2802 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment coefficient count");
2803 }
2804 if (fc.from == 0 || fc.from > num_participants) {
2805 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment sender");
2806 }
2807 if (seen_commit_from[fc.from] != 0) {
2808 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate commitment sender");
2809 }
2810 seen_commit_from[fc.from] = 1;
2811 if (pos + static_cast<size_t>(cc) * 33 > commits_len) {
2812 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "truncated commit coefficients");
2813 }
2814 for (uint32_t j = 0; j < cc; ++j) {
2815 auto pt = point_from_compressed(all_commits + pos);
2816 if (pt.is_infinity()) {
2817 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment coefficient");
2818 }
2819 fc.coeffs.push_back(pt);
2820 pos += 33;
2821 }
2822 commits.push_back(std::move(fc));
2823 }
2824 if (commits.size() != num_participants) {
2825 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment count");
2826 }
2827 /* Deserialize shares */
2828 if (shares_len == 0 || (shares_len % UFSECP_FROST_SHARE_LEN) != 0) {
2829 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid share blob length");
2830 }
2831 const size_t n_shares = shares_len / UFSECP_FROST_SHARE_LEN;
2832 if (n_shares != num_participants) {
2833 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid share count");
2834 }
2835 std::vector<secp256k1::FrostShare> shares(n_shares);
2836 auto erase_shares = [&]() {
2837 for (auto& share : shares) {
2838 secp256k1::detail::secure_erase(&share.value, sizeof(share.value));
2839 }
2840 };
2841 std::vector<uint8_t> seen_share_from(static_cast<size_t>(num_participants) + 1, 0);
2842 for (size_t i = 0; i < n_shares; ++i) {
2843 const uint8_t* s = received_shares + i * UFSECP_FROST_SHARE_LEN;
2844 std::memcpy(&shares[i].from, s, 4);
2845 if (shares[i].from == 0 || shares[i].from > num_participants) {
2846 erase_shares();
2847 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid share sender");
2848 }
2849 if (seen_share_from[shares[i].from] != 0) {
2850 erase_shares();
2851 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate share sender");
2852 }
2853 seen_share_from[shares[i].from] = 1;
2854 shares[i].id = participant_id;
2855 Scalar v;
2856 if (!scalar_parse_strict(s + 4, v)) {
2857 erase_shares();
2858 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid share scalar");
2859 }
2860 shares[i].value = v;
2861 }
2862 auto [kp, ok] = secp256k1::frost_keygen_finalize(
2863 participant_id, commits, shares, threshold, num_participants);
2864 if (!ok) {
2865 erase_shares();
2866 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "FROST keygen finalize failed");
2867 }
2868 erase_shares();
2869 /* Serialize FrostKeyPackage: id(4) | threshold(4) | num_participants(4) |
2870 signing_share(32) | verification_share(33) | group_public_key(33) = 110 bytes */
2871 std::memset(keypkg_out, 0, UFSECP_FROST_KEYPKG_LEN);
2872 std::memcpy(keypkg_out, &kp.id, 4);
2873 std::memcpy(keypkg_out + 4, &kp.threshold, 4);
2874 std::memcpy(keypkg_out + 8, &kp.num_participants, 4);
2875 scalar_to_bytes(kp.signing_share, keypkg_out + 12);
2876 point_to_compressed(kp.verification_share, keypkg_out + 44);
2877 point_to_compressed(kp.group_public_key, keypkg_out + 77);
2878 secp256k1::detail::secure_erase(&kp.signing_share, sizeof(kp.signing_share));
2879 return UFSECP_OK;
2880 } UFSECP_CATCH_RETURN(ctx)
2881}
2882
2884 ufsecp_ctx* ctx,
2885 uint32_t participant_id,
2886 const uint8_t nonce_seed[32],
2887 uint8_t nonce_out[UFSECP_FROST_NONCE_LEN],
2888 uint8_t nonce_commit_out[UFSECP_FROST_NONCE_COMMIT_LEN]) {
2889 if (!ctx || !nonce_seed || !nonce_out || !nonce_commit_out) return UFSECP_ERR_NULL_ARG;
2890 ctx_clear_err(ctx);
2891 if (participant_id == 0) {
2892 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid participant_id");
2893 }
2894 std::array<uint8_t, 32> seed_arr;
2895 std::memcpy(seed_arr.data(), nonce_seed, 32);
2896 auto [nonce, commit] = secp256k1::frost_sign_nonce_gen(participant_id, seed_arr);
2897 auto h_bytes = nonce.hiding_nonce.to_bytes();
2898 auto b_bytes = nonce.binding_nonce.to_bytes();
2899 std::memcpy(nonce_out, h_bytes.data(), 32);
2900 std::memcpy(nonce_out + 32, b_bytes.data(), 32);
2901 secp256k1::detail::secure_erase(seed_arr.data(), 32);
2902 secp256k1::detail::secure_erase(&nonce.hiding_nonce, sizeof(nonce.hiding_nonce));
2903 secp256k1::detail::secure_erase(&nonce.binding_nonce, sizeof(nonce.binding_nonce));
2904 secp256k1::detail::secure_erase(h_bytes.data(), 32);
2905 secp256k1::detail::secure_erase(b_bytes.data(), 32);
2906 std::memcpy(nonce_commit_out, &commit.id, 4);
2907 auto hp = commit.hiding_point.to_compressed();
2908 auto bp = commit.binding_point.to_compressed();
2909 std::memcpy(nonce_commit_out + 4, hp.data(), 33);
2910 std::memcpy(nonce_commit_out + 37, bp.data(), 33);
2911 return UFSECP_OK;
2912}
2913
2930 ufsecp_ctx* ctx,
2931 const uint8_t keypkg[UFSECP_FROST_KEYPKG_LEN],
2932 const uint8_t nonce[UFSECP_FROST_NONCE_LEN],
2933 const uint8_t msg32[32],
2934 const uint8_t* nonce_commits, size_t n_signers,
2935 uint8_t partial_sig_out[36]) {
2936 if (!ctx || !keypkg || !nonce || !msg32 || !nonce_commits || !partial_sig_out) {
2937 return UFSECP_ERR_NULL_ARG;
2938 }
2939 ctx_clear_err(ctx);
2940 if (n_signers == 0) {
2941 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n_signers must be non-zero");
2942 }
2943 if (n_signers > kMaxBatchN) {
2944 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n_signers too large");
2945 }
2946 std::size_t nc_total = 0;
2947 if (!checked_mul_size(n_signers, std::size_t{UFSECP_FROST_NONCE_COMMIT_LEN}, nc_total))
2948 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "nonce commit size overflow");
2949 try {
2951 std::memcpy(&kp.id, keypkg, 4);
2952 std::memcpy(&kp.threshold, keypkg + 4, 4);
2953 std::memcpy(&kp.num_participants, keypkg + 8, 4);
2954 if (kp.num_participants == 0 || kp.id == 0 || kp.id > kp.num_participants) {
2955 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid key package participant metadata");
2956 }
2957 if (kp.threshold < 2 || kp.threshold > kp.num_participants) {
2958 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid key package threshold");
2959 }
2960 if (n_signers > kp.num_participants) {
2961 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid signer count");
2962 }
2963 if (!scalar_parse_strict(keypkg + 12, kp.signing_share)) {
2964 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid signing share in keypkg");
2965 }
2966 kp.verification_share = point_from_compressed(keypkg + 44);
2968 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid verification share");
2969 }
2970 kp.group_public_key = point_from_compressed(keypkg + 77);
2971 if (kp.group_public_key.is_infinity()) {
2972 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid group public key");
2973 }
2975 Scalar h, b;
2976 if (!scalar_parse_strict(nonce, h)) {
2977 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid hiding nonce");
2978 }
2979 if (!scalar_parse_strict(nonce + 32, b)) {
2980 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid binding nonce");
2981 }
2982 fn.hiding_nonce = h;
2983 fn.binding_nonce = b;
2984 std::array<uint8_t, 32> msg_arr;
2985 std::memcpy(msg_arr.data(), msg32, 32);
2986 std::vector<secp256k1::FrostNonceCommitment> ncs(n_signers);
2987 size_t self_commitment_count = 0;
2988 ufsecp_error_t nc_err = UFSECP_OK;
2989 for (size_t i = 0; i < n_signers; ++i) {
2990 const uint8_t* nc = nonce_commits + i * UFSECP_FROST_NONCE_COMMIT_LEN;
2991 std::memcpy(&ncs[i].id, nc, 4);
2992 if (ncs[i].id == 0 || ncs[i].id > kp.num_participants) {
2993 nc_err = ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid nonce commitment signer");
2994 break;
2995 }
2996 for (size_t j = 0; j < i; ++j) {
2997 if (ncs[j].id == ncs[i].id) {
2998 nc_err = ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate nonce commitment signer");
2999 break;
3000 }
3001 }
3002 if (nc_err != UFSECP_OK) break;
3003 if (ncs[i].id == kp.id) {
3004 ++self_commitment_count;
3005 }
3006 ncs[i].hiding_point = point_from_compressed(nc + 4);
3007 if (ncs[i].hiding_point.is_infinity()) {
3008 nc_err = ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid hiding nonce point");
3009 break;
3010 }
3011 ncs[i].binding_point = point_from_compressed(nc + 37);
3012 if (ncs[i].binding_point.is_infinity()) {
3013 nc_err = ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid binding nonce point");
3014 break;
3015 }
3016 }
3017 if (nc_err == UFSECP_OK && self_commitment_count != 1) {
3018 nc_err = ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "missing signer nonce commitment");
3019 }
3020 if (nc_err != UFSECP_OK) {
3024 secp256k1::detail::secure_erase(&h, sizeof(h));
3025 secp256k1::detail::secure_erase(&b, sizeof(b));
3026 return nc_err;
3027 }
3028 auto psig = secp256k1::frost_sign(kp, fn, msg_arr, ncs);
3032 secp256k1::detail::secure_erase(&h, sizeof(h));
3033 secp256k1::detail::secure_erase(&b, sizeof(b));
3034 std::memcpy(partial_sig_out, &psig.id, 4);
3035 scalar_to_bytes(psig.z_i, partial_sig_out + 4);
3036 return UFSECP_OK;
3037 } UFSECP_CATCH_RETURN(ctx)
3038}
3039
3041 ufsecp_ctx* ctx,
3042 const uint8_t partial_sig[36],
3043 const uint8_t verification_share33[33],
3044 const uint8_t* nonce_commits, size_t n_signers,
3045 const uint8_t msg32[32],
3046 const uint8_t group_pubkey33[33]) {
3047 if (!ctx || !partial_sig || !verification_share33 || !nonce_commits || !msg32 || !group_pubkey33) {
3048 return UFSECP_ERR_NULL_ARG;
3049 }
3050 ctx_clear_err(ctx);
3051 if (n_signers == 0) {
3052 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n_signers must be non-zero");
3053 }
3054 if (n_signers > kMaxBatchN) {
3055 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n_signers too large");
3056 }
3057 std::size_t nc_total = 0;
3058 if (!checked_mul_size(n_signers, std::size_t{UFSECP_FROST_NONCE_COMMIT_LEN}, nc_total))
3059 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "nonce commit size overflow");
3060 try {
3062 std::memcpy(&psig.id, partial_sig, 4);
3063 if (psig.id == 0) {
3064 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial_sig.id must be non-zero");
3065 }
3066 Scalar z;
3067 if (!scalar_parse_strict(partial_sig + 4, z)) {
3068 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid partial sig scalar");
3069 }
3070 psig.z_i = z;
3071 auto vs = point_from_compressed(verification_share33);
3072 if (vs.is_infinity()) {
3073 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid verification share");
3074 }
3075 std::vector<secp256k1::FrostNonceCommitment> ncs(n_signers);
3076 secp256k1::FrostNonceCommitment signer_commit{};
3077 size_t signer_matches = 0;
3078 for (size_t i = 0; i < n_signers; ++i) {
3079 const uint8_t* nc = nonce_commits + i * UFSECP_FROST_NONCE_COMMIT_LEN;
3080 std::memcpy(&ncs[i].id, nc, 4);
3081 if (ncs[i].id == 0) {
3082 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "nonce commitment signer IDs must be non-zero");
3083 }
3084 for (size_t j = 0; j < i; ++j) {
3085 if (ncs[j].id == ncs[i].id) {
3086 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate nonce commitment signer IDs");
3087 }
3088 }
3089 ncs[i].hiding_point = point_from_compressed(nc + 4);
3090 if (ncs[i].hiding_point.is_infinity()) {
3091 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid hiding nonce point");
3092 }
3093 ncs[i].binding_point = point_from_compressed(nc + 37);
3094 if (ncs[i].binding_point.is_infinity()) {
3095 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid binding nonce point");
3096 }
3097 if (ncs[i].id == psig.id) {
3098 signer_commit = ncs[i];
3099 ++signer_matches;
3100 }
3101 }
3102 if (signer_matches != 1) {
3104 signer_matches == 0 ? "partial_sig.id not found in nonce_commits"
3105 : "partial_sig.id must appear exactly once in nonce_commits");
3106 }
3107 auto gp = point_from_compressed(group_pubkey33);
3108 if (gp.is_infinity()) {
3109 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid group public key");
3110 }
3111 std::array<uint8_t, 32> msg_arr;
3112 std::memcpy(msg_arr.data(), msg32, 32);
3113 const bool ok = secp256k1::frost_verify_partial(psig, signer_commit, vs, msg_arr, ncs, gp);
3114 if (!ok) {
3115 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "FROST partial signature verification failed");
3116 }
3117 return UFSECP_OK;
3118 } UFSECP_CATCH_RETURN(ctx)
3119}
3120
3122 ufsecp_ctx* ctx,
3123 const uint8_t* partial_sigs, size_t n,
3124 const uint8_t* nonce_commits, size_t n_signers,
3125 const uint8_t group_pubkey33[33],
3126 const uint8_t msg32[32],
3127 uint8_t sig64_out[64]) {
3128 if (!ctx || !partial_sigs || !nonce_commits || !group_pubkey33 || !msg32 || !sig64_out) {
3129 return UFSECP_ERR_NULL_ARG;
3130 }
3131 ctx_clear_err(ctx);
3132 if (n == 0) {
3133 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial_sigs must be non-empty");
3134 }
3135 if (n_signers == 0) {
3136 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "n_signers must be non-zero");
3137 }
3138 if (n != n_signers) {
3139 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial/nonces signer count mismatch");
3140 }
3141 if (n > kMaxBatchN) {
3142 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "signer count too large");
3143 }
3144 std::size_t psig_total = 0, nc_total = 0;
3145 if (!checked_mul_size(n, std::size_t{36}, psig_total)
3146 || !checked_mul_size(n_signers, std::size_t{UFSECP_FROST_NONCE_COMMIT_LEN}, nc_total))
3147 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "frost aggregate size overflow");
3148 try {
3149 std::vector<secp256k1::FrostPartialSig> psigs(n);
3150 for (size_t i = 0; i < n; ++i) {
3151 const uint8_t* ps = partial_sigs + i * 36;
3152 std::memcpy(&psigs[i].id, ps, 4);
3153 if (psigs[i].id == 0) {
3154 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid partial sig signer");
3155 }
3156 for (size_t j = 0; j < i; ++j) {
3157 if (psigs[j].id == psigs[i].id) {
3158 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate partial sig signer");
3159 }
3160 }
3161 Scalar z;
3162 if (!scalar_parse_strict(ps + 4, z)) {
3163 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid partial sig scalar");
3164 }
3165 psigs[i].z_i = z;
3166 }
3167 std::vector<secp256k1::FrostNonceCommitment> ncs(n_signers);
3168 for (size_t i = 0; i < n_signers; ++i) {
3169 const uint8_t* nc = nonce_commits + i * UFSECP_FROST_NONCE_COMMIT_LEN;
3170 std::memcpy(&ncs[i].id, nc, 4);
3171 if (ncs[i].id == 0) {
3172 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid nonce commitment signer");
3173 }
3174 for (size_t j = 0; j < i; ++j) {
3175 if (ncs[j].id == ncs[i].id) {
3176 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "duplicate nonce commitment signer");
3177 }
3178 }
3179 ncs[i].hiding_point = point_from_compressed(nc + 4);
3180 if (ncs[i].hiding_point.is_infinity()) {
3181 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid hiding nonce point");
3182 }
3183 ncs[i].binding_point = point_from_compressed(nc + 37);
3184 if (ncs[i].binding_point.is_infinity()) {
3185 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid binding nonce point");
3186 }
3187 }
3188 for (const auto& psig : psigs) {
3189 bool found = false;
3190 for (const auto& nc : ncs) {
3191 if (nc.id == psig.id) {
3192 found = true;
3193 break;
3194 }
3195 }
3196 if (!found) {
3197 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "partial sig signer missing from nonce commitments");
3198 }
3199 }
3200 auto gp = point_from_compressed(group_pubkey33);
3201 if (gp.is_infinity()) {
3202 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "invalid group public key");
3203 }
3204 std::array<uint8_t, 32> msg_arr;
3205 std::memcpy(msg_arr.data(), msg32, 32);
3206 auto sig = secp256k1::frost_aggregate(psigs, ncs, gp, msg_arr);
3207 auto bytes = sig.to_bytes();
3208 std::memcpy(sig64_out, bytes.data(), 64);
3209 return UFSECP_OK;
3210 } UFSECP_CATCH_RETURN(ctx)
3211}
3212
3213/* ===========================================================================
3214 * Adaptor signatures
3215 * =========================================================================== */
3216
3218 ufsecp_ctx* ctx,
3219 const uint8_t privkey[32],
3220 const uint8_t msg32[32],
3221 const uint8_t adaptor_point33[33],
3222 const uint8_t aux_rand[32],
3223 uint8_t pre_sig_out[UFSECP_SCHNORR_ADAPTOR_SIG_LEN]) {
3224 if (!ctx || !privkey || !msg32 || !adaptor_point33 || !aux_rand || !pre_sig_out) {
3225 return UFSECP_ERR_NULL_ARG;
3226 }
3227 ctx_clear_err(ctx);
3228 Scalar sk;
3229 if (!scalar_parse_strict_nonzero(privkey, sk)) {
3230 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
3231 }
3232 std::array<uint8_t, 32> msg_arr, aux_arr;
3233 std::memcpy(msg_arr.data(), msg32, 32);
3234 std::memcpy(aux_arr.data(), aux_rand, 32);
3235 auto ap = point_from_compressed(adaptor_point33);
3236 if (ap.is_infinity()) {
3237 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid adaptor point");
3238 }
3239 auto pre = secp256k1::schnorr_adaptor_sign(sk, msg_arr, ap, aux_arr);
3240 secp256k1::detail::secure_erase(&sk, sizeof(sk));
3241 auto rhat = pre.R_hat.to_compressed();
3242 auto shat = pre.s_hat.to_bytes();
3243 std::memcpy(pre_sig_out, rhat.data(), 33);
3244 std::memcpy(pre_sig_out + 33, shat.data(), 32);
3245 /* Serialize needs_negation as a 32-byte flag for completeness */
3246 std::memset(pre_sig_out + 65, 0, 32);
3247 pre_sig_out[65] = pre.needs_negation ? 1 : 0;
3248 return UFSECP_OK;
3249}
3250
3252 ufsecp_ctx* ctx,
3253 const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN],
3254 const uint8_t pubkey_x[32],
3255 const uint8_t msg32[32],
3256 const uint8_t adaptor_point33[33]) {
3257 if (!ctx || !pre_sig || !pubkey_x || !msg32 || !adaptor_point33) {
3258 return UFSECP_ERR_NULL_ARG;
3259 }
3260 ctx_clear_err(ctx);
3262 as.R_hat = point_from_compressed(pre_sig);
3263 if (as.R_hat.is_infinity()) {
3264 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3265 }
3266 Scalar shat;
3267 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3268 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3269 }
3270 as.s_hat = shat;
3271 as.needs_negation = (pre_sig[65] != 0);
3272 // Strict: reject x-only pubkey >= p at ABI gate
3273 FE pk_fe;
3274 if (!FE::parse_bytes_strict(pubkey_x, pk_fe)) {
3275 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "non-canonical pubkey (x>=p)");
3276 }
3277 std::array<uint8_t, 32> pk_arr, msg_arr;
3278 std::memcpy(pk_arr.data(), pubkey_x, 32);
3279 std::memcpy(msg_arr.data(), msg32, 32);
3280 auto ap = point_from_compressed(adaptor_point33);
3281 if (ap.is_infinity()) {
3282 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid adaptor point");
3283 }
3284 if (!secp256k1::schnorr_adaptor_verify(as, pk_arr, msg_arr, ap)) {
3285 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "adaptor verify failed");
3286 }
3287 return UFSECP_OK;
3288}
3289
3291 ufsecp_ctx* ctx,
3292 const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN],
3293 const uint8_t adaptor_secret[32],
3294 uint8_t sig64_out[64]) {
3295 if (!ctx || !pre_sig || !adaptor_secret || !sig64_out) return UFSECP_ERR_NULL_ARG;
3296 ctx_clear_err(ctx);
3298 as.R_hat = point_from_compressed(pre_sig);
3299 if (as.R_hat.is_infinity()) {
3300 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3301 }
3302 Scalar shat;
3303 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3304 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3305 }
3306 as.s_hat = shat;
3307 as.needs_negation = (pre_sig[65] != 0);
3308 Scalar secret;
3309 if (!scalar_parse_strict_nonzero(adaptor_secret, secret)) {
3310 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "adaptor secret is zero or >= n");
3311 }
3312 auto sig = secp256k1::schnorr_adaptor_adapt(as, secret);
3313 secp256k1::detail::secure_erase(&secret, sizeof(secret));
3314 auto bytes = sig.to_bytes();
3315 std::memcpy(sig64_out, bytes.data(), 64);
3316 return UFSECP_OK;
3317}
3318
3320 ufsecp_ctx* ctx,
3321 const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN],
3322 const uint8_t sig64[64],
3323 uint8_t secret32_out[32]) {
3324 if (!ctx || !pre_sig || !sig64 || !secret32_out) return UFSECP_ERR_NULL_ARG;
3325 ctx_clear_err(ctx);
3327 as.R_hat = point_from_compressed(pre_sig);
3328 if (as.R_hat.is_infinity()) {
3329 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3330 }
3331 Scalar shat;
3332 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3333 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3334 }
3335 as.s_hat = shat;
3336 as.needs_negation = (pre_sig[65] != 0);
3339 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid schnorr signature");
3340 }
3341 auto [secret, ok] = secp256k1::schnorr_adaptor_extract(as, sig);
3342 if (!ok) {
3343 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "adaptor extract failed");
3344 }
3345 scalar_to_bytes(secret, secret32_out);
3346 secp256k1::detail::secure_erase(&secret, sizeof(secret));
3347 return UFSECP_OK;
3348}
3349
3351 ufsecp_ctx* ctx,
3352 const uint8_t privkey[32],
3353 const uint8_t msg32[32],
3354 const uint8_t adaptor_point33[33],
3355 uint8_t pre_sig_out[UFSECP_ECDSA_ADAPTOR_SIG_LEN]) {
3356 if (!ctx || !privkey || !msg32 || !adaptor_point33 || !pre_sig_out) {
3357 return UFSECP_ERR_NULL_ARG;
3358 }
3359 ctx_clear_err(ctx);
3360 Scalar sk;
3361 if (!scalar_parse_strict_nonzero(privkey, sk)) {
3362 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
3363 }
3364 std::array<uint8_t, 32> msg_arr;
3365 std::memcpy(msg_arr.data(), msg32, 32);
3366 auto ap = point_from_compressed(adaptor_point33);
3367 if (ap.is_infinity()) {
3368 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid adaptor point");
3369 }
3370 auto pre = secp256k1::ecdsa_adaptor_sign(sk, msg_arr, ap);
3371 secp256k1::detail::secure_erase(&sk, sizeof(sk));
3372 auto rhat = pre.R_hat.to_compressed();
3373 auto shat = pre.s_hat.to_bytes();
3374 auto r_bytes = pre.r.to_bytes();
3375 std::memcpy(pre_sig_out, rhat.data(), 33);
3376 std::memcpy(pre_sig_out + 33, shat.data(), 32);
3377 std::memcpy(pre_sig_out + 65, r_bytes.data(), 32);
3378 /* zero-pad remainder */
3379 std::memset(pre_sig_out + 97, 0, UFSECP_ECDSA_ADAPTOR_SIG_LEN - 97);
3380 return UFSECP_OK;
3381}
3382
3384 ufsecp_ctx* ctx,
3385 const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN],
3386 const uint8_t pubkey33[33],
3387 const uint8_t msg32[32],
3388 const uint8_t adaptor_point33[33]) {
3389 if (!ctx || !pre_sig || !pubkey33 || !msg32 || !adaptor_point33) {
3390 return UFSECP_ERR_NULL_ARG;
3391 }
3392 ctx_clear_err(ctx);
3394 as.R_hat = point_from_compressed(pre_sig);
3395 if (as.R_hat.is_infinity()) {
3396 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3397 }
3398 Scalar shat;
3399 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3400 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3401 }
3402 as.s_hat = shat;
3403 if (!scalar_parse_strict(pre_sig + 65, as.r)) {
3404 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig r");
3405 }
3406 auto pk = point_from_compressed(pubkey33);
3407 if (pk.is_infinity()) {
3408 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
3409 }
3410 std::array<uint8_t, 32> msg_arr;
3411 std::memcpy(msg_arr.data(), msg32, 32);
3412 auto ap = point_from_compressed(adaptor_point33);
3413 if (ap.is_infinity()) {
3414 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid adaptor point");
3415 }
3416 if (!secp256k1::ecdsa_adaptor_verify(as, pk, msg_arr, ap)) {
3417 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "ECDSA adaptor verify failed");
3418 }
3419 return UFSECP_OK;
3420}
3421
3423 ufsecp_ctx* ctx,
3424 const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN],
3425 const uint8_t adaptor_secret[32],
3426 uint8_t sig64_out[64]) {
3427 if (!ctx || !pre_sig || !adaptor_secret || !sig64_out) return UFSECP_ERR_NULL_ARG;
3428 ctx_clear_err(ctx);
3430 as.R_hat = point_from_compressed(pre_sig);
3431 if (as.R_hat.is_infinity()) {
3432 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3433 }
3434 Scalar shat;
3435 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3436 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3437 }
3438 as.s_hat = shat;
3439 if (!scalar_parse_strict(pre_sig + 65, as.r)) {
3440 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig r");
3441 }
3442 Scalar secret;
3443 if (!scalar_parse_strict_nonzero(adaptor_secret, secret)) {
3444 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "adaptor secret is zero or >= n");
3445 }
3446 auto sig = secp256k1::ecdsa_adaptor_adapt(as, secret);
3447 secp256k1::detail::secure_erase(&secret, sizeof(secret));
3448 auto compact = sig.to_compact();
3449 std::memcpy(sig64_out, compact.data(), 64);
3450 return UFSECP_OK;
3451}
3452
3454 ufsecp_ctx* ctx,
3455 const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN],
3456 const uint8_t sig64[64],
3457 uint8_t secret32_out[32]) {
3458 if (!ctx || !pre_sig || !sig64 || !secret32_out) return UFSECP_ERR_NULL_ARG;
3459 ctx_clear_err(ctx);
3461 as.R_hat = point_from_compressed(pre_sig);
3462 if (as.R_hat.is_infinity()) {
3463 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor R_hat");
3464 }
3465 Scalar shat;
3466 if (!scalar_parse_strict(pre_sig + 33, shat)) {
3467 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig scalar");
3468 }
3469 as.s_hat = shat;
3470 if (!scalar_parse_strict(pre_sig + 65, as.r)) {
3471 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid adaptor sig r");
3472 }
3473 std::array<uint8_t, 64> compact;
3474 std::memcpy(compact.data(), sig64, 64);
3476 if (!secp256k1::ECDSASignature::parse_compact_strict(compact, ecdsasig)) {
3477 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid ECDSA sig");
3478 }
3479 auto [secret, ok] = secp256k1::ecdsa_adaptor_extract(as, ecdsasig);
3480 if (!ok) {
3481 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "ECDSA adaptor extract failed");
3482 }
3483 scalar_to_bytes(secret, secret32_out);
3484 secp256k1::detail::secure_erase(&secret, sizeof(secret));
3485 return UFSECP_OK;
3486}
3487
3488/* ===========================================================================
3489 * Pedersen commitments
3490 * =========================================================================== */
3491
3493 const uint8_t value[32],
3494 const uint8_t blinding[32],
3495 uint8_t commitment33_out[33]) {
3496 if (!ctx || !value || !blinding || !commitment33_out) return UFSECP_ERR_NULL_ARG;
3497 ctx_clear_err(ctx);
3498 Scalar v, b;
3499 if (!scalar_parse_strict(value, v)) {
3500 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "value >= n");
3501 }
3502 if (!scalar_parse_strict(blinding, b)) {
3503 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blinding >= n");
3504 }
3505 auto c = secp256k1::pedersen_commit(v, b);
3506 auto comp = c.point.to_compressed();
3507 std::memcpy(commitment33_out, comp.data(), 33);
3508 return UFSECP_OK;
3509}
3510
3512 const uint8_t commitment33[33],
3513 const uint8_t value[32],
3514 const uint8_t blinding[32]) {
3515 if (!ctx || !commitment33 || !value || !blinding) return UFSECP_ERR_NULL_ARG;
3516 ctx_clear_err(ctx);
3517 Scalar v, b;
3518 if (!scalar_parse_strict(value, v)) {
3519 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "value >= n");
3520 }
3521 if (!scalar_parse_strict(blinding, b)) {
3522 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blinding >= n");
3523 }
3524 auto commit_pt = point_from_compressed(commitment33);
3525 if (commit_pt.is_infinity()) {
3526 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment point");
3527 }
3529 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "Pedersen verify failed");
3530 }
3531 return UFSECP_OK;
3532}
3533
3535 const uint8_t* pos, size_t n_pos,
3536 const uint8_t* neg, size_t n_neg) {
3537 if (!ctx || (!pos && n_pos > 0) || (!neg && n_neg > 0)) return UFSECP_ERR_NULL_ARG;
3538 ctx_clear_err(ctx);
3539 if (n_pos > kMaxBatchN || n_neg > kMaxBatchN)
3540 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "commitment count too large");
3541 std::size_t pos_bytes = 0, neg_bytes = 0;
3542 if (!checked_mul_size(n_pos, std::size_t{33}, pos_bytes)
3543 || !checked_mul_size(n_neg, std::size_t{33}, neg_bytes))
3544 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "commitment array size overflow");
3545 try {
3546 std::vector<secp256k1::PedersenCommitment> pcs(n_pos), ncs(n_neg);
3547 for (size_t i = 0; i < n_pos; ++i) {
3548 auto p = point_from_compressed(pos + i * 33);
3549 if (p.is_infinity()) {
3550 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid positive commitment");
3551 }
3553 }
3554 for (size_t i = 0; i < n_neg; ++i) {
3555 auto p = point_from_compressed(neg + i * 33);
3556 if (p.is_infinity()) {
3557 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid negative commitment");
3558 }
3560 }
3561 if (!secp256k1::pedersen_verify_sum(pcs.data(), n_pos, ncs.data(), n_neg)) {
3562 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "Pedersen sum verify failed");
3563 }
3564 return UFSECP_OK;
3565 } UFSECP_CATCH_RETURN(ctx)
3566}
3567
3569 const uint8_t* blinds_in, size_t n_in,
3570 const uint8_t* blinds_out, size_t n_out,
3571 uint8_t sum32_out[32]) {
3572 if (!ctx || (!blinds_in && n_in > 0) || (!blinds_out && n_out > 0) || !sum32_out) {
3573 return UFSECP_ERR_NULL_ARG;
3574 }
3575 ctx_clear_err(ctx);
3576 if (n_in > kMaxBatchN || n_out > kMaxBatchN)
3577 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blind count too large");
3578 std::size_t in_bytes = 0, out_bytes = 0;
3579 if (!checked_mul_size(n_in, std::size_t{32}, in_bytes)
3580 || !checked_mul_size(n_out, std::size_t{32}, out_bytes))
3581 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blind array size overflow");
3582 try {
3583 std::vector<Scalar> ins(n_in), outs(n_out);
3584 for (size_t i = 0; i < n_in; ++i) {
3585 if (!scalar_parse_strict(blinds_in + i * 32, ins[i])) {
3586 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid input blind");
3587 }
3588 }
3589 for (size_t i = 0; i < n_out; ++i) {
3590 if (!scalar_parse_strict(blinds_out + i * 32, outs[i])) {
3591 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid output blind");
3592 }
3593 }
3594 auto sum = secp256k1::pedersen_blind_sum(ins.data(), n_in, outs.data(), n_out);
3595 scalar_to_bytes(sum, sum32_out);
3596 return UFSECP_OK;
3597 } UFSECP_CATCH_RETURN(ctx)
3598}
3599
3601 const uint8_t value[32],
3602 const uint8_t blinding[32],
3603 const uint8_t switch_blind[32],
3604 uint8_t commitment33_out[33]) {
3605 if (!ctx || !value || !blinding || !switch_blind || !commitment33_out) {
3606 return UFSECP_ERR_NULL_ARG;
3607 }
3608 ctx_clear_err(ctx);
3609 Scalar v, b, sb;
3610 if (!scalar_parse_strict(value, v)) {
3611 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "value >= n");
3612 }
3613 if (!scalar_parse_strict(blinding, b)) {
3614 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blinding >= n");
3615 }
3616 if (!scalar_parse_strict(switch_blind, sb)) {
3617 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "switch_blind >= n");
3618 }
3619 auto c = secp256k1::pedersen_switch_commit(v, b, sb);
3620 auto comp = c.point.to_compressed();
3621 std::memcpy(commitment33_out, comp.data(), 33);
3622 return UFSECP_OK;
3623}
3624
3625/* ===========================================================================
3626 * Zero-knowledge proofs
3627 * =========================================================================== */
3628
3630 ufsecp_ctx* ctx,
3631 const uint8_t secret[32],
3632 const uint8_t pubkey33[33],
3633 const uint8_t msg32[32],
3634 const uint8_t aux_rand[32],
3635 uint8_t proof_out[UFSECP_ZK_KNOWLEDGE_PROOF_LEN]) {
3636 if (!ctx || !secret || !pubkey33 || !msg32 || !aux_rand || !proof_out) {
3637 return UFSECP_ERR_NULL_ARG;
3638 }
3639 ctx_clear_err(ctx);
3640 Scalar s;
3641 if (!scalar_parse_strict_nonzero(secret, s)) {
3642 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "secret is zero or >= n");
3643 }
3644 auto pk = point_from_compressed(pubkey33);
3645 if (pk.is_infinity()) {
3646 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
3647 }
3648 std::array<uint8_t, 32> msg_arr, aux_arr;
3649 std::memcpy(msg_arr.data(), msg32, 32);
3650 std::memcpy(aux_arr.data(), aux_rand, 32);
3651 auto proof = secp256k1::zk::knowledge_prove(s, pk, msg_arr, aux_arr);
3652 secp256k1::detail::secure_erase(&s, sizeof(s));
3653 auto ser = proof.serialize();
3654 std::memcpy(proof_out, ser.data(), UFSECP_ZK_KNOWLEDGE_PROOF_LEN);
3655 return UFSECP_OK;
3656}
3657
3659 ufsecp_ctx* ctx,
3660 const uint8_t proof[UFSECP_ZK_KNOWLEDGE_PROOF_LEN],
3661 const uint8_t pubkey33[33],
3662 const uint8_t msg32[32]) {
3663 if (!ctx || !proof || !pubkey33 || !msg32) return UFSECP_ERR_NULL_ARG;
3664 ctx_clear_err(ctx);
3665 auto pk = point_from_compressed(pubkey33);
3666 if (pk.is_infinity()) {
3667 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
3668 }
3671 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid knowledge proof");
3672 }
3673 std::array<uint8_t, 32> msg_arr;
3674 std::memcpy(msg_arr.data(), msg32, 32);
3675 if (!secp256k1::zk::knowledge_verify(kp, pk, msg_arr)) {
3676 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "knowledge proof failed");
3677 }
3678 return UFSECP_OK;
3679}
3680
3682 ufsecp_ctx* ctx,
3683 const uint8_t secret[32],
3684 const uint8_t G33[33], const uint8_t H33[33],
3685 const uint8_t P33[33], const uint8_t Q33[33],
3686 const uint8_t aux_rand[32],
3687 uint8_t proof_out[UFSECP_ZK_DLEQ_PROOF_LEN]) {
3688 if (!ctx || !secret || !G33 || !H33 || !P33 || !Q33 || !aux_rand || !proof_out) {
3689 return UFSECP_ERR_NULL_ARG;
3690 }
3691 ctx_clear_err(ctx);
3692 Scalar s;
3693 if (!scalar_parse_strict_nonzero(secret, s)) {
3694 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "secret is zero or >= n");
3695 }
3696 auto G = point_from_compressed(G33);
3697 auto H = point_from_compressed(H33);
3698 auto P = point_from_compressed(P33);
3699 auto Q = point_from_compressed(Q33);
3700 if (G.is_infinity() || H.is_infinity() || P.is_infinity() || Q.is_infinity()) {
3701 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid DLEQ point");
3702 }
3703 std::array<uint8_t, 32> aux_arr;
3704 std::memcpy(aux_arr.data(), aux_rand, 32);
3705 auto proof = secp256k1::zk::dleq_prove(s, G, H, P, Q, aux_arr);
3706 secp256k1::detail::secure_erase(&s, sizeof(s));
3707 auto ser = proof.serialize();
3708 std::memcpy(proof_out, ser.data(), UFSECP_ZK_DLEQ_PROOF_LEN);
3709 return UFSECP_OK;
3710}
3711
3713 ufsecp_ctx* ctx,
3714 const uint8_t proof[UFSECP_ZK_DLEQ_PROOF_LEN],
3715 const uint8_t G33[33], const uint8_t H33[33],
3716 const uint8_t P33[33], const uint8_t Q33[33]) {
3717 if (!ctx || !proof || !G33 || !H33 || !P33 || !Q33) return UFSECP_ERR_NULL_ARG;
3718 ctx_clear_err(ctx);
3719 auto G = point_from_compressed(G33);
3720 auto H = point_from_compressed(H33);
3721 auto P = point_from_compressed(P33);
3722 auto Q = point_from_compressed(Q33);
3723 if (G.is_infinity() || H.is_infinity() || P.is_infinity() || Q.is_infinity()) {
3724 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid DLEQ point");
3725 }
3727 if (!secp256k1::zk::DLEQProof::deserialize(proof, dp)) {
3728 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid DLEQ proof");
3729 }
3730 if (!secp256k1::zk::dleq_verify(dp, G, H, P, Q)) {
3731 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "DLEQ proof failed");
3732 }
3733 return UFSECP_OK;
3734}
3735
3737 ufsecp_ctx* ctx,
3738 uint64_t value,
3739 const uint8_t blinding[32],
3740 const uint8_t commitment33[33],
3741 const uint8_t aux_rand[32],
3742 uint8_t* proof_out, size_t* proof_len) {
3743 if (!ctx || !blinding || !commitment33 || !aux_rand || !proof_out || !proof_len) {
3744 return UFSECP_ERR_NULL_ARG;
3745 }
3746 ctx_clear_err(ctx);
3747 Scalar b;
3748 if (!scalar_parse_strict(blinding, b)) {
3749 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "blinding >= n");
3750 }
3751 auto commit_pt = point_from_compressed(commitment33);
3752 if (commit_pt.is_infinity()) {
3753 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment point");
3754 }
3755 auto commit = secp256k1::PedersenCommitment{commit_pt};
3756 std::array<uint8_t, 32> aux_arr;
3757 std::memcpy(aux_arr.data(), aux_rand, 32);
3758 auto rp = secp256k1::zk::range_prove(value, b, commit, aux_arr);
3759 /* Serialize range proof: A(33)+S(33)+T1(33)+T2(33)+tau_x(32)+mu(32)+t_hat(32)+L[6]*33+R[6]*33+a(32)+b(32) */
3760 const size_t needed = 33*4 + 32*3 + 6*33 + 6*33 + 32*2;
3761 if (*proof_len < needed) {
3762 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "range proof buffer too small");
3763 }
3764 size_t off = 0;
3765 auto write_point = [&](const Point& p) {
3766 auto c = p.to_compressed();
3767 std::memcpy(proof_out + off, c.data(), 33);
3768 off += 33;
3769 };
3770 auto write_scalar = [&](const Scalar& s) {
3771 scalar_to_bytes(s, proof_out + off);
3772 off += 32;
3773 };
3774 write_point(rp.A); write_point(rp.S);
3775 write_point(rp.T1); write_point(rp.T2);
3776 write_scalar(rp.tau_x); write_scalar(rp.mu); write_scalar(rp.t_hat);
3777 for (int i = 0; i < 6; ++i) write_point(rp.L[i]);
3778 for (int i = 0; i < 6; ++i) write_point(rp.R[i]);
3779 write_scalar(rp.a); write_scalar(rp.b);
3780 *proof_len = off;
3781 return UFSECP_OK;
3782}
3783
3785 ufsecp_ctx* ctx,
3786 const uint8_t commitment33[33],
3787 const uint8_t* proof, size_t proof_len) {
3788 if (!ctx || !commitment33 || !proof) return UFSECP_ERR_NULL_ARG;
3789 ctx_clear_err(ctx);
3790 /* Deserialize range proof */
3791 const size_t expected = 33*4 + 32*3 + 6*33 + 6*33 + 32*2;
3792 if (proof_len < expected) {
3793 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "range proof too short");
3794 }
3795 if (proof_len != expected) {
3796 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "range proof length mismatch");
3797 }
3799 size_t off = 0;
3800 bool point_ok = true;
3801 auto read_point = [&]() -> Point {
3802 auto p = point_from_compressed(proof + off);
3803 if (p.is_infinity()) point_ok = false;
3804 off += 33;
3805 return p;
3806 };
3807 bool scalar_ok = true;
3808 auto read_scalar = [&]() -> Scalar {
3809 Scalar s;
3810 if (!scalar_parse_strict(proof + off, s)) {
3811 scalar_ok = false;
3812 }
3813 off += 32;
3814 return s;
3815 };
3816 rp.A = read_point(); rp.S = read_point();
3817 rp.T1 = read_point(); rp.T2 = read_point();
3818 rp.tau_x = read_scalar(); rp.mu = read_scalar(); rp.t_hat = read_scalar();
3819 for (int i = 0; i < 6; ++i) rp.L[i] = read_point();
3820 for (int i = 0; i < 6; ++i) rp.R[i] = read_point();
3821 rp.a = read_scalar(); rp.b = read_scalar();
3822 if (!point_ok) {
3823 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid point in range proof");
3824 }
3825 if (!scalar_ok) {
3826 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid scalar in range proof");
3827 }
3828 auto commit_pt = point_from_compressed(commitment33);
3829 if (commit_pt.is_infinity()) {
3830 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid commitment point");
3831 }
3832 auto commit = secp256k1::PedersenCommitment{commit_pt};
3833 if (!secp256k1::zk::range_verify(commit, rp)) {
3834 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "range proof failed");
3835 }
3836 return UFSECP_OK;
3837}
3838
3839/* ===========================================================================
3840 * Multi-coin wallet infrastructure
3841 * =========================================================================== */
3842
3843static const secp256k1::coins::CoinParams* find_coin(uint32_t coin_type) {
3844 return secp256k1::coins::find_by_coin_type(coin_type);
3845}
3846
3848 const uint8_t pubkey33[33],
3849 uint32_t coin_type, int testnet,
3850 char* addr_out, size_t* addr_len) {
3851 if (!ctx || !pubkey33 || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
3852 ctx_clear_err(ctx);
3853 auto coin = find_coin(coin_type);
3854 if (!coin) {
3855 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unknown coin type");
3856 }
3857 auto pk = point_from_compressed(pubkey33);
3858 if (pk.is_infinity()) {
3859 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
3860 }
3861 try {
3862 auto addr = secp256k1::coins::coin_address(pk, *coin, testnet != 0);
3863 if (addr.empty()) {
3864 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "address generation failed");
3865 }
3866 if (*addr_len < addr.size() + 1) {
3867 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "address buffer too small");
3868 }
3869 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
3870 *addr_len = addr.size();
3871 return UFSECP_OK;
3872 } UFSECP_CATCH_RETURN(ctx)
3873}
3874
3876 ufsecp_ctx* ctx,
3877 const uint8_t* seed, size_t seed_len,
3878 uint32_t coin_type, uint32_t account, int change, uint32_t index,
3879 int testnet,
3880 uint8_t* privkey32_out,
3881 uint8_t* pubkey33_out,
3882 char* addr_out, size_t* addr_len) {
3883 if (!ctx || !seed) return UFSECP_ERR_NULL_ARG;
3884 ctx_clear_err(ctx);
3885 if ((addr_out == nullptr) != (addr_len == nullptr)) {
3886 return ctx_set_err(ctx, UFSECP_ERR_NULL_ARG,
3887 "addr_out and addr_len must both be null or both be non-null");
3888 }
3889 if (seed_len < 16 || seed_len > 64) {
3890 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "seed must be 16-64 bytes");
3891 }
3892 auto coin = find_coin(coin_type);
3893 if (!coin) {
3894 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unknown coin type");
3895 }
3896 try {
3897 /* BIP-32 master */
3898 auto bip32_result = secp256k1::bip32_master_key(seed, seed_len);
3899 if (!bip32_result.second) {
3900 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "BIP-32 master key failed");
3901 }
3902 auto master = bip32_result.first;
3903 const auto cleanup_keys = [&]() {
3904 secp256k1::detail::secure_erase(master.key.data(), master.key.size());
3905 secp256k1::detail::secure_erase(master.chain_code.data(), master.chain_code.size());
3906 };
3907 /* Derive coin key */
3908 auto derived = secp256k1::coins::coin_derive_key(
3909 master, *coin, account, change != 0, index);
3910 auto key = derived.first;
3911 bool const d_ok = derived.second;
3912 if (!d_ok) {
3913 cleanup_keys();
3914 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "coin key derivation failed");
3915 }
3916 const auto cleanup_derived_key = [&]() {
3917 secp256k1::detail::secure_erase(key.key.data(), key.key.size());
3918 secp256k1::detail::secure_erase(key.chain_code.data(), key.chain_code.size());
3919 };
3920 if (privkey32_out) {
3921 auto sk = key.private_key();
3922 scalar_to_bytes(sk, privkey32_out);
3923 secp256k1::detail::secure_erase(&sk, sizeof(sk));
3924 }
3925 auto pk = key.public_key();
3926 cleanup_keys();
3927 cleanup_derived_key();
3928 if (pubkey33_out) {
3929 point_to_compressed(pk, pubkey33_out);
3930 }
3931 if (addr_out && addr_len) {
3932 auto addr = secp256k1::coins::coin_address(pk, *coin, testnet != 0);
3933 if (*addr_len < addr.size() + 1) {
3934 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "address buffer too small");
3935 }
3936 std::memcpy(addr_out, addr.c_str(), addr.size() + 1);
3937 *addr_len = addr.size();
3938 }
3939 return UFSECP_OK;
3940 } UFSECP_CATCH_RETURN(ctx)
3941}
3942
3944 const uint8_t privkey[32],
3945 uint32_t coin_type, int testnet,
3946 char* wif_out, size_t* wif_len) {
3947 if (!ctx || !privkey || !wif_out || !wif_len) return UFSECP_ERR_NULL_ARG;
3948 ctx_clear_err(ctx);
3949 auto coin = find_coin(coin_type);
3950 if (!coin) {
3951 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unknown coin type");
3952 }
3953 Scalar sk;
3954 if (!scalar_parse_strict_nonzero(privkey, sk)) {
3955 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
3956 }
3957 try {
3958 auto wif = secp256k1::coins::coin_wif_encode(sk, *coin, true, testnet != 0);
3959 secp256k1::detail::secure_erase(&sk, sizeof(sk));
3960 if (wif.empty()) {
3961 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "WIF encode failed");
3962 }
3963 if (*wif_len < wif.size() + 1) {
3964 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "WIF buffer too small");
3965 }
3966 std::memcpy(wif_out, wif.c_str(), wif.size() + 1);
3967 *wif_len = wif.size();
3968 return UFSECP_OK;
3969 } UFSECP_CATCH_RETURN(ctx)
3970}
3971
3973 const uint8_t* msg, size_t msg_len,
3974 const uint8_t privkey[32],
3975 char* base64_out, size_t* base64_len) {
3976 if (!ctx || !msg || !privkey || !base64_out || !base64_len) return UFSECP_ERR_NULL_ARG;
3977 ctx_clear_err(ctx);
3978 Scalar sk;
3979 if (!scalar_parse_strict_nonzero(privkey, sk)) {
3980 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
3981 }
3982 try {
3983 auto rsig = secp256k1::coins::bitcoin_sign_message(msg, msg_len, sk);
3984 secp256k1::detail::secure_erase(&sk, sizeof(sk));
3986 if (*base64_len < b64.size() + 1) {
3987 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "base64 buffer too small");
3988 }
3989 std::memcpy(base64_out, b64.c_str(), b64.size() + 1);
3990 *base64_len = b64.size();
3991 return UFSECP_OK;
3992 } UFSECP_CATCH_RETURN(ctx)
3993}
3994
3996 const uint8_t* msg, size_t msg_len,
3997 const uint8_t pubkey33[33],
3998 const char* base64_sig) {
3999 if (!ctx || !msg || !pubkey33 || !base64_sig) return UFSECP_ERR_NULL_ARG;
4000 ctx_clear_err(ctx);
4001 auto pk = point_from_compressed(pubkey33);
4002 if (pk.is_infinity()) {
4003 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
4004 }
4005 try {
4006 auto dec = secp256k1::coins::bitcoin_sig_from_base64(std::string(base64_sig));
4007 if (!dec.valid) {
4008 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "invalid base64 signature");
4009 }
4010 if (!secp256k1::coins::bitcoin_verify_message(msg, msg_len, pk, dec.sig)) {
4011 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "BTC message verify failed");
4012 }
4013 return UFSECP_OK;
4014 } UFSECP_CATCH_RETURN(ctx)
4015}
4016
4017ufsecp_error_t ufsecp_btc_message_hash(const uint8_t* msg, size_t msg_len,
4018 uint8_t digest32_out[32]) {
4019 if (!msg || !digest32_out) return UFSECP_ERR_NULL_ARG;
4020 auto h = secp256k1::coins::bitcoin_message_hash(msg, msg_len);
4021 std::memcpy(digest32_out, h.data(), 32);
4022 return UFSECP_OK;
4023}
4024
4025/* ===========================================================================
4026 * BIP-352 Silent Payments
4027 * =========================================================================== */
4028
4030 ufsecp_ctx* ctx,
4031 const uint8_t scan_privkey[32],
4032 const uint8_t spend_privkey[32],
4033 uint8_t scan_pubkey33_out[33],
4034 uint8_t spend_pubkey33_out[33],
4035 char* addr_out, size_t* addr_len) {
4036 if (!ctx || !scan_privkey || !spend_privkey || !scan_pubkey33_out ||
4037 !spend_pubkey33_out || !addr_out || !addr_len) {
4038 return UFSECP_ERR_NULL_ARG;
4039 }
4040 ctx_clear_err(ctx);
4041
4042 Scalar scan_sk, spend_sk;
4043 auto cleanup = [&]() {
4044 secp256k1::detail::secure_erase(&scan_sk, sizeof(scan_sk));
4045 secp256k1::detail::secure_erase(&spend_sk, sizeof(spend_sk));
4046 };
4047 if (!scalar_parse_strict_nonzero(scan_privkey, scan_sk)) {
4048 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "scan privkey is zero or >= n");
4049 }
4050 if (!scalar_parse_strict_nonzero(spend_privkey, spend_sk)) {
4051 cleanup();
4052 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "spend privkey is zero or >= n");
4053 }
4054
4055 auto spa = secp256k1::silent_payment_address(scan_sk, spend_sk);
4056 auto scan_comp = spa.scan_pubkey.to_compressed();
4057 auto spend_comp = spa.spend_pubkey.to_compressed();
4058 std::memcpy(scan_pubkey33_out, scan_comp.data(), 33);
4059 std::memcpy(spend_pubkey33_out, spend_comp.data(), 33);
4060
4061 auto addr_str = spa.encode();
4062 if (addr_str.size() >= *addr_len) {
4063 cleanup();
4064 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "address buffer too small");
4065 }
4066 std::memcpy(addr_out, addr_str.c_str(), addr_str.size() + 1);
4067 *addr_len = addr_str.size();
4068
4069 cleanup();
4070 return UFSECP_OK;
4071}
4072
4074 ufsecp_ctx* ctx,
4075 const uint8_t* input_privkeys, size_t n_inputs,
4076 const uint8_t scan_pubkey33[33],
4077 const uint8_t spend_pubkey33[33],
4078 uint32_t k,
4079 uint8_t output_pubkey33_out[33],
4080 uint8_t* tweak32_out) {
4081 if (!ctx || !input_privkeys || n_inputs == 0 || !scan_pubkey33 ||
4082 !spend_pubkey33 || !output_pubkey33_out) {
4083 return UFSECP_ERR_NULL_ARG;
4084 }
4085 ctx_clear_err(ctx);
4086 if (n_inputs > kMaxBatchN) return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "input count too large");
4087 std::size_t total = 0;
4088 if (!checked_mul_size(n_inputs, std::size_t{32}, total))
4089 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "input privkey array size overflow");
4090 try {
4091
4092 // Parse input private keys
4093 std::vector<Scalar> privkeys;
4094 auto cleanup_privkeys = [&]() {
4095 for (auto& sk : privkeys) {
4096 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4097 }
4098 };
4099 privkeys.reserve(n_inputs);
4100 for (size_t i = 0; i < n_inputs; ++i) {
4101 Scalar sk;
4102 if (!scalar_parse_strict_nonzero(input_privkeys + i * 32, sk)) {
4103 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4104 cleanup_privkeys();
4105 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "input privkey is zero or >= n");
4106 }
4107 privkeys.push_back(sk);
4108 }
4109
4110 // Parse recipient address
4112 recipient.scan_pubkey = point_from_compressed(scan_pubkey33);
4113 recipient.spend_pubkey = point_from_compressed(spend_pubkey33);
4114 if (recipient.scan_pubkey.is_infinity() || recipient.spend_pubkey.is_infinity()) {
4115 cleanup_privkeys();
4116 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid recipient pubkey");
4117 }
4118
4119 auto [output_point, tweak] = secp256k1::silent_payment_create_output(privkeys, recipient, k);
4120 if (output_point.is_infinity()) {
4121 cleanup_privkeys();
4122 return ctx_set_err(ctx, UFSECP_ERR_ARITH, "output point is infinity");
4123 }
4124
4125 auto out_comp = output_point.to_compressed();
4126 std::memcpy(output_pubkey33_out, out_comp.data(), 33);
4127
4128 if (tweak32_out) {
4129 auto tweak_bytes = tweak.to_bytes();
4130 std::memcpy(tweak32_out, tweak_bytes.data(), 32);
4131 }
4132
4133 cleanup_privkeys();
4134 return UFSECP_OK;
4135 } UFSECP_CATCH_RETURN(ctx)
4136}
4137
4139 ufsecp_ctx* ctx,
4140 const uint8_t scan_privkey[32],
4141 const uint8_t spend_privkey[32],
4142 const uint8_t* input_pubkeys33, size_t n_input_pubkeys,
4143 const uint8_t* output_xonly32, size_t n_outputs,
4144 uint32_t* found_indices_out,
4145 uint8_t* found_privkeys_out,
4146 size_t* n_found) {
4147 if (!ctx || !scan_privkey || !spend_privkey || !input_pubkeys33 ||
4148 !output_xonly32 || !n_found) {
4149 return UFSECP_ERR_NULL_ARG;
4150 }
4151 if (n_input_pubkeys == 0 || n_outputs == 0) {
4152 return UFSECP_ERR_BAD_INPUT;
4153 }
4154 if (n_input_pubkeys > kMaxBatchN || n_outputs > kMaxBatchN)
4155 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "input/output count too large");
4156 std::size_t pk_bytes = 0, out_bytes = 0;
4157 if (!checked_mul_size(n_input_pubkeys, std::size_t{33}, pk_bytes)
4158 || !checked_mul_size(n_outputs, std::size_t{32}, out_bytes))
4159 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "silent payment array size overflow");
4160 ctx_clear_err(ctx);
4161 try {
4162
4163 Scalar scan_sk, spend_sk;
4164 auto cleanup = [&]() {
4165 secp256k1::detail::secure_erase(&scan_sk, sizeof(scan_sk));
4166 secp256k1::detail::secure_erase(&spend_sk, sizeof(spend_sk));
4167 };
4168 if (!scalar_parse_strict_nonzero(scan_privkey, scan_sk)) {
4169 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "scan privkey is zero or >= n");
4170 }
4171 if (!scalar_parse_strict_nonzero(spend_privkey, spend_sk)) {
4172 cleanup();
4173 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "spend privkey is zero or >= n");
4174 }
4175
4176 // Parse input pubkeys
4177 std::vector<Point> input_pks;
4178 input_pks.reserve(n_input_pubkeys);
4179 for (size_t i = 0; i < n_input_pubkeys; ++i) {
4180 auto pk = point_from_compressed(input_pubkeys33 + i * 33);
4181 if (pk.is_infinity()) {
4182 cleanup();
4183 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid input pubkey");
4184 }
4185 input_pks.push_back(pk);
4186 }
4187
4188 // Parse output x-only pubkeys
4189 std::vector<std::array<uint8_t, 32>> outputs;
4190 outputs.reserve(n_outputs);
4191 for (size_t i = 0; i < n_outputs; ++i) {
4192 std::array<uint8_t, 32> x;
4193 std::memcpy(x.data(), output_xonly32 + i * 32, 32);
4194 outputs.push_back(x);
4195 }
4196
4197 auto results = secp256k1::silent_payment_scan(scan_sk, spend_sk, input_pks, outputs);
4198
4199 size_t const capacity = *n_found;
4200 size_t const count = results.size() < capacity ? results.size() : capacity;
4201 *n_found = results.size();
4202
4203 for (size_t i = 0; i < count; ++i) {
4204 if (found_indices_out) found_indices_out[i] = results[i].first;
4205 if (found_privkeys_out) {
4206 auto key_bytes = results[i].second.to_bytes();
4207 std::memcpy(found_privkeys_out + i * 32, key_bytes.data(), 32);
4208 secp256k1::detail::secure_erase(key_bytes.data(), key_bytes.size());
4209 }
4210 }
4211
4212 // Erase result private keys from heap before vector destruction
4213 for (auto& r : results) {
4214 secp256k1::detail::secure_erase(&r.second, sizeof(r.second));
4215 }
4216
4217 cleanup();
4218 return UFSECP_OK;
4219 } UFSECP_CATCH_RETURN(ctx)
4220}
4221
4222/* ===========================================================================
4223 * ECIES (Elliptic Curve Integrated Encryption Scheme)
4224 * =========================================================================== */
4225
4227 ufsecp_ctx* ctx,
4228 const uint8_t recipient_pubkey33[33],
4229 const uint8_t* plaintext, size_t plaintext_len,
4230 uint8_t* envelope_out, size_t* envelope_len) {
4231 if (!ctx || !recipient_pubkey33 || !plaintext || !envelope_out || !envelope_len) {
4232 return UFSECP_ERR_NULL_ARG;
4233 }
4234 if (plaintext_len == 0) {
4235 return UFSECP_ERR_BAD_INPUT;
4236 }
4237 ctx_clear_err(ctx);
4238
4239 if (plaintext_len > SIZE_MAX - UFSECP_ECIES_OVERHEAD) {
4240 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "plaintext_len too large");
4241 }
4242 size_t const needed = plaintext_len + UFSECP_ECIES_OVERHEAD;
4243 if (*envelope_len < needed) {
4244 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "envelope buffer too small");
4245 }
4246
4247 auto pk = point_from_compressed(recipient_pubkey33);
4248 if (pk.is_infinity()) {
4249 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid recipient pubkey");
4250 }
4251 try {
4252 auto envelope = secp256k1::ecies_encrypt(pk, plaintext, plaintext_len);
4253 if (envelope.empty()) {
4254 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "ECIES encryption failed");
4255 }
4256
4257 std::memcpy(envelope_out, envelope.data(), envelope.size());
4258 *envelope_len = envelope.size();
4259 return UFSECP_OK;
4260 } UFSECP_CATCH_RETURN(ctx)
4261}
4262
4264 ufsecp_ctx* ctx,
4265 const uint8_t privkey[32],
4266 const uint8_t* envelope, size_t envelope_len,
4267 uint8_t* plaintext_out, size_t* plaintext_len) {
4268 if (!ctx || !privkey || !envelope || !plaintext_out || !plaintext_len) {
4269 return UFSECP_ERR_NULL_ARG;
4270 }
4271 if (envelope_len < 82) { // min: 33 + 16 + 1 + 32
4272 return UFSECP_ERR_BAD_INPUT;
4273 }
4274 ctx_clear_err(ctx);
4275
4276 size_t const expected_pt_len = envelope_len - UFSECP_ECIES_OVERHEAD;
4277 if (*plaintext_len < expected_pt_len) {
4278 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "plaintext buffer too small");
4279 }
4280
4281 Scalar sk;
4282 if (!scalar_parse_strict_nonzero(privkey, sk)) {
4283 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
4284 }
4285 try {
4286 auto pt = secp256k1::ecies_decrypt(sk, envelope, envelope_len);
4287 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4288
4289 if (pt.empty()) {
4290 return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "ECIES decryption failed (bad key or tampered)");
4291 }
4292
4293 std::memcpy(plaintext_out, pt.data(), pt.size());
4294 *plaintext_len = pt.size();
4295 return UFSECP_OK;
4296 } UFSECP_CATCH_RETURN(ctx)
4297}
4298
4299/* ===========================================================================
4300 * BIP-324: Version 2 P2P Encrypted Transport (conditional: SECP256K1_BIP324)
4301 * =========================================================================== */
4302
4303#if defined(SECP256K1_BIP324)
4304
4305struct ufsecp_bip324_session {
4306 secp256k1::Bip324Session* cpp_session;
4307};
4308
4309ufsecp_error_t ufsecp_bip324_create(
4310 ufsecp_ctx* ctx,
4311 int initiator,
4312 ufsecp_bip324_session** session_out,
4313 uint8_t ellswift64_out[64]) {
4314 if (!ctx || !session_out || !ellswift64_out) return UFSECP_ERR_NULL_ARG;
4315 ctx_clear_err(ctx);
4316 *session_out = nullptr;
4317 if (initiator != 0 && initiator != 1) {
4318 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "initiator must be 0 or 1");
4319 }
4320
4321 auto* sess = new (std::nothrow) ufsecp_bip324_session;
4322 if (!sess) return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "allocation failed");
4323 sess->cpp_session = nullptr;
4324
4325 sess->cpp_session = new (std::nothrow) secp256k1::Bip324Session(initiator == 1);
4326 if (!sess->cpp_session) {
4327 delete sess;
4328 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "allocation failed");
4329 }
4330
4331 auto& enc = sess->cpp_session->our_ellswift_encoding();
4332 std::memcpy(ellswift64_out, enc.data(), 64);
4333
4334 *session_out = sess;
4335 return UFSECP_OK;
4336}
4337
4338ufsecp_error_t ufsecp_bip324_handshake(
4339 ufsecp_bip324_session* session,
4340 const uint8_t peer_ellswift64[64],
4341 uint8_t session_id32_out[32]) {
4342 if (!session || !session->cpp_session || !peer_ellswift64) return UFSECP_ERR_NULL_ARG;
4343
4344 if (!session->cpp_session->complete_handshake(peer_ellswift64)) {
4345 return UFSECP_ERR_INTERNAL;
4346 }
4347
4348 if (session_id32_out) {
4349 auto& sid = session->cpp_session->session_id();
4350 std::memcpy(session_id32_out, sid.data(), 32);
4351 }
4352 return UFSECP_OK;
4353}
4354
4355ufsecp_error_t ufsecp_bip324_encrypt(
4356 ufsecp_bip324_session* session,
4357 const uint8_t* plaintext, size_t plaintext_len,
4358 uint8_t* out, size_t* out_len) {
4359 if (!session || !session->cpp_session || !out || !out_len) return UFSECP_ERR_NULL_ARG;
4360 if (!plaintext && plaintext_len > 0) return UFSECP_ERR_NULL_ARG;
4361 if (plaintext_len > SIZE_MAX - 19) return UFSECP_ERR_BAD_INPUT;
4362
4363 size_t const needed = plaintext_len + 19; // 3 (length) + payload + 16 (tag)
4364 if (*out_len < needed) return UFSECP_ERR_BUF_TOO_SMALL;
4365 try {
4366 auto enc = session->cpp_session->encrypt(plaintext, plaintext_len);
4367 if (enc.empty()) return UFSECP_ERR_INTERNAL;
4368
4369 std::memcpy(out, enc.data(), enc.size());
4370 *out_len = enc.size();
4371 return UFSECP_OK;
4372 } catch (...) { return UFSECP_ERR_INTERNAL; }
4373}
4374
4375ufsecp_error_t ufsecp_bip324_decrypt(
4376 ufsecp_bip324_session* session,
4377 const uint8_t* encrypted, size_t encrypted_len,
4378 uint8_t* plaintext_out, size_t* plaintext_len) {
4379 if (!session || !session->cpp_session || !encrypted || !plaintext_out || !plaintext_len)
4380 return UFSECP_ERR_NULL_ARG;
4381
4382 // encrypted = [3B header][payload][16B tag], minimum length 19
4383 if (encrypted_len < 19) return UFSECP_ERR_BUF_TOO_SMALL;
4384
4385 const uint8_t* header = encrypted;
4386 const uint8_t* payload_tag = encrypted + 3;
4387 const size_t payload_tag_len = encrypted_len - 3;
4388 try {
4389 std::vector<uint8_t> dec;
4390 if (!session->cpp_session->decrypt(header, payload_tag, payload_tag_len, dec)) {
4392 }
4393
4394 if (*plaintext_len < dec.size()) return UFSECP_ERR_BUF_TOO_SMALL;
4395 std::memcpy(plaintext_out, dec.data(), dec.size());
4396 *plaintext_len = dec.size();
4397 return UFSECP_OK;
4398 } catch (...) { return UFSECP_ERR_INTERNAL; }
4399}
4400
4401void ufsecp_bip324_destroy(ufsecp_bip324_session* session) {
4402 if (session) {
4403 if (session->cpp_session) {
4404 delete session->cpp_session;
4405 }
4406 delete session;
4407 }
4408}
4409
4410ufsecp_error_t ufsecp_aead_chacha20_poly1305_encrypt(
4411 const uint8_t key[32], const uint8_t nonce[12],
4412 const uint8_t* aad, size_t aad_len,
4413 const uint8_t* plaintext, size_t plaintext_len,
4414 uint8_t* out, uint8_t tag[16]) {
4415 if (!key || !nonce || !out || !tag) return UFSECP_ERR_NULL_ARG;
4416 if (!plaintext && plaintext_len > 0) return UFSECP_ERR_NULL_ARG;
4417 if (!aad && aad_len > 0) return UFSECP_ERR_NULL_ARG;
4418
4420 key, nonce, aad, aad_len, plaintext, plaintext_len, out, tag);
4421 return UFSECP_OK;
4422}
4423
4424ufsecp_error_t ufsecp_aead_chacha20_poly1305_decrypt(
4425 const uint8_t key[32], const uint8_t nonce[12],
4426 const uint8_t* aad, size_t aad_len,
4427 const uint8_t* ciphertext, size_t ciphertext_len,
4428 const uint8_t tag[16], uint8_t* out) {
4429 if (!key || !nonce || !tag || !out) return UFSECP_ERR_NULL_ARG;
4430 if (!ciphertext && ciphertext_len > 0) return UFSECP_ERR_NULL_ARG;
4431 if (!aad && aad_len > 0) return UFSECP_ERR_NULL_ARG;
4432
4434 key, nonce, aad, aad_len, ciphertext, ciphertext_len, tag, out);
4435 return ok ? UFSECP_OK : UFSECP_ERR_VERIFY_FAIL;
4436}
4437
4438ufsecp_error_t ufsecp_ellswift_create(
4439 ufsecp_ctx* ctx,
4440 const uint8_t privkey[32],
4441 uint8_t encoding64_out[64]) {
4442 if (!ctx || !privkey || !encoding64_out) return UFSECP_ERR_NULL_ARG;
4443 ctx_clear_err(ctx);
4444
4445 Scalar sk;
4446 if (!scalar_parse_strict_nonzero(privkey, sk)) {
4447 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
4448 }
4449
4450 auto enc = secp256k1::ellswift_create(sk);
4451 std::memcpy(encoding64_out, enc.data(), 64);
4452
4453 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4454 return UFSECP_OK;
4455}
4456
4457ufsecp_error_t ufsecp_ellswift_xdh(
4458 ufsecp_ctx* ctx,
4459 const uint8_t ell_a64[64],
4460 const uint8_t ell_b64[64],
4461 const uint8_t our_privkey[32],
4462 int initiating,
4463 uint8_t secret32_out[32]) {
4464 if (!ctx || !ell_a64 || !ell_b64 || !our_privkey || !secret32_out)
4465 return UFSECP_ERR_NULL_ARG;
4466 ctx_clear_err(ctx);
4467
4468 Scalar sk;
4469 if (!scalar_parse_strict_nonzero(our_privkey, sk)) {
4470 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
4471 }
4472
4473 auto secret = secp256k1::ellswift_xdh(ell_a64, ell_b64, sk, initiating != 0);
4474 std::memcpy(secret32_out, secret.data(), 32);
4475
4476 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4477 return UFSECP_OK;
4478}
4479
4480#endif /* SECP256K1_BIP324 */
4481
4482/* ===========================================================================
4483 * Ethereum (conditional: SECP256K1_BUILD_ETHEREUM)
4484 * =========================================================================== */
4485
4486#if defined(SECP256K1_BUILD_ETHEREUM)
4487
4488ufsecp_error_t ufsecp_keccak256(const uint8_t* data, size_t len,
4489 uint8_t digest32_out[32]) {
4490 if (!data && len > 0) return UFSECP_ERR_NULL_ARG;
4491 if (!digest32_out) return UFSECP_ERR_NULL_ARG;
4492
4493 auto hash = secp256k1::coins::keccak256(data, len);
4494 std::memcpy(digest32_out, hash.data(), 32);
4495 return UFSECP_OK;
4496}
4497
4498ufsecp_error_t ufsecp_eth_address(ufsecp_ctx* ctx,
4499 const uint8_t pubkey33[33],
4500 uint8_t addr20_out[20]) {
4501 if (!ctx || !pubkey33 || !addr20_out) return UFSECP_ERR_NULL_ARG;
4502 ctx_clear_err(ctx);
4503
4504 const Point pk = point_from_compressed(pubkey33);
4505 if (pk.is_infinity()) {
4506 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid compressed pubkey");
4507 }
4508
4510 std::memcpy(addr20_out, addr.data(), 20);
4511 return UFSECP_OK;
4512}
4513
4514ufsecp_error_t ufsecp_eth_address_checksummed(ufsecp_ctx* ctx,
4515 const uint8_t pubkey33[33],
4516 char* addr_out, size_t* addr_len) {
4517 if (!ctx || !pubkey33 || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
4518 ctx_clear_err(ctx);
4519
4520 if (*addr_len < 43) {
4521 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "need >= 43 bytes for ETH address");
4522 }
4523
4524 const Point pk = point_from_compressed(pubkey33);
4525 if (pk.is_infinity()) {
4526 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid compressed pubkey");
4527 }
4528
4529 try {
4530 const std::string addr_str = secp256k1::coins::ethereum_address(pk);
4531 std::memcpy(addr_out, addr_str.c_str(), addr_str.size());
4532 addr_out[addr_str.size()] = '\0';
4533 *addr_len = addr_str.size();
4534 return UFSECP_OK;
4535 } UFSECP_CATCH_RETURN(ctx)
4536}
4537
4538ufsecp_error_t ufsecp_eth_personal_hash(const uint8_t* msg, size_t msg_len,
4539 uint8_t digest32_out[32]) {
4540 if (!msg && msg_len > 0) return UFSECP_ERR_NULL_ARG;
4541 if (!digest32_out) return UFSECP_ERR_NULL_ARG;
4542
4543 auto hash = secp256k1::coins::eip191_hash(msg, msg_len);
4544 std::memcpy(digest32_out, hash.data(), 32);
4545 return UFSECP_OK;
4546}
4547
4548ufsecp_error_t ufsecp_eth_sign(ufsecp_ctx* ctx,
4549 const uint8_t msg32[32],
4550 const uint8_t privkey[32],
4551 uint8_t r_out[32],
4552 uint8_t s_out[32],
4553 uint64_t* v_out,
4554 uint64_t chain_id) {
4555 if (!ctx || !msg32 || !privkey || !r_out || !s_out || !v_out) {
4556 return UFSECP_ERR_NULL_ARG;
4557 }
4558 ctx_clear_err(ctx);
4559
4560 Scalar sk;
4561 if (!scalar_parse_strict_nonzero(privkey, sk)) {
4562 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
4563 }
4564
4565 std::array<uint8_t, 32> hash;
4566 std::memcpy(hash.data(), msg32, 32);
4567
4568 auto esig = secp256k1::coins::eth_sign_hash(hash, sk, chain_id);
4569 std::memcpy(r_out, esig.r.data(), 32);
4570 std::memcpy(s_out, esig.s.data(), 32);
4571 *v_out = esig.v;
4572
4573 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4574 return UFSECP_OK;
4575}
4576
4577ufsecp_error_t ufsecp_eth_ecrecover(ufsecp_ctx* ctx,
4578 const uint8_t msg32[32],
4579 const uint8_t r[32],
4580 const uint8_t s[32],
4581 uint64_t v,
4582 uint8_t addr20_out[20]) {
4583 if (!ctx || !msg32 || !r || !s || !addr20_out) return UFSECP_ERR_NULL_ARG;
4584 ctx_clear_err(ctx);
4585
4586 std::array<uint8_t, 32> hash, r_arr, s_arr;
4587 std::memcpy(hash.data(), msg32, 32);
4588 std::memcpy(r_arr.data(), r, 32);
4589 std::memcpy(s_arr.data(), s, 32);
4590
4591 auto [addr, ok] = secp256k1::coins::ecrecover(hash, r_arr, s_arr, v);
4592 if (!ok) {
4593 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "ecrecover failed");
4594 }
4595
4596 std::memcpy(addr20_out, addr.data(), 20);
4597 return UFSECP_OK;
4598}
4599
4600#endif /* SECP256K1_BUILD_ETHEREUM */
4601
4602/* ===========================================================================
4603 * BIP-85 — Deterministic Entropy from BIP-32 Keychains
4604 * =========================================================================== */
4605
4607 ufsecp_ctx* ctx,
4608 const ufsecp_bip32_key* master_xprv,
4609 const char* path,
4610 uint8_t* entropy_out, size_t entropy_len) {
4611 if (!ctx || !master_xprv || !path || !entropy_out) return UFSECP_ERR_NULL_ARG;
4612 if (entropy_len == 0 || entropy_len > 32) {
4613 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "entropy_len must be 1-32");
4614 }
4615 ctx_clear_err(ctx);
4616
4617 if (!master_xprv->is_private) {
4618 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "BIP-85 requires xprv (private key)");
4619 }
4620
4622 ufsecp_error_t const parse_rc = parse_bip32_key(ctx, master_xprv, ek);
4623 if (parse_rc != UFSECP_OK) return parse_rc;
4624
4625 try {
4626 // Derive at the given path (all components must be hardened per BIP-85)
4627 auto [derived, ok] = secp256k1::bip32_derive_path(ek, std::string(path));
4628 secp256k1::detail::secure_erase(ek.key.data(), ek.key.size());
4629 secp256k1::detail::secure_erase(ek.chain_code.data(), ek.chain_code.size());
4630 if (!ok) {
4631 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "BIP-85 path derivation failed");
4632 }
4633
4634 // Get the derived private key
4635 auto child_privkey = derived.private_key().to_bytes();
4636 secp256k1::detail::secure_erase(derived.key.data(), derived.key.size());
4637 secp256k1::detail::secure_erase(derived.chain_code.data(), derived.chain_code.size());
4638
4639 // HMAC-SHA512(key="bip-85", data=child_privkey)
4640 static const uint8_t BIP85_KEY[] = {'b','i','p','-','8','5'};
4641 auto hmac = secp256k1::hmac_sha512(BIP85_KEY, 6, child_privkey.data(), 32);
4642 secp256k1::detail::secure_erase(child_privkey.data(), child_privkey.size());
4643
4644 std::memcpy(entropy_out, hmac.data(), entropy_len);
4645 secp256k1::detail::secure_erase(hmac.data(), hmac.size());
4646 return UFSECP_OK;
4647 } UFSECP_CATCH_RETURN(ctx)
4648}
4649
4651 ufsecp_ctx* ctx,
4652 const ufsecp_bip32_key* master_xprv,
4653 uint32_t words, uint32_t language_index, uint32_t index,
4654 char* mnemonic_out, size_t* mnemonic_len) {
4655 if (!ctx || !master_xprv || !mnemonic_out || !mnemonic_len) return UFSECP_ERR_NULL_ARG;
4656 ctx_clear_err(ctx);
4657
4658 if (words != 12 && words != 18 && words != 24) {
4659 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "words must be 12, 18, or 24");
4660 }
4661 if (language_index != 0) {
4662 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "only language_index=0 (English) supported");
4663 }
4664
4665 // BIP-85 path for BIP-39: m/83696968'/39'/<language>'/<words>'/<index>'
4666 char path[128];
4667 std::snprintf(path, sizeof(path),
4668 "m/83696968'/39'/%u'/%u'/%u'",
4669 language_index, words, index);
4670
4671 // entropy_len = (words / 3) * 4
4672 size_t const entropy_len = (static_cast<size_t>(words) / 3) * 4;
4673 uint8_t entropy[32]{};
4674
4675 ufsecp_error_t rc = ufsecp_bip85_entropy(ctx, master_xprv, path, entropy, entropy_len);
4676 if (rc != UFSECP_OK) {
4677 secp256k1::detail::secure_erase(entropy, sizeof(entropy));
4678 return rc;
4679 }
4680
4681 try {
4682 auto [mnemonic, ok] = secp256k1::bip39_generate(entropy_len, entropy);
4683 secp256k1::detail::secure_erase(entropy, sizeof(entropy));
4684 if (!ok || mnemonic.empty()) {
4685 return ctx_set_err(ctx, UFSECP_ERR_INTERNAL, "BIP-85 BIP-39 mnemonic generation failed");
4686 }
4687 if (*mnemonic_len < mnemonic.size() + 1) {
4688 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "mnemonic buffer too small");
4689 }
4690 std::memcpy(mnemonic_out, mnemonic.c_str(), mnemonic.size() + 1);
4691 *mnemonic_len = mnemonic.size();
4692 return UFSECP_OK;
4693 } UFSECP_CATCH_RETURN(ctx)
4694}
4695
4696/* ===========================================================================
4697 * BIP-340 Variable-Length Schnorr
4698 * =========================================================================== */
4699
4701 ufsecp_ctx* ctx,
4702 const uint8_t privkey[32],
4703 const uint8_t* msg, size_t msg_len,
4704 const uint8_t* aux_rand32,
4705 uint8_t sig64_out[64]) {
4706 if (!ctx || !privkey || !sig64_out) return UFSECP_ERR_NULL_ARG;
4707 if (!msg && msg_len > 0) return UFSECP_ERR_NULL_ARG;
4708 if (!aux_rand32) return UFSECP_ERR_NULL_ARG;
4709 ctx_clear_err(ctx);
4710
4711 // Hash the message with BIP-340 tagged hash "BIP0340/msg"
4712 static const uint8_t tag_data[] = "BIP0340/msg";
4713 uint8_t msg_hash[32];
4714 {
4715 // tagged_hash("BIP0340/msg", msg) = SHA256(SHA256(tag)||SHA256(tag)||msg)
4716 auto tag_hash = secp256k1::SHA256::hash(tag_data, sizeof(tag_data) - 1);
4718 h.update(tag_hash.data(), 32);
4719 h.update(tag_hash.data(), 32);
4720 if (msg && msg_len > 0) h.update(msg, msg_len);
4721 auto digest = h.finalize();
4722 std::memcpy(msg_hash, digest.data(), 32);
4723 }
4724
4725 // Use the fixed 32-byte sign API
4726 uint8_t aux_arr[32];
4727 std::memcpy(aux_arr, aux_rand32, 32);
4728 return ufsecp_schnorr_sign(ctx, msg_hash, privkey, aux_arr, sig64_out);
4729}
4730
4732 ufsecp_ctx* ctx,
4733 const uint8_t pubkey_x[32],
4734 const uint8_t* msg, size_t msg_len,
4735 const uint8_t sig64[64]) {
4736 if (!ctx || !pubkey_x || !sig64) return UFSECP_ERR_NULL_ARG;
4737 if (!msg && msg_len > 0) return UFSECP_ERR_NULL_ARG;
4738 ctx_clear_err(ctx);
4739
4740 static const uint8_t tag_data[] = "BIP0340/msg";
4741 uint8_t msg_hash[32];
4742 {
4743 auto tag_hash = secp256k1::SHA256::hash(tag_data, sizeof(tag_data) - 1);
4745 h.update(tag_hash.data(), 32);
4746 h.update(tag_hash.data(), 32);
4747 if (msg && msg_len > 0) h.update(msg, msg_len);
4748 auto digest = h.finalize();
4749 std::memcpy(msg_hash, digest.data(), 32);
4750 }
4751
4752 return ufsecp_schnorr_verify(ctx, msg_hash, sig64, pubkey_x);
4753}
4754
4755/* ===========================================================================
4756 * BIP-322 — Generic Message Signing
4757 * =========================================================================== */
4758
4760 ufsecp_ctx* ctx,
4761 const uint8_t privkey[32],
4762 ufsecp_bip322_addr_type addr_type,
4763 const uint8_t* msg, size_t msg_len,
4764 uint8_t* sig_out, size_t* sig_len) {
4765 if (!ctx || !privkey || !sig_out || !sig_len) return UFSECP_ERR_NULL_ARG;
4766 if (!msg && msg_len > 0) return UFSECP_ERR_NULL_ARG;
4767 ctx_clear_err(ctx);
4768
4769 // BIP-322 message hash: tagged_hash("BIP0322-signed-message", msg)
4770 static const uint8_t bip322_tag[] = "BIP0322-signed-message";
4771 uint8_t msg_hash[32];
4772 {
4773 auto tag_hash = secp256k1::SHA256::hash(bip322_tag, sizeof(bip322_tag) - 1);
4775 h.update(tag_hash.data(), 32);
4776 h.update(tag_hash.data(), 32);
4777 if (msg && msg_len > 0) h.update(msg, msg_len);
4778 auto digest = h.finalize();
4779 std::memcpy(msg_hash, digest.data(), 32);
4780 }
4781
4782 Scalar sk;
4783 if (!scalar_parse_strict_nonzero(privkey, sk)) {
4784 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
4785 }
4786
4787 try {
4788 if (addr_type == UFSECP_BIP322_ADDR_P2TR) {
4789 // P2TR: Schnorr sign
4790 if (*sig_len < 64) {
4791 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "P2TR sig buffer too small (need 64)");
4792 }
4793 std::array<uint8_t, 32> msg_arr{}, aux_arr{};
4794 std::memcpy(msg_arr.data(), msg_hash, 32);
4796 auto sig = secp256k1::ct::schnorr_sign(kp, msg_arr, aux_arr);
4797 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4798 secp256k1::detail::secure_erase(&kp.d, sizeof(kp.d));
4799 auto bytes = sig.to_bytes();
4800 std::memcpy(sig_out, bytes.data(), 64);
4801 *sig_len = 64;
4802 } else {
4803 // P2PKH, P2WPKH, P2SH-P2WPKH: ECDSA sign
4804 if (*sig_len < 65) {
4805 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4806 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "ECDSA sig buffer too small (need 65)");
4807 }
4808 std::array<uint8_t, 32> msg_arr;
4809 std::memcpy(msg_arr.data(), msg_hash, 32);
4810 auto sig = secp256k1::ecdsa_sign(msg_arr, sk);
4811 secp256k1::detail::secure_erase(&sk, sizeof(sk));
4812 auto compact = sig.to_compact();
4813 std::memcpy(sig_out, compact.data(), 32);
4814 std::memcpy(sig_out + 32, compact.data() + 32, 32);
4815 sig_out[64] = 0x01; // SIGHASH_ALL type byte
4816 *sig_len = 65;
4817 }
4818 return UFSECP_OK;
4819 } UFSECP_CATCH_RETURN(ctx)
4820}
4821
4823 ufsecp_ctx* ctx,
4824 const uint8_t* pubkey, size_t pubkey_len,
4825 ufsecp_bip322_addr_type addr_type,
4826 const uint8_t* msg, size_t msg_len,
4827 const uint8_t* sig, size_t sig_len) {
4828 if (!ctx || !pubkey || !sig) return UFSECP_ERR_NULL_ARG;
4829 if (!msg && msg_len > 0) return UFSECP_ERR_NULL_ARG;
4830 ctx_clear_err(ctx);
4831
4832 static const uint8_t bip322_tag[] = "BIP0322-signed-message";
4833 uint8_t msg_hash[32];
4834 {
4835 auto tag_hash = secp256k1::SHA256::hash(bip322_tag, sizeof(bip322_tag) - 1);
4837 h.update(tag_hash.data(), 32);
4838 h.update(tag_hash.data(), 32);
4839 if (msg && msg_len > 0) h.update(msg, msg_len);
4840 auto digest = h.finalize();
4841 std::memcpy(msg_hash, digest.data(), 32);
4842 }
4843
4844 try {
4845 if (addr_type == UFSECP_BIP322_ADDR_P2TR) {
4846 if (pubkey_len != 32) {
4847 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "P2TR requires 32-byte x-only pubkey");
4848 }
4849 if (sig_len < 64) {
4850 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "P2TR sig must be at least 64 bytes");
4851 }
4852 return ufsecp_schnorr_verify(ctx, msg_hash, sig, pubkey);
4853 } else {
4854 if (pubkey_len != 33) {
4855 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "requires 33-byte compressed pubkey");
4856 }
4857 if (sig_len < 64) {
4858 return ctx_set_err(ctx, UFSECP_ERR_BAD_SIG, "sig must be at least 64 bytes");
4859 }
4860 auto pk = point_from_compressed(pubkey);
4861 if (pk.is_infinity()) {
4862 return ctx_set_err(ctx, UFSECP_ERR_BAD_PUBKEY, "invalid pubkey");
4863 }
4864 std::array<uint8_t, 32> msg_arr;
4865 std::memcpy(msg_arr.data(), msg_hash, 32);
4866 // Build compact sig from first 64 bytes
4867 std::array<uint8_t, 64> compact64;
4868 std::memcpy(compact64.data(), sig, 64);
4869 auto esig = secp256k1::ECDSASignature::from_compact(compact64);
4870 bool ok = secp256k1::ecdsa_verify(msg_arr, pk, esig);
4871 if (!ok) return ctx_set_err(ctx, UFSECP_ERR_VERIFY_FAIL, "BIP-322 ECDSA verify failed");
4872 return UFSECP_OK;
4873 }
4874 } UFSECP_CATCH_RETURN(ctx)
4875}
4876
4877/* ===========================================================================
4878 * BIP-157/158 — Compact Block Filters (Golomb-Coded Set)
4879 * =========================================================================== */
4880
4881// SipHash-2-4 implementation (for GCS)
4882namespace {
4883
4884/* Portable 64×64→high-64 multiply.
4885 * Provided by secp256k1::detail::mulhi64 (arith64.hpp).
4886 * Alias it into the anonymous namespace used by the GCS code below. */
4888
4889static inline uint64_t siphash_rotl64(uint64_t x, int b) {
4890 return (x << b) | (x >> (64 - b));
4891}
4892
4893static uint64_t siphash24(const uint8_t key[16], const uint8_t* data, size_t len) {
4894 uint64_t k0, k1;
4895 std::memcpy(&k0, key, 8);
4896 std::memcpy(&k1, key + 8, 8);
4897
4898 uint64_t v0 = k0 ^ 0x736f6d6570736575ULL;
4899 uint64_t v1 = k1 ^ 0x646f72616e646f6dULL;
4900 uint64_t v2 = k0 ^ 0x6c7967656e657261ULL;
4901 uint64_t v3 = k1 ^ 0x7465646279746573ULL;
4902
4903 auto sipround = [&]() {
4904 v0 += v1; v1 = siphash_rotl64(v1, 13); v1 ^= v0; v0 = siphash_rotl64(v0, 32);
4905 v2 += v3; v3 = siphash_rotl64(v3, 16); v3 ^= v2;
4906 v0 += v3; v3 = siphash_rotl64(v3, 21); v3 ^= v0;
4907 v2 += v1; v1 = siphash_rotl64(v1, 17); v1 ^= v2; v2 = siphash_rotl64(v2, 32);
4908 };
4909
4910 size_t i = 0;
4911 for (; i + 8 <= len; i += 8) {
4912 uint64_t m;
4913 std::memcpy(&m, data + i, 8);
4914 v3 ^= m;
4915 sipround(); sipround();
4916 v0 ^= m;
4917 }
4918 // Remaining bytes + length byte
4919 uint64_t last = static_cast<uint64_t>(len & 0xff) << 56;
4920 size_t rem = len - i;
4921 for (size_t j = 0; j < rem; ++j) last |= static_cast<uint64_t>(data[i + j]) << (j * 8);
4922 v3 ^= last;
4923 sipround(); sipround();
4924 v0 ^= last;
4925 v2 ^= 0xff;
4926 sipround(); sipround(); sipround(); sipround();
4927 return v0 ^ v1 ^ v2 ^ v3;
4928}
4929
4930} // anonymous namespace
4931
4932static constexpr uint64_t GCS_P = 19;
4933static constexpr uint64_t GCS_M = 784931ULL;
4934
4935// Encode a GCS filter: sort, delta-encode, Golomb-Rice encode with P=19
4936static bool gcs_encode(const std::vector<uint64_t>& values,
4937 uint8_t* out, size_t* out_len) {
4938 std::vector<uint64_t> sorted = values;
4939 std::sort(sorted.begin(), sorted.end());
4940
4941 // Golomb-Rice encode: quotient (unary) + P-bit remainder
4942 // Pack bits into bytes
4943 std::vector<uint8_t> bits;
4944 bits.reserve(sorted.size() * 4);
4945
4946 uint64_t prev = 0;
4947 for (uint64_t v : sorted) {
4948 uint64_t delta = v - prev;
4949 prev = v;
4950 uint64_t q = delta >> GCS_P;
4951 uint64_t r = delta & ((1ULL << GCS_P) - 1);
4952 // Write q ones then a zero (unary)
4953 for (uint64_t i = 0; i < q; ++i) bits.push_back(1);
4954 bits.push_back(0);
4955 // Write P-bit remainder, MSB first
4956 for (int i = static_cast<int>(GCS_P) - 1; i >= 0; --i) {
4957 bits.push_back(static_cast<uint8_t>((r >> i) & 1));
4958 }
4959 }
4960
4961 // Pack bits into bytes
4962 size_t nbytes = (bits.size() + 7) / 8;
4963 if (*out_len < nbytes) return false;
4964 std::memset(out, 0, nbytes);
4965 for (size_t i = 0; i < bits.size(); ++i) {
4966 if (bits[i]) out[i / 8] |= static_cast<uint8_t>(0x80u >> (i % 8));
4967 }
4968 *out_len = nbytes;
4969 return true;
4970}
4971
4972static bool gcs_decode(const uint8_t* filter, size_t filter_len, size_t n_items,
4973 std::vector<uint64_t>& out) {
4974 out.clear();
4975 out.reserve(n_items);
4976
4977 // Unpack bits
4978 size_t bit_pos = 0;
4979 auto read_bit = [&]() -> int {
4980 if (bit_pos / 8 >= filter_len) return -1;
4981 return (filter[bit_pos / 8] >> (7 - (bit_pos % 8))) & 1;
4982 };
4983
4984 uint64_t prev = 0;
4985 for (size_t i = 0; i < n_items; ++i) {
4986 // Read unary quotient
4987 uint64_t q = 0;
4988 while (true) {
4989 int b = read_bit();
4990 if (b < 0) return false;
4991 ++bit_pos;
4992 if (b == 0) break;
4993 ++q;
4994 }
4995 // Read P-bit remainder
4996 uint64_t r = 0;
4997 for (size_t j = 0; j < GCS_P; ++j) {
4998 int b = read_bit();
4999 if (b < 0) return false;
5000 ++bit_pos;
5001 r = (r << 1) | static_cast<uint64_t>(b);
5002 }
5003 uint64_t delta = (q << GCS_P) | r;
5004 prev += delta;
5005 out.push_back(prev);
5006 }
5007 return true;
5008}
5009
5011 const uint8_t key[16],
5012 const uint8_t** data, const size_t* data_sizes, size_t count,
5013 uint8_t* filter_out, size_t* filter_len) {
5014 if (!key || !filter_out || !filter_len) return UFSECP_ERR_NULL_ARG;
5015 if (!data && count > 0) return UFSECP_ERR_NULL_ARG;
5016 if (!data_sizes && count > 0) return UFSECP_ERR_NULL_ARG;
5017
5018 try {
5019 uint64_t const modulus = static_cast<uint64_t>(count) * GCS_M;
5020 std::vector<uint64_t> hashed;
5021 hashed.reserve(count);
5022
5023 for (size_t i = 0; i < count; ++i) {
5024 uint64_t h = siphash24(key, data[i], data_sizes[i]);
5025 // Reduce modulo N*M using multiplication technique to avoid bias
5026 hashed.push_back(mulhi64(h, modulus));
5027 }
5028
5029 if (!gcs_encode(hashed, filter_out, filter_len)) {
5031 }
5032 return UFSECP_OK;
5033 } catch (...) { return UFSECP_ERR_INTERNAL; }
5034}
5035
5037 const uint8_t key[16],
5038 const uint8_t* filter, size_t filter_len,
5039 size_t n_items,
5040 const uint8_t* item, size_t item_len) {
5041 if (!key || !filter || !item) return UFSECP_ERR_NULL_ARG;
5042
5043 try {
5044 std::vector<uint64_t> decoded;
5045 if (!gcs_decode(filter, filter_len, n_items, decoded)) {
5046 return UFSECP_ERR_BAD_INPUT;
5047 }
5048
5049 uint64_t const modulus = static_cast<uint64_t>(n_items) * GCS_M;
5050 uint64_t h = siphash24(key, item, item_len);
5051 uint64_t target = mulhi64(h, modulus);
5052
5053 for (uint64_t v : decoded) {
5054 if (v == target) return UFSECP_OK;
5055 if (v > target) break;
5056 }
5057 return UFSECP_ERR_NOT_FOUND;
5058 } catch (...) { return UFSECP_ERR_INTERNAL; }
5059}
5060
5062 const uint8_t key[16],
5063 const uint8_t* filter, size_t filter_len,
5064 size_t n_items,
5065 const uint8_t** query, const size_t* query_sizes, size_t query_count) {
5066 if (!key || !filter) return UFSECP_ERR_NULL_ARG;
5067 if (!query && query_count > 0) return UFSECP_ERR_NULL_ARG;
5068 if (!query_sizes && query_count > 0) return UFSECP_ERR_NULL_ARG;
5069
5070 try {
5071 std::vector<uint64_t> decoded;
5072 if (!gcs_decode(filter, filter_len, n_items, decoded)) {
5073 return UFSECP_ERR_BAD_INPUT;
5074 }
5075
5076 uint64_t const modulus = static_cast<uint64_t>(n_items) * GCS_M;
5077 for (size_t qi = 0; qi < query_count; ++qi) {
5078 uint64_t h = siphash24(key, query[qi], query_sizes[qi]);
5079 uint64_t target = mulhi64(h, modulus);
5080 for (uint64_t v : decoded) {
5081 if (v == target) return UFSECP_OK;
5082 if (v > target) break;
5083 }
5084 }
5085 return UFSECP_ERR_NOT_FOUND;
5086 } catch (...) { return UFSECP_ERR_INTERNAL; }
5087}
5088
5089/* ===========================================================================
5090 * BIP-174/370 — PSBT Signing Helpers
5091 * =========================================================================== */
5092
5094 ufsecp_ctx* ctx,
5095 const uint8_t sighash32[32],
5096 const uint8_t privkey[32],
5097 uint8_t sighash_type,
5098 uint8_t* sig_out, size_t* sig_len) {
5099 if (!ctx || !sighash32 || !privkey || !sig_out || !sig_len) return UFSECP_ERR_NULL_ARG;
5100 ctx_clear_err(ctx);
5101
5102 Scalar sk;
5103 if (!scalar_parse_strict_nonzero(privkey, sk)) {
5104 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
5105 }
5106
5107 if (*sig_len < 73) {
5108 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "legacy sig buffer too small (need 73)");
5109 }
5110
5111 try {
5112 std::array<uint8_t, 32> msg_arr;
5113 std::memcpy(msg_arr.data(), sighash32, 32);
5114 auto sig = secp256k1::ecdsa_sign(msg_arr, sk);
5115 secp256k1::detail::secure_erase(&sk, sizeof(sk));
5116
5117 // DER encode: extract r and s from compact encoding
5118 auto compact = sig.to_compact();
5119 std::array<uint8_t, 32> r_arr, s_arr;
5120 std::memcpy(r_arr.data(), compact.data(), 32);
5121 std::memcpy(s_arr.data(), compact.data() + 32, 32);
5122 // Build DER manually
5123 uint8_t der[72];
5124 size_t der_len = 0;
5125 // r: add 0x00 prefix if high bit set
5126 uint8_t r_data[33], s_data[33];
5127 size_t r_len = 0, s_len = 0;
5128 // Strip leading zeros from r
5129 size_t r_start = 0;
5130 while (r_start < 31 && r_arr[r_start] == 0) ++r_start;
5131 if (r_arr[r_start] & 0x80) { r_data[0] = 0x00; ++r_len; }
5132 for (size_t i = r_start; i < 32; ++i) r_data[r_len++] = r_arr[i];
5133 // Strip leading zeros from s
5134 size_t s_start = 0;
5135 while (s_start < 31 && s_arr[s_start] == 0) ++s_start;
5136 if (s_arr[s_start] & 0x80) { s_data[0] = 0x00; ++s_len; }
5137 for (size_t i = s_start; i < 32; ++i) s_data[s_len++] = s_arr[i];
5138
5139 size_t total = 2 + r_len + 2 + s_len;
5140 der[der_len++] = 0x30;
5141 der[der_len++] = static_cast<uint8_t>(total);
5142 der[der_len++] = 0x02;
5143 der[der_len++] = static_cast<uint8_t>(r_len);
5144 std::memcpy(der + der_len, r_data, r_len); der_len += r_len;
5145 der[der_len++] = 0x02;
5146 der[der_len++] = static_cast<uint8_t>(s_len);
5147 std::memcpy(der + der_len, s_data, s_len); der_len += s_len;
5148
5149 if (*sig_len < der_len + 1) {
5150 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "legacy sig buffer too small");
5151 }
5152 std::memcpy(sig_out, der, der_len);
5153 sig_out[der_len] = sighash_type;
5154 *sig_len = der_len + 1;
5155 return UFSECP_OK;
5156 } UFSECP_CATCH_RETURN(ctx)
5157}
5158
5160 ufsecp_ctx* ctx,
5161 const uint8_t sighash32[32],
5162 const uint8_t privkey[32],
5163 uint8_t sighash_type,
5164 uint8_t* sig_out, size_t* sig_len) {
5165 if (!ctx || !sighash32 || !privkey || !sig_out || !sig_len) return UFSECP_ERR_NULL_ARG;
5166 ctx_clear_err(ctx);
5167
5168 if (*sig_len < 65) {
5169 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "segwit sig buffer too small (need 65)");
5170 }
5171
5172 Scalar sk;
5173 if (!scalar_parse_strict_nonzero(privkey, sk)) {
5174 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
5175 }
5176
5177 try {
5178 std::array<uint8_t, 32> msg_arr;
5179 std::memcpy(msg_arr.data(), sighash32, 32);
5180 auto sig = secp256k1::ecdsa_sign(msg_arr, sk);
5181 secp256k1::detail::secure_erase(&sk, sizeof(sk));
5182 auto compact = sig.to_compact();
5183 std::memcpy(sig_out, compact.data(), 32);
5184 std::memcpy(sig_out + 32, compact.data() + 32, 32);
5185 sig_out[64] = sighash_type;
5186 *sig_len = 65;
5187 return UFSECP_OK;
5188 } UFSECP_CATCH_RETURN(ctx)
5189}
5190
5192 ufsecp_ctx* ctx,
5193 const uint8_t sighash32[32],
5194 const uint8_t privkey[32],
5195 uint8_t sighash_type,
5196 const uint8_t* aux_rand32,
5197 uint8_t* sig_out, size_t* sig_len) {
5198 if (!ctx || !sighash32 || !privkey || !sig_out || !sig_len) return UFSECP_ERR_NULL_ARG;
5199 ctx_clear_err(ctx);
5200
5201 size_t const expected_len = (sighash_type == UFSECP_SIGHASH_DEFAULT) ? 64 : 65;
5202 if (*sig_len < expected_len) {
5203 return ctx_set_err(ctx, UFSECP_ERR_BUF_TOO_SMALL, "taproot sig buffer too small");
5204 }
5205
5206 Scalar sk;
5207 if (!scalar_parse_strict_nonzero(privkey, sk)) {
5208 return ctx_set_err(ctx, UFSECP_ERR_BAD_KEY, "privkey is zero or >= n");
5209 }
5210
5211 try {
5212 std::array<uint8_t, 32> msg_arr;
5213 std::array<uint8_t, 32> aux_arr{};
5214 std::memcpy(msg_arr.data(), sighash32, 32);
5215 if (aux_rand32) std::memcpy(aux_arr.data(), aux_rand32, 32);
5216
5218 auto sig = secp256k1::ct::schnorr_sign(kp, msg_arr, aux_arr);
5219 secp256k1::detail::secure_erase(&sk, sizeof(sk));
5220 secp256k1::detail::secure_erase(&kp.d, sizeof(kp.d));
5221
5222 auto bytes = sig.to_bytes();
5223 std::memcpy(sig_out, bytes.data(), 64);
5224 if (sighash_type != UFSECP_SIGHASH_DEFAULT) {
5225 sig_out[64] = sighash_type;
5226 *sig_len = 65;
5227 } else {
5228 *sig_len = 64;
5229 }
5230 return UFSECP_OK;
5231 } UFSECP_CATCH_RETURN(ctx)
5232}
5233
5235 ufsecp_ctx* ctx,
5236 const ufsecp_bip32_key* master_xprv,
5237 const char* key_path,
5238 uint8_t privkey_out[32]) {
5239 if (!ctx || !master_xprv || !key_path || !privkey_out) return UFSECP_ERR_NULL_ARG;
5240 ctx_clear_err(ctx);
5241
5242 ufsecp_bip32_key derived{};
5243 ufsecp_error_t rc = ufsecp_bip32_derive_path(ctx, master_xprv, key_path, &derived);
5244 if (rc != UFSECP_OK) return rc;
5245
5246 return ufsecp_bip32_privkey(ctx, &derived, privkey_out);
5247}
5248
5249/* ===========================================================================
5250 * BIP-380..386 — Output Descriptors (key expression parser)
5251 * =========================================================================== */
5252
5254 ufsecp_ctx* ctx,
5255 const char* descriptor,
5256 uint32_t index,
5257 ufsecp_desc_key* key_out,
5258 char* addr_out, size_t* addr_len) {
5259 if (!ctx || !descriptor || !key_out) return UFSECP_ERR_NULL_ARG;
5260 if (addr_out && !addr_len) return UFSECP_ERR_NULL_ARG;
5261 ctx_clear_err(ctx);
5262
5263 std::string desc(descriptor);
5264
5265 try {
5266 // Determine descriptor type by prefix
5268 std::string inner;
5269 std::string outer_func;
5270
5271 auto strip_func = [](const std::string& s, const std::string& fname, std::string& inner_out) -> bool {
5272 if (s.size() <= fname.size() + 2) return false;
5273 if (s.substr(0, fname.size() + 1) != fname + "(") return false;
5274 if (s.back() != ')') return false;
5275 inner_out = s.substr(fname.size() + 1, s.size() - fname.size() - 2);
5276 return true;
5277 };
5278
5279 if (strip_func(desc, "wpkh", inner)) {
5280 dtype = UFSECP_DESC_WPKH;
5281 } else if (desc.substr(0, 8) == "sh(wpkh(" && desc.back() == ')' &&
5282 desc[desc.size()-2] == ')') {
5283 dtype = UFSECP_DESC_SH_WPKH;
5284 inner = desc.substr(8, desc.size() - 10); // strip sh(wpkh( and ))
5285 } else if (strip_func(desc, "tr", inner)) {
5286 dtype = UFSECP_DESC_TR;
5287 } else if (strip_func(desc, "pkh", inner)) {
5288 dtype = UFSECP_DESC_PKH;
5289 } else if (strip_func(desc, "pk", inner)) {
5290 dtype = UFSECP_DESC_PK;
5291 } else {
5292 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unsupported descriptor type");
5293 }
5294
5295 // Validate no extra ')' in inner
5296 // Find path suffix: inner may be "xpub.../path" or "xpub.../path/*"
5297 // or just a raw hex pubkey
5298 key_out->type = dtype;
5299 key_out->network = UFSECP_NET_MAINNET;
5300 key_out->path[0] = '\0';
5301
5302 // Find the key part: if it starts with xpub/xprv, parse BIP-32
5303 std::string key_str, path_suffix;
5304 auto slash_pos = inner.find('/');
5305 if (slash_pos != std::string::npos) {
5306 key_str = inner.substr(0, slash_pos);
5307 path_suffix = inner.substr(slash_pos);
5308 } else {
5309 key_str = inner;
5310 }
5311
5312 // Validate path_suffix - reject non-numeric characters besides / and '*
5313 for (char c : path_suffix) {
5314 if (c != '/' && c != '*' && c != '\'' && !std::isdigit((unsigned char)c) && c != ';' && c != '<' && c != '>') {
5315 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid path characters");
5316 }
5317 }
5318
5319 uint8_t pubkey33[33]{};
5320 int net = UFSECP_NET_MAINNET;
5321
5322 if (key_str.size() == 111 || key_str.size() == 112 || // xpub
5323 (key_str.substr(0,4) == "xpub" || key_str.substr(0,4) == "xprv" ||
5324 key_str.substr(0,4) == "tpub" || key_str.substr(0,4) == "tprv")) {
5325 // BIP-32 extended key
5326 if (key_str[0] == 't') net = UFSECP_NET_TESTNET;
5327
5328 // Decode the xpub/xprv using base58check
5329 auto [data, valid] = secp256k1::base58check_decode(key_str);
5330 if (!valid || data.size() < 78) {
5331 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid xpub/xprv in descriptor");
5332 }
5333
5334 // Build ufsecp_bip32_key
5335 ufsecp_bip32_key bip32k{};
5336 std::memcpy(bip32k.data, data.data(), 78);
5337 // Determine if private
5338 uint32_t ver = (uint32_t(data[0]) << 24) | (uint32_t(data[1]) << 16) |
5339 (uint32_t(data[2]) << 8) | uint32_t(data[3]);
5340 bip32k.is_private = (ver == 0x0488ADE4u || ver == 0x04358394u) ? 1 : 0;
5341
5342 // Derive path + index
5343 std::string derive_path = "m";
5344 if (!path_suffix.empty()) {
5345 // Replace '*' with index, handle <a;b> range as index selection
5346 std::string ps = path_suffix;
5347 // Handle <0;1> style
5348 auto ab = ps.find('<');
5349 if (ab != std::string::npos) {
5350 auto ae = ps.find('>', ab);
5351 if (ae != std::string::npos) ps.replace(ab, ae - ab + 1, "0");
5352 }
5353 // Replace * with index
5354 auto star = ps.find('*');
5355 if (star != std::string::npos) {
5356 ps.replace(star, 1, std::to_string(index));
5357 }
5358 derive_path += ps;
5359 }
5360
5361 ufsecp_bip32_key derived{};
5362 if (derive_path != "m") {
5363 ufsecp_ctx* dummy = ctx;
5364 ufsecp_error_t rc = ufsecp_bip32_derive_path(dummy, &bip32k, derive_path.c_str(), &derived);
5365 if (rc != UFSECP_OK) return rc;
5366 } else {
5367 derived = bip32k;
5368 }
5369
5370 // Extract pubkey
5371 ufsecp_error_t rc = ufsecp_bip32_pubkey(ctx, &derived, pubkey33);
5372 if (rc != UFSECP_OK) return rc;
5373 } else if (key_str.size() == 64 || key_str.size() == 66) {
5374 // Raw hex pubkey
5375 if (key_str.size() % 2 != 0) {
5376 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "odd hex length in descriptor");
5377 }
5378 size_t expected_bytes = key_str.size() / 2;
5379 if (expected_bytes != 33 && expected_bytes != 32) {
5380 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "pubkey must be 33 or 32 bytes");
5381 }
5382 for (size_t i = 0; i < key_str.size(); i += 2) {
5383 unsigned int byte_val = 0;
5384 // Manual hex parse
5385 auto hexchar = [](char c) -> int {
5386 if (c >= '0' && c <= '9') return c - '0';
5387 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
5388 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
5389 return -1;
5390 };
5391 int hi = hexchar(key_str[i]), lo = hexchar(key_str[i+1]);
5392 if (hi < 0 || lo < 0) {
5393 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "invalid hex in descriptor key");
5394 }
5395 byte_val = static_cast<unsigned int>(hi * 16 + lo);
5396 pubkey33[i / 2] = static_cast<uint8_t>(byte_val);
5397 }
5398 } else {
5399 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unrecognized key format in descriptor");
5400 }
5401
5402 // Fill key_out
5403 if (dtype == UFSECP_DESC_TR) {
5404 // x-only pubkey (skip prefix byte if 33 bytes)
5405 if (pubkey33[0] == 0x02 || pubkey33[0] == 0x03) {
5406 std::memcpy(key_out->pubkey, pubkey33 + 1, 32);
5407 key_out->pubkey_len = 32;
5408 } else {
5409 std::memcpy(key_out->pubkey, pubkey33, 32);
5410 key_out->pubkey_len = 32;
5411 }
5412 } else {
5413 std::memcpy(key_out->pubkey, pubkey33, 33);
5414 key_out->pubkey_len = 33;
5415 }
5416 key_out->network = net;
5417 if (path_suffix.size() < sizeof(key_out->path)) {
5418 std::memcpy(key_out->path, path_suffix.c_str(), path_suffix.size() + 1);
5419 } else {
5420 key_out->path[0] = '\0';
5421 }
5422
5423 // Generate address if requested
5424 if (addr_out && addr_len) {
5425 switch (dtype) {
5426 case UFSECP_DESC_WPKH:
5427 return ufsecp_addr_p2wpkh(ctx, pubkey33, net, addr_out, addr_len);
5428 case UFSECP_DESC_PKH:
5429 case UFSECP_DESC_PK:
5430 return ufsecp_addr_p2pkh(ctx, pubkey33, net, addr_out, addr_len);
5431 case UFSECP_DESC_TR: {
5432 uint8_t xonly[32];
5433 if (pubkey33[0] == 0x02 || pubkey33[0] == 0x03) {
5434 std::memcpy(xonly, pubkey33 + 1, 32);
5435 } else {
5436 std::memcpy(xonly, pubkey33, 32);
5437 }
5438 return ufsecp_addr_p2tr(ctx, xonly, net, addr_out, addr_len);
5439 }
5441 return ufsecp_addr_p2sh_p2wpkh(ctx, pubkey33, net, addr_out, addr_len);
5442 default:
5443 return ctx_set_err(ctx, UFSECP_ERR_BAD_INPUT, "unknown descriptor type for address");
5444 }
5445 }
5446
5447 return UFSECP_OK;
5448 } UFSECP_CATCH_RETURN(ctx)
5449}
5450
5452 ufsecp_ctx* ctx,
5453 const char* descriptor,
5454 uint32_t index,
5455 char* addr_out, size_t* addr_len) {
5456 if (!ctx || !descriptor || !addr_out || !addr_len) return UFSECP_ERR_NULL_ARG;
5457 ctx_clear_err(ctx);
5458
5459 ufsecp_desc_key key_out{};
5460 return ufsecp_descriptor_parse(ctx, descriptor, index, &key_out, addr_out, addr_len);
5461}
SecureEraseGuard & operator=(const SecureEraseGuard &)=delete
SecureEraseGuard(const SecureEraseGuard &)=delete
SecureEraseGuard(T *value) noexcept
static digest_type hash(const void *data, std::size_t len) noexcept
Definition sha256.hpp:120
static digest_type hash256(const void *data, std::size_t len) noexcept
Definition sha256.hpp:127
digest_type finalize() noexcept
Definition sha256.hpp:73
void update(const void *data, std::size_t len) noexcept
Definition sha256.hpp:43
static digest_type hash(const void *data, std::size_t len) noexcept
Definition sha512.hpp:113
static bool parse_bytes_strict(const std::uint8_t *bytes32, FieldElement &out) noexcept
static FieldElement from_uint64(std::uint64_t value)
static Point from_affine(const FieldElement &x, const FieldElement &y)
std::array< std::uint8_t, 65 > to_uncompressed() const
Point scalar_mul(const Scalar &scalar) const
bool is_infinity() const noexcept
Definition point.hpp:97
Point add(const Point &other) const
static Point generator()
static Point infinity()
std::array< std::uint8_t, 33 > to_compressed() const
static bool parse_bytes_strict_nonzero(const std::uint8_t *bytes32, Scalar &out) noexcept
std::array< std::uint8_t, 32 > to_bytes() const
static bool parse_bytes_strict(const std::uint8_t *bytes32, Scalar &out) noexcept
Scalar negate() const
std::array< std::uint8_t, 20 > ethereum_address_bytes(const fast::Point &pubkey)
bool bitcoin_verify_message(const std::uint8_t *msg, std::size_t msg_len, const fast::Point &pubkey, const ECDSASignature &sig)
const CoinParams * find_by_coin_type(std::uint32_t coin_type)
std::string coin_wif_encode(const fast::Scalar &private_key, const CoinParams &coin, bool compressed=true, bool testnet=false)
std::string bitcoin_sig_to_base64(const RecoverableSignature &rsig, bool compressed=true)
BitcoinSigDecodeResult bitcoin_sig_from_base64(const std::string &base64)
std::string coin_address(const fast::Point &pubkey, const CoinParams &coin, bool testnet=false)
std::string ethereum_address(const fast::Point &pubkey)
EthSignature eth_sign_hash(const std::array< std::uint8_t, 32 > &hash, const fast::Scalar &private_key, std::uint64_t chain_id=0)
RecoverableSignature bitcoin_sign_message(const std::uint8_t *msg, std::size_t msg_len, const fast::Scalar &private_key)
std::pair< std::array< std::uint8_t, 20 >, bool > ecrecover(const std::array< std::uint8_t, 32 > &msg_hash, const std::array< std::uint8_t, 32 > &r, const std::array< std::uint8_t, 32 > &s, std::uint64_t v)
std::array< std::uint8_t, 32 > bitcoin_message_hash(const std::uint8_t *msg, std::size_t msg_len)
std::array< std::uint8_t, 32 > eip191_hash(const std::uint8_t *msg, std::size_t msg_len)
std::array< std::uint8_t, 32 > keccak256(const std::uint8_t *data, std::size_t len)
std::pair< ExtendedKey, bool > coin_derive_key(const ExtendedKey &master, const CoinParams &coin, std::uint32_t account=0, bool change=false, std::uint32_t address_index=0)
ECDSASignature ecdsa_sign_verified(const std::array< std::uint8_t, 32 > &msg_hash, const fast::Scalar &private_key)
Point generator_mul(const Scalar &k) noexcept
ECDSASignature ecdsa_sign(const std::array< std::uint8_t, 32 > &msg_hash, const fast::Scalar &private_key)
SchnorrSignature schnorr_sign(const SchnorrKeypair &kp, const std::array< std::uint8_t, 32 > &msg, const std::array< std::uint8_t, 32 > &aux_rand)
SchnorrSignature schnorr_sign_verified(const SchnorrKeypair &kp, const std::array< std::uint8_t, 32 > &msg, const std::array< std::uint8_t, 32 > &aux_rand)
SchnorrKeypair schnorr_keypair_create(const fast::Scalar &private_key)
void secure_erase(void *ptr, std::size_t len) noexcept
std::uint64_t mulhi64(std::uint64_t a, std::uint64_t b) noexcept
Definition arith64.hpp:102
bool ensure_library_integrity(bool verbose=false)
Definition init.hpp:14
RangeProof range_prove(std::uint64_t value, const fast::Scalar &blinding, const PedersenCommitment &commitment, const std::array< std::uint8_t, 32 > &aux_rand)
bool range_verify(const PedersenCommitment &commitment, const RangeProof &proof)
DLEQProof dleq_prove(const fast::Scalar &secret, const fast::Point &G, const fast::Point &H, const fast::Point &P, const fast::Point &Q, const std::array< std::uint8_t, 32 > &aux_rand)
bool dleq_verify(const DLEQProof &proof, const fast::Point &G, const fast::Point &H, const fast::Point &P, const fast::Point &Q)
KnowledgeProof knowledge_prove(const fast::Scalar &secret, const fast::Point &pubkey, const std::array< std::uint8_t, 32 > &msg, const std::array< std::uint8_t, 32 > &aux_rand)
bool knowledge_verify(const KnowledgeProof &proof, const fast::Point &pubkey, const std::array< std::uint8_t, 32 > &msg)
std::array< std::uint8_t, 32 > ecdh_compute_raw(const Scalar &private_key, const Point &public_key)
std::array< std::uint8_t, 32 > ecdh_compute_xonly(const Scalar &private_key, const Point &public_key)
SchnorrSignature frost_aggregate(const std::vector< FrostPartialSig > &partial_sigs, const std::vector< FrostNonceCommitment > &nonce_commitments, const fast::Point &group_public_key, const std::array< std::uint8_t, 32 > &msg)
std::pair< FrostKeyPackage, bool > frost_keygen_finalize(ParticipantId participant_id, const std::vector< FrostCommitment > &commitments, const std::vector< FrostShare > &received_shares, std::uint32_t threshold, std::uint32_t num_participants)
std::pair< Bip39Entropy, bool > bip39_mnemonic_to_entropy(const std::string &mnemonic)
std::array< std::uint8_t, 32 > ecdh_compute(const Scalar &private_key, const Point &public_key)
std::vector< std::uint8_t > ecies_decrypt(const fast::Scalar &privkey, const std::uint8_t *envelope, std::size_t envelope_len)
MuSig2KeyAggCtx musig2_key_agg(const std::vector< std::array< std::uint8_t, 32 > > &pubkeys)
std::string address_p2sh(const std::array< std::uint8_t, 20 > &script_hash, Network net=Network::Mainnet)
PedersenCommitment pedersen_switch_commit(const fast::Scalar &value, const fast::Scalar &blinding, const fast::Scalar &switch_blind)
MuSig2AggNonce musig2_nonce_agg(const std::vector< MuSig2PubNonce > &pub_nonces)
std::pair< ExtendedKey, bool > bip32_master_key(const std::uint8_t *seed, std::size_t seed_len)
RecoverableSignature ecdsa_sign_recoverable(const std::array< std::uint8_t, 32 > &msg_hash, const Scalar &private_key)
fast::Scalar musig2_partial_sign(MuSig2SecNonce &sec_nonce, const fast::Scalar &secret_key, const MuSig2KeyAggCtx &key_agg_ctx, const MuSig2Session &session, std::size_t signer_index)
std::array< std::uint8_t, 32 > ellswift_xdh(const std::uint8_t ell_a64[64], const std::uint8_t ell_b64[64], const Scalar &our_privkey, bool initiating) noexcept
std::string address_p2tr_raw(const std::array< std::uint8_t, 32 > &output_key_x, Network net=Network::Mainnet)
WitnessProgram parse_witness_program(const std::uint8_t *script, std::size_t script_len) noexcept
bool pedersen_verify_sum(const PedersenCommitment *commitments_pos, std::size_t n_pos, const PedersenCommitment *commitments_neg, std::size_t n_neg)
bool aead_chacha20_poly1305_decrypt(const std::uint8_t key[32], const std::uint8_t nonce[12], const std::uint8_t *aad, std::size_t aad_len, const std::uint8_t *ciphertext, std::size_t ciphertext_len, const std::uint8_t tag[16], std::uint8_t *out) noexcept
std::array< std::uint8_t, 32 > taproot_keypath_sighash(const TapSighashTxData &tx_data, std::size_t input_index, std::uint8_t hash_type, const std::uint8_t *annex=nullptr, std::size_t annex_len=0) noexcept
std::string address_p2sh_p2wpkh(const fast::Point &pubkey, Network net=Network::Mainnet)
bool schnorr_batch_verify(const SchnorrBatchEntry *entries, std::size_t n)
std::pair< fast::Point, fast::Scalar > silent_payment_create_output(const std::vector< fast::Scalar > &input_privkeys, const SilentPaymentAddress &recipient, std::uint32_t k=0)
void aead_chacha20_poly1305_encrypt(const std::uint8_t key[32], const std::uint8_t nonce[12], const std::uint8_t *aad, std::size_t aad_len, const std::uint8_t *plaintext, std::size_t plaintext_len, std::uint8_t *out, std::uint8_t tag[16]) noexcept
std::array< std::uint8_t, 34 > segwit_scriptpubkey_p2wsh(const std::uint8_t script_hash[32]) noexcept
bool frost_verify_partial(const FrostPartialSig &partial_sig, const FrostNonceCommitment &signer_commitment, const fast::Point &verification_share, const std::array< std::uint8_t, 32 > &msg, const std::vector< FrostNonceCommitment > &nonce_commitments, const fast::Point &group_public_key)
std::array< std::uint8_t, 32 > tagged_hash(const char *tag, const void *data, std::size_t len)
Scalar taproot_tweak_privkey(const Scalar &private_key, const std::uint8_t *merkle_root=nullptr, std::size_t merkle_root_len=0)
std::array< std::uint8_t, 25 > bip143_p2wpkh_script_code(const std::uint8_t pubkey_hash[20]) noexcept
std::pair< std::vector< std::uint8_t >, bool > base58check_decode(const std::string &encoded)
WIFDecodeResult wif_decode(const std::string &wif)
std::pair< std::string, bool > bip39_generate(std::size_t entropy_bytes, const std::uint8_t *entropy_in=nullptr)
std::array< std::uint8_t, 64 > hmac_sha512(const std::uint8_t *key, std::size_t key_len, const std::uint8_t *data, std::size_t data_len)
std::array< std::uint8_t, 32 > witness_commitment(const std::array< std::uint8_t, 32 > &witness_root, const std::array< std::uint8_t, 32 > &witness_nonce) noexcept
std::array< std::uint8_t, 20 > hash160(const std::uint8_t *data, std::size_t len)
std::array< std::uint8_t, 32 > tapscript_sighash(const TapSighashTxData &tx_data, std::size_t input_index, std::uint8_t hash_type, const std::array< std::uint8_t, 32 > &tapleaf_hash, std::uint8_t key_version, std::uint32_t code_separator_pos, const std::uint8_t *annex=nullptr, std::size_t annex_len=0) noexcept
std::vector< std::size_t > schnorr_batch_identify_invalid(const SchnorrBatchEntry *entries, std::size_t n)
std::array< std::uint8_t, 32 > witness_script_hash(const std::uint8_t *script, std::size_t script_len) noexcept
std::string address_p2wpkh(const fast::Point &pubkey, Network net=Network::Mainnet)
fast::Point shamir_trick(const fast::Scalar &a, const fast::Point &P, const fast::Scalar &b, const fast::Point &Q)
bool schnorr_verify(const std::uint8_t *pubkey_x32, const std::uint8_t *msg32, const SchnorrSignature &sig)
std::pair< FrostCommitment, std::vector< FrostShare > > frost_keygen_begin(ParticipantId participant_id, std::uint32_t threshold, std::uint32_t num_participants, const std::array< std::uint8_t, 32 > &secret_seed)
std::array< std::uint8_t, 64 > ellswift_create(const Scalar &privkey)
std::string address_p2pkh(const fast::Point &pubkey, Network net=Network::Mainnet)
bool ecdsa_batch_verify(const ECDSABatchEntry *entries, std::size_t n)
fast::Point multi_scalar_mul(const fast::Scalar *scalars, const fast::Point *points, std::size_t n)
bool schnorr_adaptor_verify(const SchnorrAdaptorSig &pre_sig, const std::array< std::uint8_t, 32 > &pubkey_x, const std::array< std::uint8_t, 32 > &msg, const fast::Point &adaptor_point)
SchnorrAdaptorSig schnorr_adaptor_sign(const fast::Scalar &private_key, const std::array< std::uint8_t, 32 > &msg, const fast::Point &adaptor_point, const std::array< std::uint8_t, 32 > &aux_rand)
bool bip39_validate(const std::string &mnemonic)
ECDSAAdaptorSig ecdsa_adaptor_sign(const fast::Scalar &private_key, const std::array< std::uint8_t, 32 > &msg_hash, const fast::Point &adaptor_point)
bool pedersen_verify(const PedersenCommitment &commitment, const fast::Scalar &value, const fast::Scalar &blinding)
fast::Scalar pedersen_blind_sum(const fast::Scalar *blinds_in, std::size_t n_in, const fast::Scalar *blinds_out, std::size_t n_out)
PedersenCommitment pedersen_commit(const fast::Scalar &value, const fast::Scalar &blinding)
ECDSASignature ecdsa_adaptor_adapt(const ECDSAAdaptorSig &pre_sig, const fast::Scalar &adaptor_secret)
std::pair< fast::Scalar, bool > ecdsa_adaptor_extract(const ECDSAAdaptorSig &pre_sig, const ECDSASignature &sig)
MuSig2Session musig2_start_sign_session(const MuSig2AggNonce &agg_nonce, const MuSig2KeyAggCtx &key_agg_ctx, const std::array< std::uint8_t, 32 > &msg)
std::array< std::uint8_t, 22 > segwit_scriptpubkey_p2wpkh(const std::uint8_t pubkey_hash[20]) noexcept
std::vector< std::size_t > ecdsa_batch_identify_invalid(const ECDSABatchEntry *entries, std::size_t n)
std::pair< std::array< std::uint8_t, 64 >, bool > bip39_mnemonic_to_seed(const std::string &mnemonic, const std::string &passphrase="")
std::string wif_encode(const fast::Scalar &private_key, bool compressed=true, Network net=Network::Mainnet)
std::pair< fast::Scalar, bool > schnorr_adaptor_extract(const SchnorrAdaptorSig &pre_sig, const SchnorrSignature &sig)
bool musig2_partial_verify(const fast::Scalar &partial_sig, const MuSig2PubNonce &pub_nonce, const std::array< std::uint8_t, 32 > &pubkey, const MuSig2KeyAggCtx &key_agg_ctx, const MuSig2Session &session, std::size_t signer_index)
std::vector< std::pair< std::uint32_t, fast::Scalar > > silent_payment_scan(const fast::Scalar &scan_privkey, const fast::Scalar &spend_privkey, const std::vector< fast::Point > &input_pubkeys, const std::vector< std::array< std::uint8_t, 32 > > &output_pubkeys)
std::pair< std::array< std::uint8_t, 32 >, int > taproot_output_key(const std::array< std::uint8_t, 32 > &internal_key_x, const std::uint8_t *merkle_root=nullptr, std::size_t merkle_root_len=0)
SchnorrSignature schnorr_adaptor_adapt(const SchnorrAdaptorSig &pre_sig, const fast::Scalar &adaptor_secret)
std::pair< Point, bool > ecdsa_recover(const std::array< std::uint8_t, 32 > &msg_hash, const ECDSASignature &sig, int recid)
std::pair< MuSig2SecNonce, MuSig2PubNonce > musig2_nonce_gen(const fast::Scalar &secret_key, const std::array< std::uint8_t, 32 > &pub_key, const std::array< std::uint8_t, 32 > &agg_pub_key, const std::array< std::uint8_t, 32 > &msg, const std::uint8_t *extra_input=nullptr)
std::array< std::uint8_t, 32 > bip143_sighash(const Bip143Preimage &preimage, const Outpoint &outpoint, const std::uint8_t *script_code, std::size_t script_code_len, std::uint64_t value, std::uint32_t sequence, std::uint32_t sighash_type) noexcept
std::pair< FrostNonce, FrostNonceCommitment > frost_sign_nonce_gen(ParticipantId participant_id, const std::array< std::uint8_t, 32 > &nonce_seed)
bool taproot_verify_commitment(const std::array< std::uint8_t, 32 > &output_key_x, int output_key_parity, const std::array< std::uint8_t, 32 > &internal_key_x, const std::uint8_t *merkle_root=nullptr, std::size_t merkle_root_len=0)
FrostPartialSig frost_sign(const FrostKeyPackage &key_pkg, FrostNonce &nonce, const std::array< std::uint8_t, 32 > &msg, const std::vector< FrostNonceCommitment > &nonce_commitments)
std::array< std::uint8_t, 32 > schnorr_pubkey(const fast::Scalar &private_key)
ECDSASignature ecdsa_sign(const std::array< std::uint8_t, 32 > &msg_hash, const fast::Scalar &private_key)
bool ecdsa_adaptor_verify(const ECDSAAdaptorSig &pre_sig, const fast::Point &public_key, const std::array< std::uint8_t, 32 > &msg_hash, const fast::Point &adaptor_point)
std::array< std::uint8_t, 64 > musig2_partial_sig_agg(const std::vector< fast::Scalar > &partial_sigs, const MuSig2Session &session)
bool ecdsa_verify(const std::uint8_t *msg_hash32, const fast::Point &public_key, const ECDSASignature &sig)
std::array< std::uint8_t, 34 > segwit_scriptpubkey_p2tr(const std::uint8_t output_key[32]) noexcept
std::vector< std::uint8_t > ecies_encrypt(const fast::Point &recipient_pubkey, const std::uint8_t *plaintext, std::size_t plaintext_len)
SilentPaymentAddress silent_payment_address(const fast::Scalar &scan_privkey, const fast::Scalar &spend_privkey)
bool is_witness_program(const std::uint8_t *script, std::size_t script_len) noexcept
std::pair< ExtendedKey, bool > bip32_derive_path(const ExtendedKey &master, const std::string &path)
std::uint32_t version
Definition bip143.hpp:53
std::pair< std::array< std::uint8_t, 72 >, std::size_t > to_der() const
static bool parse_compact_strict(const std::uint8_t *data64, ECDSASignature &out) noexcept
static ECDSASignature from_compact(const std::uint8_t *data64)
std::array< std::uint8_t, 78 > serialize() const
std::uint8_t depth
Definition bip32.hpp:32
std::vector< fast::Point > coeffs
Definition frost.hpp:51
std::uint32_t threshold
Definition frost.hpp:60
fast::Point verification_share
Definition frost.hpp:58
std::uint32_t num_participants
Definition frost.hpp:61
fast::Scalar signing_share
Definition frost.hpp:57
fast::Point group_public_key
Definition frost.hpp:59
fast::Scalar binding_nonce
Definition frost.hpp:67
fast::Scalar hiding_nonce
Definition frost.hpp:66
std::vector< fast::Scalar > key_coefficients
Definition musig2.hpp:38
std::array< std::uint8_t, 32 > Q_x
Definition musig2.hpp:37
static MuSig2PubNonce deserialize(const std::array< std::uint8_t, 66 > &data)
static bool parse_strict(const std::uint8_t *data64, SchnorrSignature &out) noexcept
static bool deserialize(const std::uint8_t *data64, DLEQProof &out)
static bool deserialize(const std::uint8_t *data64, KnowledgeProof &out)
fast::Scalar tau_x
Definition zk.hpp:154
std::array< fast::Point, RANGE_PROOF_LOG2 > R
Definition zk.hpp:160
fast::Scalar mu
Definition zk.hpp:155
fast::Scalar t_hat
Definition zk.hpp:156
std::array< fast::Point, RANGE_PROOF_LOG2 > L
Definition zk.hpp:159
uint8_t is_private
Definition ufsecp.h:396
uint8_t _pad[3]
Definition ufsecp.h:397
uint8_t data[UFSECP_BIP32_SERIALIZED_LEN]
Definition ufsecp.h:395
char last_msg[128]
ufsecp_error_t last_err
uint8_t pubkey[33]
Definition ufsecp.h:1563
char path[64]
Definition ufsecp.h:1566
ufsecp_desc_type type
Definition ufsecp.h:1562
uint8_t pubkey_len
Definition ufsecp.h:1564
#define UFSECP_FROST_NONCE_COMMIT_LEN
Definition ufsecp.h:826
#define UFSECP_NET_MAINNET
Definition ufsecp.h:64
#define UFSECP_ZK_KNOWLEDGE_PROOF_LEN
Definition ufsecp.h:1023
#define UFSECP_MUSIG2_SECNONCE_LEN
Definition ufsecp.h:740
#define UFSECP_ECIES_OVERHEAD
Definition ufsecp.h:1208
#define UFSECP_ZK_DLEQ_PROOF_LEN
Definition ufsecp.h:1024
#define UFSECP_FROST_NONCE_LEN
Definition ufsecp.h:825
#define UFSECP_SIGHASH_DEFAULT
Definition ufsecp.h:1506
ufsecp_desc_type
Definition ufsecp.h:1552
@ UFSECP_DESC_PKH
Definition ufsecp.h:1554
@ UFSECP_DESC_WPKH
Definition ufsecp.h:1555
@ UFSECP_DESC_PK
Definition ufsecp.h:1553
@ UFSECP_DESC_SH_WPKH
Definition ufsecp.h:1557
@ UFSECP_DESC_TR
Definition ufsecp.h:1556
ufsecp_bip322_addr_type
Definition ufsecp.h:1435
@ UFSECP_BIP322_ADDR_P2TR
Definition ufsecp.h:1438
#define UFSECP_MUSIG2_KEYAGG_LEN
Definition ufsecp.h:738
#define UFSECP_FROST_KEYPKG_LEN
Definition ufsecp.h:824
#define UFSECP_FROST_SHARE_LEN
Definition ufsecp.h:823
#define UFSECP_MUSIG2_AGGNONCE_LEN
Definition ufsecp.h:737
#define UFSECP_NET_TESTNET
Definition ufsecp.h:65
#define UFSECP_MUSIG2_PUBNONCE_LEN
Definition ufsecp.h:736
#define UFSECP_ECDSA_ADAPTOR_SIG_LEN
Definition ufsecp.h:914
#define UFSECP_MUSIG2_SESSION_LEN
Definition ufsecp.h:739
#define UFSECP_SCHNORR_ADAPTOR_SIG_LEN
Definition ufsecp.h:913
#define UFSECP_ERR_INTERNAL
#define UFSECP_ERR_BAD_PUBKEY
#define UFSECP_ERR_NULL_ARG
#define UFSECP_ERR_VERIFY_FAIL
#define UFSECP_ERR_NOT_FOUND
#define UFSECP_ERR_SELFTEST
#define UFSECP_ERR_BAD_SIG
#define UFSECP_ERR_BUF_TOO_SMALL
#define UFSECP_ERR_BAD_INPUT
#define UFSECP_OK
#define UFSECP_ERR_ARITH
int ufsecp_error_t
#define UFSECP_ERR_BAD_KEY
ufsecp_error_t ufsecp_bip143_sighash(ufsecp_ctx *ctx, uint32_t version, const uint8_t hash_prevouts[32], const uint8_t hash_sequence[32], const uint8_t outpoint_txid[32], uint32_t outpoint_vout, const uint8_t *script_code, size_t script_code_len, uint64_t value, uint32_t sequence, const uint8_t hash_outputs[32], uint32_t locktime, uint32_t sighash_type, uint8_t sighash_out[32])
ufsecp_error_t ufsecp_seckey_negate(ufsecp_ctx *ctx, uint8_t privkey[32])
void ufsecp_ctx_destroy(ufsecp_ctx *ctx)
ufsecp_error_t ufsecp_descriptor_address(ufsecp_ctx *ctx, const char *descriptor, uint32_t index, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_hash160(const uint8_t *data, size_t len, uint8_t digest20_out[20])
ufsecp_error_t ufsecp_musig2_nonce_gen(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t pubkey32[32], const uint8_t agg_pubkey32[32], const uint8_t msg32[32], const uint8_t extra_in[32], uint8_t secnonce_out[UFSECP_MUSIG2_SECNONCE_LEN], uint8_t pubnonce_out[UFSECP_MUSIG2_PUBNONCE_LEN])
ufsecp_error_t ufsecp_silent_payment_scan(ufsecp_ctx *ctx, const uint8_t scan_privkey[32], const uint8_t spend_privkey[32], const uint8_t *input_pubkeys33, size_t n_input_pubkeys, const uint8_t *output_xonly32, size_t n_outputs, uint32_t *found_indices_out, uint8_t *found_privkeys_out, size_t *n_found)
ufsecp_error_t ufsecp_bip39_to_seed(ufsecp_ctx *ctx, const char *mnemonic, const char *passphrase, uint8_t seed64_out[64])
ufsecp_error_t ufsecp_musig2_partial_verify(ufsecp_ctx *ctx, const uint8_t partial_sig32[32], const uint8_t pubnonce[UFSECP_MUSIG2_PUBNONCE_LEN], const uint8_t pubkey32[32], const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN], const uint8_t session[UFSECP_MUSIG2_SESSION_LEN], size_t signer_index)
ufsecp_error_t ufsecp_ecdsa_sign(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t privkey[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_musig2_start_sign_session(ufsecp_ctx *ctx, const uint8_t aggnonce[UFSECP_MUSIG2_AGGNONCE_LEN], const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN], const uint8_t msg32[32], uint8_t session_out[UFSECP_MUSIG2_SESSION_LEN])
ufsecp_error_t ufsecp_pedersen_commit(ufsecp_ctx *ctx, const uint8_t value[32], const uint8_t blinding[32], uint8_t commitment33_out[33])
ufsecp_error_t ufsecp_gcs_match_any(const uint8_t key[16], const uint8_t *filter, size_t filter_len, size_t n_items, const uint8_t **query, const size_t *query_sizes, size_t query_count)
static bool scalar_parse_strict_nonzero(const uint8_t b[32], Scalar &out)
ufsecp_error_t ufsecp_schnorr_verify(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t sig64[64], const uint8_t pubkey_x[32])
ufsecp_error_t ufsecp_schnorr_verify_msg(ufsecp_ctx *ctx, const uint8_t pubkey_x[32], const uint8_t *msg, size_t msg_len, const uint8_t sig64[64])
static bool scalar_parse_strict(const uint8_t b[32], Scalar &out)
static void point_to_compressed(const Point &p, uint8_t out[33])
ufsecp_error_t ufsecp_taproot_tweak_seckey(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t *merkle_root, uint8_t tweaked32_out[32])
ufsecp_error_t ufsecp_descriptor_parse(ufsecp_ctx *ctx, const char *descriptor, uint32_t index, ufsecp_desc_key *key_out, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_psbt_derive_key(ufsecp_ctx *ctx, const ufsecp_bip32_key *master_xprv, const char *key_path, uint8_t privkey_out[32])
ufsecp_error_t ufsecp_musig2_partial_sign(ufsecp_ctx *ctx, uint8_t secnonce[UFSECP_MUSIG2_SECNONCE_LEN], const uint8_t privkey[32], const uint8_t keyagg[UFSECP_MUSIG2_KEYAGG_LEN], const uint8_t session[UFSECP_MUSIG2_SESSION_LEN], size_t signer_index, uint8_t partial_sig32_out[32])
ufsecp_error_t ufsecp_pubkey_add(ufsecp_ctx *ctx, const uint8_t a33[33], const uint8_t b33[33], uint8_t out33[33])
ufsecp_error_t ufsecp_sha512(const uint8_t *data, size_t len, uint8_t digest64_out[64])
ufsecp_error_t ufsecp_pubkey_combine(ufsecp_ctx *ctx, const uint8_t *pubkeys, size_t n, uint8_t out33[33])
ufsecp_error_t ufsecp_bip144_witness_commitment(const uint8_t witness_root[32], const uint8_t witness_nonce[32], uint8_t commitment_out[32])
ufsecp_error_t ufsecp_bip143_p2wpkh_script_code(const uint8_t pubkey_hash[20], uint8_t script_code_out[25])
static ufsecp_error_t pubkey_create_core(ufsecp_ctx *ctx, const uint8_t privkey[32], Point &pk_out)
size_t ufsecp_ctx_size(void)
ufsecp_error_t ufsecp_ecdh(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t pubkey33[33], uint8_t secret32_out[32])
ufsecp_error_t ufsecp_bip322_sign(ufsecp_ctx *ctx, const uint8_t privkey[32], ufsecp_bip322_addr_type addr_type, const uint8_t *msg, size_t msg_len, uint8_t *sig_out, size_t *sig_len)
static void extkey_to_uf(const secp256k1::ExtendedKey &ek, ufsecp_bip32_key *out)
ufsecp_error_t ufsecp_tapscript_sighash(ufsecp_ctx *ctx, uint32_t version, uint32_t locktime, size_t input_count, const uint8_t *prevout_txids, const uint32_t *prevout_vouts, const uint64_t *input_amounts, const uint32_t *input_sequences, const uint8_t *const *input_spks, const size_t *input_spk_lens, size_t output_count, const uint64_t *output_values, const uint8_t *const *output_spks, const size_t *output_spk_lens, size_t input_index, uint8_t hash_type, const uint8_t tapleaf_hash[32], uint8_t key_version, uint32_t code_separator_pos, const uint8_t *annex, size_t annex_len, uint8_t sighash_out[32])
ufsecp_error_t ufsecp_bip32_privkey(ufsecp_ctx *ctx, const ufsecp_bip32_key *key, uint8_t privkey32_out[32])
ufsecp_error_t ufsecp_schnorr_adaptor_extract(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN], const uint8_t sig64[64], uint8_t secret32_out[32])
ufsecp_error_t ufsecp_musig2_partial_sig_agg(ufsecp_ctx *ctx, const uint8_t *partial_sigs, size_t n, const uint8_t session[UFSECP_MUSIG2_SESSION_LEN], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_ecies_decrypt(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t *envelope, size_t envelope_len, uint8_t *plaintext_out, size_t *plaintext_len)
ufsecp_error_t ufsecp_last_error(const ufsecp_ctx *ctx)
ufsecp_error_t ufsecp_ecdsa_verify(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t sig64[64], const uint8_t pubkey33[33])
static void ctx_clear_err(ufsecp_ctx *ctx)
ufsecp_error_t ufsecp_pubkey_tweak_add(ufsecp_ctx *ctx, const uint8_t pubkey33[33], const uint8_t tweak[32], uint8_t out33[33])
ufsecp_error_t ufsecp_schnorr_sign(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t privkey[32], const uint8_t aux_rand[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_schnorr_adaptor_adapt(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN], const uint8_t adaptor_secret[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_coin_derive_from_seed(ufsecp_ctx *ctx, const uint8_t *seed, size_t seed_len, uint32_t coin_type, uint32_t account, int change, uint32_t index, int testnet, uint8_t *privkey32_out, uint8_t *pubkey33_out, char *addr_out, size_t *addr_len)
static bool skip_compact_bytes(const uint8_t *buf, size_t len, size_t &offset)
ufsecp_error_t ufsecp_ecdsa_sign_recoverable(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t privkey[32], uint8_t sig64_out[64], int *recid_out)
ufsecp_error_t ufsecp_pedersen_verify(ufsecp_ctx *ctx, const uint8_t commitment33[33], const uint8_t value[32], const uint8_t blinding[32])
ufsecp_error_t ufsecp_bip85_entropy(ufsecp_ctx *ctx, const ufsecp_bip32_key *master_xprv, const char *path, uint8_t *entropy_out, size_t entropy_len)
ufsecp_error_t ufsecp_bip85_bip39(ufsecp_ctx *ctx, const ufsecp_bip32_key *master_xprv, uint32_t words, uint32_t language_index, uint32_t index, char *mnemonic_out, size_t *mnemonic_len)
ufsecp_error_t ufsecp_silent_payment_address(ufsecp_ctx *ctx, const uint8_t scan_privkey[32], const uint8_t spend_privkey[32], uint8_t scan_pubkey33_out[33], uint8_t spend_pubkey33_out[33], char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_taproot_keypath_sighash(ufsecp_ctx *ctx, uint32_t version, uint32_t locktime, size_t input_count, const uint8_t *prevout_txids, const uint32_t *prevout_vouts, const uint64_t *input_amounts, const uint32_t *input_sequences, const uint8_t *const *input_spks, const size_t *input_spk_lens, size_t output_count, const uint64_t *output_values, const uint8_t *const *output_spks, const size_t *output_spk_lens, size_t input_index, uint8_t hash_type, const uint8_t *annex, size_t annex_len, uint8_t sighash_out[32])
ufsecp_error_t ufsecp_schnorr_sign_verified(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t privkey[32], const uint8_t aux_rand[32], uint8_t sig64_out[64])
const char * ufsecp_last_error_msg(const ufsecp_ctx *ctx)
ufsecp_error_t ufsecp_segwit_witness_script_hash(const uint8_t *script, size_t script_len, uint8_t hash_out[32])
ufsecp_error_t ufsecp_ecdh_xonly(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t pubkey33[33], uint8_t secret32_out[32])
static ufsecp_error_t parse_bip32_key(ufsecp_ctx *ctx, const ufsecp_bip32_key *key, secp256k1::ExtendedKey &out)
ufsecp_error_t ufsecp_addr_p2pkh(ufsecp_ctx *ctx, const uint8_t pubkey33[33], int network, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_ecdsa_sign_verified(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t privkey[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_seckey_tweak_add(ufsecp_ctx *ctx, uint8_t privkey[32], const uint8_t tweak[32])
ufsecp_error_t ufsecp_taproot_output_key(ufsecp_ctx *ctx, const uint8_t internal_x[32], const uint8_t *merkle_root, uint8_t output_x_out[32], int *parity_out)
static bool gcs_encode(const std::vector< uint64_t > &values, uint8_t *out, size_t *out_len)
ufsecp_error_t ufsecp_silent_payment_create_output(ufsecp_ctx *ctx, const uint8_t *input_privkeys, size_t n_inputs, const uint8_t scan_pubkey33[33], const uint8_t spend_pubkey33[33], uint32_t k, uint8_t output_pubkey33_out[33], uint8_t *tweak32_out)
ufsecp_error_t ufsecp_addr_p2tr(ufsecp_ctx *ctx, const uint8_t internal_key_x[32], int network, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_frost_sign_nonce_gen(ufsecp_ctx *ctx, uint32_t participant_id, const uint8_t nonce_seed[32], uint8_t nonce_out[UFSECP_FROST_NONCE_LEN], uint8_t nonce_commit_out[UFSECP_FROST_NONCE_COMMIT_LEN])
ufsecp_error_t ufsecp_psbt_sign_legacy(ufsecp_ctx *ctx, const uint8_t sighash32[32], const uint8_t privkey[32], uint8_t sighash_type, uint8_t *sig_out, size_t *sig_len)
ufsecp_error_t ufsecp_ecdsa_sig_to_der(ufsecp_ctx *ctx, const uint8_t sig64[64], uint8_t *der_out, size_t *der_len)
const char * ufsecp_version_string(void)
ufsecp_error_t ufsecp_pedersen_blind_sum(ufsecp_ctx *ctx, const uint8_t *blinds_in, size_t n_in, const uint8_t *blinds_out, size_t n_out, uint8_t sum32_out[32])
ufsecp_error_t ufsecp_sha256(const uint8_t *data, size_t len, uint8_t digest32_out[32])
ufsecp_error_t ufsecp_zk_dleq_verify(ufsecp_ctx *ctx, const uint8_t proof[UFSECP_ZK_DLEQ_PROOF_LEN], const uint8_t G33[33], const uint8_t H33[33], const uint8_t P33[33], const uint8_t Q33[33])
ufsecp_error_t ufsecp_segwit_parse_program(const uint8_t *script, size_t script_len, int *version_out, uint8_t *program_out, size_t *program_len_out)
ufsecp_error_t ufsecp_bip32_derive(ufsecp_ctx *ctx, const ufsecp_bip32_key *parent, uint32_t index, ufsecp_bip32_key *child_out)
ufsecp_error_t ufsecp_schnorr_batch_verify(ufsecp_ctx *ctx, const uint8_t *entries, size_t n)
ufsecp_error_t ufsecp_bip32_derive_path(ufsecp_ctx *ctx, const ufsecp_bip32_key *master, const char *path, ufsecp_bip32_key *key_out)
ufsecp_error_t ufsecp_musig2_nonce_agg(ufsecp_ctx *ctx, const uint8_t *pubnonces, size_t n, uint8_t aggnonce_out[UFSECP_MUSIG2_AGGNONCE_LEN])
ufsecp_error_t ufsecp_schnorr_sign_batch(ufsecp_ctx *ctx, size_t count, const uint8_t *msgs32, const uint8_t *privkeys32, const uint8_t *aux_rands32, uint8_t *sigs64_out)
ufsecp_error_t ufsecp_ecdsa_batch_verify(ufsecp_ctx *ctx, const uint8_t *entries, size_t n)
ufsecp_error_t ufsecp_multi_scalar_mul(ufsecp_ctx *ctx, const uint8_t *scalars, const uint8_t *points, size_t n, uint8_t out33[33])
ufsecp_error_t ufsecp_pubkey_create_uncompressed(ufsecp_ctx *ctx, const uint8_t privkey[32], uint8_t pubkey65_out[65])
ufsecp_error_t ufsecp_zk_knowledge_prove(ufsecp_ctx *ctx, const uint8_t secret[32], const uint8_t pubkey33[33], const uint8_t msg32[32], const uint8_t aux_rand[32], uint8_t proof_out[UFSECP_ZK_KNOWLEDGE_PROOF_LEN])
static void scalar_to_bytes(const Scalar &s, uint8_t out[32])
ufsecp_error_t ufsecp_coin_address(ufsecp_ctx *ctx, const uint8_t pubkey33[33], uint32_t coin_type, int testnet, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_psbt_sign_segwit(ufsecp_ctx *ctx, const uint8_t sighash32[32], const uint8_t privkey[32], uint8_t sighash_type, uint8_t *sig_out, size_t *sig_len)
unsigned int ufsecp_version(void)
ufsecp_error_t ufsecp_frost_aggregate(ufsecp_ctx *ctx, const uint8_t *partial_sigs, size_t n, const uint8_t *nonce_commits, size_t n_signers, const uint8_t group_pubkey33[33], const uint8_t msg32[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_ecdsa_batch_identify_invalid(ufsecp_ctx *ctx, const uint8_t *entries, size_t n, size_t *invalid_out, size_t *invalid_count)
ufsecp_error_t ufsecp_bip144_wtxid(ufsecp_ctx *ctx, const uint8_t *raw_tx, size_t raw_tx_len, uint8_t wtxid_out[32])
ufsecp_error_t ufsecp_seckey_verify(const ufsecp_ctx *ctx, const uint8_t privkey[32])
ufsecp_error_t ufsecp_btc_message_hash(const uint8_t *msg, size_t msg_len, uint8_t digest32_out[32])
ufsecp_error_t ufsecp_pubkey_negate(ufsecp_ctx *ctx, const uint8_t pubkey33[33], uint8_t out33[33])
static void secure_erase_scalar_vector(std::vector< Scalar > &values)
static secp256k1::Network to_network(int n)
ufsecp_error_t ufsecp_ecdsa_adaptor_verify(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN], const uint8_t pubkey33[33], const uint8_t msg32[32], const uint8_t adaptor_point33[33])
ufsecp_error_t ufsecp_coin_wif_encode(ufsecp_ctx *ctx, const uint8_t privkey[32], uint32_t coin_type, int testnet, char *wif_out, size_t *wif_len)
ufsecp_error_t ufsecp_psbt_sign_taproot(ufsecp_ctx *ctx, const uint8_t sighash32[32], const uint8_t privkey[32], uint8_t sighash_type, const uint8_t *aux_rand32, uint8_t *sig_out, size_t *sig_len)
int ufsecp_segwit_is_witness_program(const uint8_t *script, size_t script_len)
ufsecp_error_t ufsecp_pubkey_tweak_mul(ufsecp_ctx *ctx, const uint8_t pubkey33[33], const uint8_t tweak[32], uint8_t out33[33])
ufsecp_error_t ufsecp_btc_message_sign(ufsecp_ctx *ctx, const uint8_t *msg, size_t msg_len, const uint8_t privkey[32], char *base64_out, size_t *base64_len)
ufsecp_error_t ufsecp_zk_range_prove(ufsecp_ctx *ctx, uint64_t value, const uint8_t blinding[32], const uint8_t commitment33[33], const uint8_t aux_rand[32], uint8_t *proof_out, size_t *proof_len)
static constexpr uint64_t GCS_P
ufsecp_error_t ufsecp_bip32_master(ufsecp_ctx *ctx, const uint8_t *seed, size_t seed_len, ufsecp_bip32_key *key_out)
ufsecp_error_t ufsecp_pubkey_create(ufsecp_ctx *ctx, const uint8_t privkey[32], uint8_t pubkey33_out[33])
ufsecp_error_t ufsecp_zk_dleq_prove(ufsecp_ctx *ctx, const uint8_t secret[32], const uint8_t G33[33], const uint8_t H33[33], const uint8_t P33[33], const uint8_t Q33[33], const uint8_t aux_rand[32], uint8_t proof_out[UFSECP_ZK_DLEQ_PROOF_LEN])
ufsecp_error_t ufsecp_bip144_txid(ufsecp_ctx *ctx, const uint8_t *raw_tx, size_t raw_tx_len, uint8_t txid_out[32])
ufsecp_error_t ufsecp_ecdh_raw(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t pubkey33[33], uint8_t secret32_out[32])
ufsecp_error_t ufsecp_pedersen_switch_commit(ufsecp_ctx *ctx, const uint8_t value[32], const uint8_t blinding[32], const uint8_t switch_blind[32], uint8_t commitment33_out[33])
ufsecp_error_t ufsecp_seckey_tweak_mul(ufsecp_ctx *ctx, uint8_t privkey[32], const uint8_t tweak[32])
ufsecp_error_t ufsecp_addr_p2sh(const uint8_t *redeem_script, size_t redeem_script_len, int network, char *addr_out, size_t *addr_len)
static ufsecp_error_t ecdh_parse_args(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t pubkey33[33], Scalar &sk, Point &pk)
ufsecp_error_t ufsecp_musig2_key_agg(ufsecp_ctx *ctx, const uint8_t *pubkeys, size_t n, uint8_t keyagg_out[UFSECP_MUSIG2_KEYAGG_LEN], uint8_t agg_pubkey32_out[32])
ufsecp_error_t ufsecp_bip39_validate(const ufsecp_ctx *ctx, const char *mnemonic)
static secp256k1::TapSighashTxData build_tap_tx_data(uint32_t version, uint32_t locktime, size_t input_count, const uint8_t *prevout_txids_flat, const uint32_t *prevout_vouts, const uint64_t *input_amounts, const uint32_t *input_sequences, const uint8_t *const *input_spks, const size_t *input_spk_lens, size_t output_count, const uint64_t *output_values, const uint8_t *const *output_spks, const size_t *output_spk_lens, std::vector< std::array< uint8_t, 32 > > &txid_storage)
static constexpr uint64_t GCS_M
#define UFSECP_CATCH_RETURN(ctx_ptr)
ufsecp_error_t ufsecp_gcs_match(const uint8_t key[16], const uint8_t *filter, size_t filter_len, size_t n_items, const uint8_t *item, size_t item_len)
ufsecp_error_t ufsecp_btc_message_verify(ufsecp_ctx *ctx, const uint8_t *msg, size_t msg_len, const uint8_t pubkey33[33], const char *base64_sig)
ufsecp_error_t ufsecp_bip322_verify(ufsecp_ctx *ctx, const uint8_t *pubkey, size_t pubkey_len, ufsecp_bip322_addr_type addr_type, const uint8_t *msg, size_t msg_len, const uint8_t *sig, size_t sig_len)
const char * ufsecp_error_str(ufsecp_error_t err)
ufsecp_error_t ufsecp_bip39_generate(ufsecp_ctx *ctx, size_t entropy_bytes, const uint8_t *entropy_in, char *mnemonic_out, size_t *mnemonic_len)
ufsecp_error_t ufsecp_schnorr_adaptor_verify(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_SCHNORR_ADAPTOR_SIG_LEN], const uint8_t pubkey_x[32], const uint8_t msg32[32], const uint8_t adaptor_point33[33])
ufsecp_error_t ufsecp_addr_p2wpkh(ufsecp_ctx *ctx, const uint8_t pubkey33[33], int network, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_ecies_encrypt(ufsecp_ctx *ctx, const uint8_t recipient_pubkey33[33], const uint8_t *plaintext, size_t plaintext_len, uint8_t *envelope_out, size_t *envelope_len)
ufsecp_error_t ufsecp_tagged_hash(const char *tag, const uint8_t *data, size_t len, uint8_t digest32_out[32])
ufsecp_error_t ufsecp_ecdsa_sign_batch(ufsecp_ctx *ctx, size_t count, const uint8_t *msgs32, const uint8_t *privkeys32, uint8_t *sigs64_out)
ufsecp_error_t ufsecp_addr_p2sh_p2wpkh(ufsecp_ctx *ctx, const uint8_t pubkey33[33], int network, char *addr_out, size_t *addr_len)
ufsecp_error_t ufsecp_pubkey_xonly(ufsecp_ctx *ctx, const uint8_t privkey[32], uint8_t xonly32_out[32])
ufsecp_error_t ufsecp_frost_verify_partial(ufsecp_ctx *ctx, const uint8_t partial_sig[36], const uint8_t verification_share33[33], const uint8_t *nonce_commits, size_t n_signers, const uint8_t msg32[32], const uint8_t group_pubkey33[33])
ufsecp_error_t ufsecp_schnorr_adaptor_sign(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t msg32[32], const uint8_t adaptor_point33[33], const uint8_t aux_rand[32], uint8_t pre_sig_out[UFSECP_SCHNORR_ADAPTOR_SIG_LEN])
ufsecp_error_t ufsecp_zk_range_verify(ufsecp_ctx *ctx, const uint8_t commitment33[33], const uint8_t *proof, size_t proof_len)
ufsecp_error_t ufsecp_ecdsa_recover(ufsecp_ctx *ctx, const uint8_t msg32[32], const uint8_t sig64[64], int recid, uint8_t pubkey33_out[33])
ufsecp_error_t ufsecp_segwit_p2tr_spk(const uint8_t output_key[32], uint8_t spk_out[34])
ufsecp_error_t ufsecp_ctx_clone(const ufsecp_ctx *src, ufsecp_ctx **ctx_out)
ufsecp_error_t ufsecp_pedersen_verify_sum(ufsecp_ctx *ctx, const uint8_t *pos, size_t n_pos, const uint8_t *neg, size_t n_neg)
ufsecp_error_t ufsecp_taproot_verify(ufsecp_ctx *ctx, const uint8_t output_x[32], int output_parity, const uint8_t internal_x[32], const uint8_t *merkle_root, size_t merkle_root_len)
static bool gcs_decode(const uint8_t *filter, size_t filter_len, size_t n_items, std::vector< uint64_t > &out)
ufsecp_error_t ufsecp_frost_sign(ufsecp_ctx *ctx, const uint8_t keypkg[UFSECP_FROST_KEYPKG_LEN], const uint8_t nonce[UFSECP_FROST_NONCE_LEN], const uint8_t msg32[32], const uint8_t *nonce_commits, size_t n_signers, uint8_t partial_sig_out[36])
Sign a FROST round-2 partial signature.
ufsecp_error_t ufsecp_segwit_p2wpkh_spk(const uint8_t pubkey_hash[20], uint8_t spk_out[22])
ufsecp_error_t ufsecp_segwit_p2wsh_spk(const uint8_t script_hash[32], uint8_t spk_out[34])
unsigned int ufsecp_abi_version(void)
ufsecp_error_t ufsecp_ecdsa_sig_from_der(ufsecp_ctx *ctx, const uint8_t *der, size_t der_len, uint8_t sig64_out[64])
static secp256k1::ExtendedKey extkey_from_uf(const ufsecp_bip32_key *k)
ufsecp_error_t ufsecp_shamir_trick(ufsecp_ctx *ctx, const uint8_t a[32], const uint8_t P33[33], const uint8_t b[32], const uint8_t Q33[33], uint8_t out33[33])
ufsecp_error_t ufsecp_zk_knowledge_verify(ufsecp_ctx *ctx, const uint8_t proof[UFSECP_ZK_KNOWLEDGE_PROOF_LEN], const uint8_t pubkey33[33], const uint8_t msg32[32])
ufsecp_error_t ufsecp_ecdsa_adaptor_adapt(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN], const uint8_t adaptor_secret[32], uint8_t sig64_out[64])
ufsecp_error_t ufsecp_ecdsa_adaptor_extract(ufsecp_ctx *ctx, const uint8_t pre_sig[UFSECP_ECDSA_ADAPTOR_SIG_LEN], const uint8_t sig64[64], uint8_t secret32_out[32])
ufsecp_error_t ufsecp_schnorr_sign_msg(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t *msg, size_t msg_len, const uint8_t *aux_rand32, uint8_t sig64_out[64])
static Point point_from_compressed(const uint8_t pub[33])
ufsecp_error_t ufsecp_wif_decode(ufsecp_ctx *ctx, const char *wif, uint8_t privkey32_out[32], int *compressed_out, int *network_out)
static const secp256k1::coins::CoinParams * find_coin(uint32_t coin_type)
ufsecp_error_t ufsecp_wif_encode(ufsecp_ctx *ctx, const uint8_t privkey[32], int compressed, int network, char *wif_out, size_t *wif_len)
ufsecp_error_t ufsecp_gcs_build(const uint8_t key[16], const uint8_t **data, const size_t *data_sizes, size_t count, uint8_t *filter_out, size_t *filter_len)
ufsecp_error_t ufsecp_ecdsa_adaptor_sign(ufsecp_ctx *ctx, const uint8_t privkey[32], const uint8_t msg32[32], const uint8_t adaptor_point33[33], uint8_t pre_sig_out[UFSECP_ECDSA_ADAPTOR_SIG_LEN])
static ufsecp_error_t ctx_set_err(ufsecp_ctx *ctx, ufsecp_error_t err, const char *msg)
ufsecp_error_t ufsecp_frost_keygen_begin(ufsecp_ctx *ctx, uint32_t participant_id, uint32_t threshold, uint32_t num_participants, const uint8_t seed[32], uint8_t *commits_out, size_t *commits_len, uint8_t *shares_out, size_t *shares_len)
ufsecp_error_t ufsecp_bip32_pubkey(ufsecp_ctx *ctx, const ufsecp_bip32_key *key, uint8_t pubkey33_out[33])
ufsecp_error_t ufsecp_pubkey_parse(ufsecp_ctx *ctx, const uint8_t *input, size_t input_len, uint8_t pubkey33_out[33])
ufsecp_error_t ufsecp_frost_keygen_finalize(ufsecp_ctx *ctx, uint32_t participant_id, const uint8_t *all_commits, size_t commits_len, const uint8_t *received_shares, size_t shares_len, uint32_t threshold, uint32_t num_participants, uint8_t keypkg_out[UFSECP_FROST_KEYPKG_LEN])
ufsecp_error_t ufsecp_schnorr_batch_identify_invalid(ufsecp_ctx *ctx, const uint8_t *entries, size_t n, size_t *invalid_out, size_t *invalid_count)
ufsecp_error_t ufsecp_ctx_create(ufsecp_ctx **ctx_out)
ufsecp_error_t ufsecp_bip39_to_entropy(ufsecp_ctx *ctx, const char *mnemonic, uint8_t *entropy_out, size_t *entropy_len)
static size_t read_compact_size(const uint8_t *buf, size_t len, size_t &offset, uint64_t &val)
#define UFSECP_VERSION_PACKED
#define UFSECP_VERSION_STRING
#define UFSECP_ABI_VERSION