feat: complete all 12 binding APIs + 9 READMEs + fix package naming

Bindings:
- Java: +22 JNI functions + 3 helper classes (RecoverableSignature, WifDecoded, TaprootOutputKeyResult)
- Swift: +20 functions (DER, recovery, ECDH, tagged_hash, BIP-32, taproot)
- React Native: +15 functions
- Python: +3 functions (ctx_clone, last_error, last_error_msg)
- Rust: +2 functions (last_error, last_error_msg)
- Dart: +1 function (ctx_clone)

Documentation:
- 9 new binding READMEs: c_api, dart, go, java, php, python, ruby, rust, swift
- 3 existing READMEs fixed: nodejs, csharp, react-native (CT/fast architecture note)
- Fix incorrect package names across all docs:
  libsecp256k1-fast* -> libufsecp* (apt, rpm, arch, pkg-config, CMake)
  secp256k1-fast-cpu -> fastsecp256k1 (linker flags, CMake targets)
- Fix INDUSTRIAL_ROADMAP_WORKING.md link -> ROADMAP.md in README
- Rename RPM spec: libsecp256k1-fast.spec -> libufsecp.spec
- Fix debian/control, debian/changelog, arch/PKGBUILD package names
- Fix secp256k1-fast.pc.in linker flag
- Fix .github/workflows/packaging.yml comment

Selftest:
- Add selftest report structs (selftest.hpp)
- Refactor tally() in selftest.cpp
This commit is contained in:
vano 2026-02-25 00:04:31 +04:00
parent ab13fddb7a
commit 03c1263cdb
35 changed files with 1561 additions and 105 deletions

View File

@ -7,7 +7,7 @@
#
# curl -fsSL https://shrec.github.io/UltrafastSecp256k1/KEY.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/ultrafastsecp256k1.gpg
# echo "deb [signed-by=/etc/apt/keyrings/ultrafastsecp256k1.gpg] https://shrec.github.io/UltrafastSecp256k1/apt stable main" | sudo tee /etc/apt/sources.list.d/ultrafastsecp256k1.list
# sudo apt update && sudo apt install libsecp256k1-fast-dev
# sudo apt update && sudo apt install libufsecp-dev
#
# ============================================================================

View File

@ -122,7 +122,7 @@ Get a working selftest in under a minute:
**Option A — Linux (apt)**
```bash
sudo apt install libsecp256k1-fast3
sudo apt install libufsecp3
ufsecp_selftest # Expected: "OK (version 3.x, backend CPU)"
```
@ -151,7 +151,7 @@ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build -
| Target | Backend | Install / Entry Point | Status |
|--------|---------|----------------------|--------|
| **Linux x64** | CPU | `apt install libsecp256k1-fast3` | ✅ Stable |
| **Linux x64** | CPU | `apt install libufsecp3` | ✅ Stable |
| **Windows x64** | CPU | NuGet `UltrafastSecp256k1` / [Release .zip](https://github.com/shrec/UltrafastSecp256k1/releases) | ✅ Stable |
| **macOS (x64/ARM64)** | CPU + Metal | `brew install ufsecp` / build from source | ✅ Stable |
| **Android ARM64** | CPU | `implementation 'io.github.shrec:ufsecp'` (Maven) | ✅ Stable |
@ -179,10 +179,10 @@ echo "deb [signed-by=/etc/apt/keyrings/ultrafastsecp256k1.gpg] https://shrec.git
sudo apt update
# Install (runtime only)
sudo apt install libsecp256k1-fast3
sudo apt install libufsecp3
# Install (development — headers, static lib, cmake/pkgconfig)
sudo apt install libsecp256k1-fast-dev
sudo apt install libufsecp-dev
```
### Linux (RPM — Fedora / RHEL)
@ -197,11 +197,11 @@ sudo dnf install ./UltrafastSecp256k1-*.rpm
```bash
# Using yay
yay -S libsecp256k1-fast
yay -S libufsecp
# Or manually
git clone https://aur.archlinux.org/libsecp256k1-fast.git
cd libsecp256k1-fast && makepkg -si
git clone https://aur.archlinux.org/libufsecp.git
cd libufsecp && makepkg -si
```
### From source (any platform)
@ -221,14 +221,14 @@ sudo ldconfig
### Use in your CMake project
```cmake
find_package(secp256k1-fast 3 REQUIRED COMPONENTS CPU)
target_link_libraries(myapp PRIVATE secp256k1::fastsecp256k1)
find_package(ufsecp 3 REQUIRED)
target_link_libraries(myapp PRIVATE ufsecp::ufsecp)
```
### Use with pkg-config
```bash
g++ myapp.cpp $(pkg-config --cflags --libs secp256k1-fast) -o myapp
g++ myapp.cpp $(pkg-config --cflags --libs ufsecp) -o myapp
```
---
@ -348,7 +348,7 @@ See [THREAT_MODEL.md](THREAT_MODEL.md) for a full layer-by-layer risk assessment
| **No secret-dependent branches** | All `ct::` functions | ✅ Enforced by design, verified via Clang-Tidy checks |
| **No secret-dependent memory access** | All `ct::` table lookups use constant-index cmov | ✅ |
| **ASan + UBSan CI** | Every push — catches undefined behavior in CT paths | ✅ CI |
| **Timing tests (dudect)** | CPU field/scalar ops | 🔜 Planned (see [roadmap](INDUSTRIAL_ROADMAP_WORKING.md)) |
| **Timing tests (dudect)** | CPU field/scalar ops | 🔜 Planned (see [roadmap](ROADMAP.md)) |
| **Formal CT verification** | Fiat-Crypto style | 🔜 Planned |
**Assumptions:** CT guarantees depend on compiler not introducing secret-dependent branches during optimization. Builds use `-O2` with Clang; MSVC may require additional flags. Micro-architectural side channels (Spectre, power analysis) are outside current scope — see [THREAT_MODEL.md](THREAT_MODEL.md).
@ -654,7 +654,7 @@ int main() {
```
```bash
g++ -std=c++20 example.cpp -lsecp256k1-fast-cpu -o example && ./example
g++ -std=c++20 example.cpp -lufsecp -o example && ./example
```
### GPU Batch Multiplication

53
bindings/c_api/README.md Normal file
View File

@ -0,0 +1,53 @@
# ultrafast_secp256k1 — C API
Standalone C header-only API for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
This is a **stateless** API with `secp256k1_*` naming (no context object). It differs from the main `ufsecp_*` context-based API.
## Features
- **ECDSA** — sign, verify, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — shared secret
- **BIP-32** — HD key derivation
- **Taproot** — output key tweaking, commitment verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256, HASH160
## Quick Start
```c
#include "ultrafast_secp256k1.h"
secp256k1_init();
uint8_t privkey[32] = {0};
privkey[31] = 1;
uint8_t pubkey[33];
secp256k1_ec_pubkey_create(privkey, pubkey);
uint8_t msg[32] = {0};
uint8_t sig[64];
secp256k1_ecdsa_sign(msg, privkey, sig);
int ok = secp256k1_ecdsa_verify(msg, sig, pubkey);
```
## API Differences
| Feature | `ufsecp_*` (main) | `secp256k1_*` (this) |
|---------|-------------------|----------------------|
| Context | Required | None (global init) |
| Init | `ufsecp_ctx_create()` | `secp256k1_init()` |
| Naming | `ufsecp_ecdsa_sign` | `secp256k1_ecdsa_sign` |
| Thread safety | Per-context | Global state |
## Architecture Note
Both APIs use the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -117,9 +117,9 @@ byte[] tagged = Ufsecp.TaggedHash("BIP0340/aux", data); // BIP-340 tagged hash
Keys, ECDSA (sign/verify/recover/DER), Schnorr BIP-340, ECDH (compressed/xonly/raw), SHA-256, HASH160, Tagged Hash, BIP-32 HD, Taproot (BIP-341), Bitcoin Addresses (P2PKH/P2WPKH/P2TR), WIF, Key Tweaking.
## Constant-Time Architecture
## Architecture Note
All secret-key operations (signing, ECDH, key derivation) automatically use the constant-time layer — no flags or opt-in required.
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## Supported Platforms

54
bindings/dart/README.md Normal file
View File

@ -0,0 +1,54 @@
# ufsecp — Dart
Dart FFI binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Quick Start
```dart
import 'package:ufsecp/ufsecp.dart';
final ctx = UfsecpContext();
final privkey = Uint8List(32)..[31] = 1;
final pubkey = ctx.pubkeyCreate(privkey);
final msgHash = UfsecpContext.sha256(utf8.encode('hello'));
final sig = ctx.ecdsaSign(msgHash, privkey);
final valid = ctx.ecdsaVerify(msgHash, sig, pubkey);
ctx.destroy();
```
## ECDSA Recovery
```dart
final rs = ctx.ecdsaSignRecoverable(msgHash, privkey);
final recovered = ctx.ecdsaRecover(msgHash, rs.signature, rs.recoveryId);
```
## Taproot (BIP-341)
```dart
final tok = ctx.taprootOutputKey(xonlyPub);
final tweaked = ctx.taprootTweakSeckey(privkey);
final tapValid = ctx.taprootVerify(tok.outputKeyX, tok.parity, xonlyPub);
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -81,6 +81,8 @@ typedef _CtxCreateC = ffi.Int32 Function(ffi.Pointer<ffi.Pointer<ffi.Void>>);
typedef _CtxCreateDart = int Function(ffi.Pointer<ffi.Pointer<ffi.Void>>);
typedef _CtxDestroyC = ffi.Void Function(ffi.Pointer<ffi.Void>);
typedef _CtxDestroyDart = void Function(ffi.Pointer<ffi.Void>);
typedef _CtxCloneC = ffi.Int32 Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Pointer<ffi.Void>>);
typedef _CtxCloneDart = int Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Pointer<ffi.Void>>);
// Version
typedef _VersionC = ffi.Uint32 Function();
@ -183,6 +185,7 @@ class UfsecpContext {
// cached lookups
late final _CtxDestroyDart _ctxDestroy;
late final _CtxCloneDart _ctxClone;
late final _LastErrorDart _lastError;
late final _LastErrorMsgDart _lastErrorMsg;
late final _VersionDart _version;
@ -258,6 +261,23 @@ class UfsecpContext {
}
}
/// Deep-copy this context into a new independent context.
UfsecpContext._fromPointer(this._lib, this._ctx) {
_bindAll();
}
UfsecpContext cloneCtx() {
_ensureAlive();
final pp = calloc<ffi.Pointer<ffi.Void>>();
try {
final rc = _ctxClone(_ctx, pp);
if (rc != 0) throw UfsecpException('ctx_clone', UfsecpError.fromCode(rc));
return UfsecpContext._fromPointer(_lib, pp.value);
} finally {
calloc.free(pp);
}
}
void _ensureAlive() {
if (_destroyed) throw StateError('UfsecpContext already destroyed');
}
@ -854,6 +874,7 @@ class UfsecpContext {
void _bindAll() {
_ctxDestroy = _lib.lookupFunction<_CtxDestroyC, _CtxDestroyDart>('ufsecp_ctx_destroy');
_ctxClone = _lib.lookupFunction<_CtxCloneC, _CtxCloneDart>('ufsecp_ctx_clone');
_lastError = _lib.lookupFunction<_LastErrorC, _LastErrorDart>('ufsecp_last_error');
_lastErrorMsg = _lib.lookupFunction<_LastErrorMsgC, _LastErrorMsgDart>('ufsecp_last_error_msg');
_version = _lib.lookupFunction<_VersionC, _VersionDart>('ufsecp_version');

56
bindings/go/README.md Normal file
View File

@ -0,0 +1,56 @@
# ufsecp — Go
Go (CGo) binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Quick Start
```go
import "github.com/shrec/UltrafastSecp256k1/bindings/go"
ctx, err := ufsecp.NewContext()
if err != nil { panic(err) }
defer ctx.Destroy()
privkey := make([]byte, 32)
privkey[31] = 1
pubkey, err := ctx.PubkeyCreate(privkey)
msgHash, _ := ufsecp.Sha256([]byte("hello"))
sig, err := ctx.EcdsaSign(msgHash, privkey)
valid, err := ctx.EcdsaVerify(msgHash, sig, pubkey)
```
## ECDSA Recovery
```go
sig, recid, err := ctx.EcdsaSignRecoverable(msgHash, privkey)
recovered, err := ctx.EcdsaRecover(msgHash, sig, recid)
```
## Taproot (BIP-341)
```go
outputKey, parity, err := ctx.TaprootOutputKey(xonlyPub, nil)
tweaked, err := ctx.TaprootTweakSeckey(privkey, nil)
valid, err := ctx.TaprootVerify(outputKey, parity, xonlyPub, nil)
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

71
bindings/java/README.md Normal file
View File

@ -0,0 +1,71 @@
# ufsecp — Java
Java binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography via JNI.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Quick Start
```java
import com.ultrafast.ufsecp.Ufsecp;
try (Ufsecp ctx = Ufsecp.create()) {
byte[] privkey = new byte[32];
privkey[31] = 1;
byte[] pubkey = ctx.pubkeyCreate(privkey);
byte[] msgHash = Ufsecp.sha256("hello".getBytes());
byte[] sig = ctx.ecdsaSign(msgHash, privkey);
boolean valid = ctx.ecdsaVerify(msgHash, sig, pubkey);
}
```
## ECDSA Recovery
```java
RecoverableSignature rs = ctx.ecdsaSignRecoverable(msgHash, privkey);
byte[] recovered = ctx.ecdsaRecover(msgHash, rs.getSignature(), rs.getRecid());
```
## Schnorr (BIP-340)
```java
byte[] xonlyPub = ctx.pubkeyXonly(privkey);
byte[] schnorrSig = ctx.schnorrSign(msgHash, privkey, auxRand);
boolean ok = ctx.schnorrVerify(msgHash, schnorrSig, xonlyPub);
```
## BIP-32 HD Derivation
```java
byte[] master = ctx.bip32Master(seed);
byte[] child = ctx.bip32DerivePath(master, "m/44'/0'/0'/0/0");
byte[] childPriv = ctx.bip32Privkey(child);
byte[] childPub = ctx.bip32Pubkey(child);
```
## Taproot (BIP-341)
```java
TaprootOutputKeyResult tok = ctx.taprootOutputKey(xonlyPub, null);
byte[] tweakedPriv = ctx.taprootTweakSeckey(privkey, null);
boolean tapValid = ctx.taprootVerify(tok.getOutputKey(), tok.getParity(), xonlyPub, null);
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -280,7 +280,301 @@ JNIEXPORT jint JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeVersion(JNIEnv *en
return (jint)ufsecp_version();
}
JNIEXPORT jint JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeAbiVersion(JNIEnv *env, jclass clz) {
(void)env; (void)clz;
return (jint)ufsecp_abi_version();
}
JNIEXPORT jstring JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeVersionString(JNIEnv *env, jclass clz) {
(void)clz;
return (*env)->NewStringUTF(env, ufsecp_version_string());
}
/* ── Context extras ──────────────────────────────────────────────────── */
JNIEXPORT jlong JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeClone(JNIEnv *env, jclass clz, jlong ptr) {
(void)clz;
ufsecp_ctx *clone = NULL;
int rc = ufsecp_ctx_clone((const ufsecp_ctx*)(uintptr_t)ptr, &clone);
if (throw_on_err(env, rc, "ctx_clone")) return 0;
return (jlong)(uintptr_t)clone;
}
JNIEXPORT jint JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeLastError(JNIEnv *env, jclass clz, jlong ptr) {
(void)env; (void)clz;
return (jint)ufsecp_last_error((const ufsecp_ctx*)(uintptr_t)ptr);
}
JNIEXPORT jstring JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeLastErrorMsg(JNIEnv *env, jclass clz, jlong ptr) {
(void)clz;
return (*env)->NewStringUTF(env, ufsecp_last_error_msg((const ufsecp_ctx*)(uintptr_t)ptr));
}
/* ── Key ops extras ──────────────────────────────────────────────────── */
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeSeckeyNegate(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey) {
(void)clz;
jbyte *pk = pin(env, privkey);
uint8_t buf[32];
memcpy(buf, pk, 32);
unpin(env, privkey, pk);
int rc = ufsecp_seckey_negate((ufsecp_ctx*)(uintptr_t)ctx, buf);
if (throw_on_err(env, rc, "seckey_negate")) return NULL;
return mk(env, buf, 32);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeSeckeyTweakAdd(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey, jbyteArray tweak) {
(void)clz;
jbyte *pk = pin(env, privkey);
jbyte *tw = pin(env, tweak);
uint8_t buf[32];
memcpy(buf, pk, 32);
int rc = ufsecp_seckey_tweak_add((ufsecp_ctx*)(uintptr_t)ctx, buf, (const uint8_t*)tw);
unpin(env, tweak, tw);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "seckey_tweak_add")) return NULL;
return mk(env, buf, 32);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeSeckeyTweakMul(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey, jbyteArray tweak) {
(void)clz;
jbyte *pk = pin(env, privkey);
jbyte *tw = pin(env, tweak);
uint8_t buf[32];
memcpy(buf, pk, 32);
int rc = ufsecp_seckey_tweak_mul((ufsecp_ctx*)(uintptr_t)ctx, buf, (const uint8_t*)tw);
unpin(env, tweak, tw);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "seckey_tweak_mul")) return NULL;
return mk(env, buf, 32);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativePubkeyParse(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray pubkey) {
(void)clz;
jbyte *pk = pin(env, pubkey);
jsize len = (*env)->GetArrayLength(env, pubkey);
uint8_t out[33];
int rc = ufsecp_pubkey_parse((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)pk, (size_t)len, out);
unpin(env, pubkey, pk);
if (throw_on_err(env, rc, "pubkey_parse")) return NULL;
return mk(env, out, 33);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativePubkeyXonly(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey) {
(void)clz;
jbyte *pk = pin(env, privkey);
uint8_t out[32];
int rc = ufsecp_pubkey_xonly((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)pk, out);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "pubkey_xonly")) return NULL;
return mk(env, out, 32);
}
/* ── ECDSA extras ────────────────────────────────────────────────────── */
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdsaSigToDer(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray sig) {
(void)clz;
jbyte *s = pin(env, sig);
uint8_t der[72];
size_t dlen = 72;
int rc = ufsecp_ecdsa_sig_to_der((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)s, der, &dlen);
unpin(env, sig, s);
if (throw_on_err(env, rc, "ecdsa_sig_to_der")) return NULL;
return mk(env, der, (int)dlen);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdsaSigFromDer(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray der) {
(void)clz;
jbyte *d = pin(env, der);
jsize dlen = (*env)->GetArrayLength(env, der);
uint8_t sig[64];
int rc = ufsecp_ecdsa_sig_from_der((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)d, (size_t)dlen, sig);
unpin(env, der, d);
if (throw_on_err(env, rc, "ecdsa_sig_from_der")) return NULL;
return mk(env, sig, 64);
}
JNIEXPORT jobject JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdsaSignRecoverable(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray msgHash, jbyteArray privkey) {
(void)clz;
jbyte *msg = pin(env, msgHash);
jbyte *pk = pin(env, privkey);
uint8_t sig[64];
int recid = 0;
int rc = ufsecp_ecdsa_sign_recoverable((ufsecp_ctx*)(uintptr_t)ctx,
(const uint8_t*)msg, (const uint8_t*)pk, sig, &recid);
unpin(env, privkey, pk);
unpin(env, msgHash, msg);
if (throw_on_err(env, rc, "ecdsa_sign_recoverable")) return NULL;
jbyteArray sigArr = mk(env, sig, 64);
jclass cls = (*env)->FindClass(env, "com/ultrafast/ufsecp/RecoverableSignature");
jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BI)V");
return (*env)->NewObject(env, cls, ctor, sigArr, (jint)recid);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdsaRecover(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray msgHash, jbyteArray sig, jint recid) {
(void)clz;
jbyte *msg = pin(env, msgHash);
jbyte *s = pin(env, sig);
uint8_t pub[33];
int rc = ufsecp_ecdsa_recover((ufsecp_ctx*)(uintptr_t)ctx,
(const uint8_t*)msg, (const uint8_t*)s, (int)recid, pub);
unpin(env, sig, s);
unpin(env, msgHash, msg);
if (throw_on_err(env, rc, "ecdsa_recover")) return NULL;
return mk(env, pub, 33);
}
/* ── ECDH extras ─────────────────────────────────────────────────────── */
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdhXonly(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey, jbyteArray pubkey) {
(void)clz;
jbyte *pk = pin(env, privkey);
jbyte *pub = pin(env, pubkey);
uint8_t out[32];
int rc = ufsecp_ecdh_xonly((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)pk, (const uint8_t*)pub, out);
unpin(env, pubkey, pub);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "ecdh_xonly")) return NULL;
return mk(env, out, 32);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeEcdhRaw(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey, jbyteArray pubkey) {
(void)clz;
jbyte *pk = pin(env, privkey);
jbyte *pub = pin(env, pubkey);
uint8_t out[32];
int rc = ufsecp_ecdh_raw((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)pk, (const uint8_t*)pub, out);
unpin(env, pubkey, pub);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "ecdh_raw")) return NULL;
return mk(env, out, 32);
}
/* ── Hash extras ─────────────────────────────────────────────────────── */
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeTaggedHash(
JNIEnv *env, jclass clz, jbyteArray tag, jbyteArray data) {
(void)clz;
const char *t = (const char*)(*env)->GetByteArrayElements(env, tag, NULL);
jsize tlen = (*env)->GetArrayLength(env, tag);
/* null-terminate the tag */
char tbuf[256];
if (tlen >= (jsize)sizeof(tbuf)) tlen = (jsize)sizeof(tbuf) - 1;
memcpy(tbuf, t, tlen);
tbuf[tlen] = '\0';
(*env)->ReleaseByteArrayElements(env, tag, (jbyte*)t, JNI_ABORT);
jbyte *d = pin(env, data);
jsize dlen = (*env)->GetArrayLength(env, data);
uint8_t out[32];
int rc = ufsecp_tagged_hash(tbuf, (const uint8_t*)d, (size_t)dlen, out);
unpin(env, data, d);
if (throw_on_err(env, rc, "tagged_hash")) return NULL;
return mk(env, out, 32);
}
/* ── WIF extras ──────────────────────────────────────────────────────── */
JNIEXPORT jobject JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeWifDecode(
JNIEnv *env, jclass clz, jlong ctx, jstring wif) {
(void)clz;
const char *w = (*env)->GetStringUTFChars(env, wif, NULL);
uint8_t privkey[32];
int comp = 0, net = 0;
int rc = ufsecp_wif_decode((ufsecp_ctx*)(uintptr_t)ctx, w, privkey, &comp, &net);
(*env)->ReleaseStringUTFChars(env, wif, w);
if (throw_on_err(env, rc, "wif_decode")) return NULL;
jbyteArray keyArr = mk(env, privkey, 32);
jclass cls = (*env)->FindClass(env, "com/ultrafast/ufsecp/WifDecoded");
jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BZI)V");
return (*env)->NewObject(env, cls, ctor, keyArr, (jboolean)(comp == 1), (jint)net);
}
/* ── BIP-32 extras ───────────────────────────────────────────────────── */
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeBip32Privkey(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray key) {
(void)clz;
jbyte *k = pin(env, key);
uint8_t priv[32];
int rc = ufsecp_bip32_privkey((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)k, priv);
unpin(env, key, k);
if (throw_on_err(env, rc, "bip32_privkey")) return NULL;
return mk(env, priv, 32);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeBip32Pubkey(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray key) {
(void)clz;
jbyte *k = pin(env, key);
uint8_t pub[33];
int rc = ufsecp_bip32_pubkey((ufsecp_ctx*)(uintptr_t)ctx, (const uint8_t*)k, pub);
unpin(env, key, k);
if (throw_on_err(env, rc, "bip32_pubkey")) return NULL;
return mk(env, pub, 33);
}
/* ── Taproot ─────────────────────────────────────────────────────────── */
JNIEXPORT jobject JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeTaprootOutputKey(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray internalX, jbyteArray merkleRoot) {
(void)clz;
jbyte *ix = pin(env, internalX);
jbyte *mr = merkleRoot ? pin(env, merkleRoot) : NULL;
uint8_t outx[32];
int parity = 0;
int rc = ufsecp_taproot_output_key((ufsecp_ctx*)(uintptr_t)ctx,
(const uint8_t*)ix, mr ? (const uint8_t*)mr : NULL, outx, &parity);
if (mr) unpin(env, merkleRoot, mr);
unpin(env, internalX, ix);
if (throw_on_err(env, rc, "taproot_output_key")) return NULL;
jbyteArray outArr = mk(env, outx, 32);
jclass cls = (*env)->FindClass(env, "com/ultrafast/ufsecp/TaprootOutputKeyResult");
jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BI)V");
return (*env)->NewObject(env, cls, ctor, outArr, (jint)parity);
}
JNIEXPORT jbyteArray JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeTaprootTweakSeckey(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray privkey, jbyteArray merkleRoot) {
(void)clz;
jbyte *pk = pin(env, privkey);
jbyte *mr = merkleRoot ? pin(env, merkleRoot) : NULL;
uint8_t out[32];
int rc = ufsecp_taproot_tweak_seckey((ufsecp_ctx*)(uintptr_t)ctx,
(const uint8_t*)pk, mr ? (const uint8_t*)mr : NULL, out);
if (mr) unpin(env, merkleRoot, mr);
unpin(env, privkey, pk);
if (throw_on_err(env, rc, "taproot_tweak_seckey")) return NULL;
return mk(env, out, 32);
}
JNIEXPORT jboolean JNICALL Java_com_ultrafast_ufsecp_Ufsecp_nativeTaprootVerify(
JNIEnv *env, jclass clz, jlong ctx, jbyteArray outputX, jint parity,
jbyteArray internalX, jbyteArray merkleRoot) {
(void)clz;
jbyte *ox = pin(env, outputX);
jbyte *ix = pin(env, internalX);
jbyte *mr = merkleRoot ? pin(env, merkleRoot) : NULL;
size_t mrLen = merkleRoot ? (size_t)(*env)->GetArrayLength(env, merkleRoot) : 0;
int rc = ufsecp_taproot_verify((ufsecp_ctx*)(uintptr_t)ctx,
(const uint8_t*)ox, (int)parity, (const uint8_t*)ix,
mr ? (const uint8_t*)mr : NULL, mrLen);
if (mr) unpin(env, merkleRoot, mr);
unpin(env, internalX, ix);
unpin(env, outputX, ox);
return rc == 0 ? JNI_TRUE : JNI_FALSE;
}

View File

@ -0,0 +1,15 @@
package com.ultrafast.ufsecp;
/** ECDSA recoverable signature: 64-byte compact sig + recovery id (0-3). */
public final class RecoverableSignature {
private final byte[] signature;
private final int recid;
public RecoverableSignature(byte[] signature, int recid) {
this.signature = signature;
this.recid = recid;
}
public byte[] getSignature() { return signature; }
public int getRecid() { return recid; }
}

View File

@ -0,0 +1,15 @@
package com.ultrafast.ufsecp;
/** Taproot output key: 32-byte x-only public key + parity flag. */
public final class TaprootOutputKeyResult {
private final byte[] outputKey;
private final int parity;
public TaprootOutputKeyResult(byte[] outputKey, int parity) {
this.outputKey = outputKey;
this.parity = parity;
}
public byte[] getOutputKey() { return outputKey; }
public int getParity() { return parity; }
}

View File

@ -50,8 +50,26 @@ public final class Ufsecp implements AutoCloseable {
// Version
public static int version() { return nativeVersion(); }
public static int abiVersion() { return nativeAbiVersion(); }
public static String versionString(){ return nativeVersionString(); }
// Context extras
public Ufsecp clone_() {
alive();
return new Ufsecp(nativeClone(ptr));
}
public int lastError() {
alive();
return nativeLastError(ptr);
}
public String lastErrorMsg() {
alive();
return nativeLastErrorMsg(ptr);
}
// Key operations
public byte[] pubkeyCreate(byte[] privkey) {
@ -64,11 +82,36 @@ public final class Ufsecp implements AutoCloseable {
return nativePubkeyCreateUncompressed(ptr, privkey);
}
public byte[] pubkeyParse(byte[] pubkey) {
alive();
return nativePubkeyParse(ptr, pubkey);
}
public byte[] pubkeyXonly(byte[] privkey) {
alive();
return nativePubkeyXonly(ptr, privkey);
}
public boolean seckeyVerify(byte[] privkey) {
alive();
return nativeSeckeyVerify(ptr, privkey);
}
public byte[] seckeyNegate(byte[] privkey) {
alive();
return nativeSeckeyNegate(ptr, privkey);
}
public byte[] seckeyTweakAdd(byte[] privkey, byte[] tweak) {
alive();
return nativeSeckeyTweakAdd(ptr, privkey, tweak);
}
public byte[] seckeyTweakMul(byte[] privkey, byte[] tweak) {
alive();
return nativeSeckeyTweakMul(ptr, privkey, tweak);
}
// ECDSA
public byte[] ecdsaSign(byte[] msgHash, byte[] privkey) {
@ -81,6 +124,26 @@ public final class Ufsecp implements AutoCloseable {
return nativeEcdsaVerify(ptr, msgHash, sig, pubkey);
}
public byte[] ecdsaSigToDer(byte[] sig) {
alive();
return nativeEcdsaSigToDer(ptr, sig);
}
public byte[] ecdsaSigFromDer(byte[] der) {
alive();
return nativeEcdsaSigFromDer(ptr, der);
}
public RecoverableSignature ecdsaSignRecoverable(byte[] msgHash, byte[] privkey) {
alive();
return nativeEcdsaSignRecoverable(ptr, msgHash, privkey);
}
public byte[] ecdsaRecover(byte[] msgHash, byte[] sig, int recid) {
alive();
return nativeEcdsaRecover(ptr, msgHash, sig, recid);
}
// Schnorr
public byte[] schnorrSign(byte[] msg, byte[] privkey, byte[] auxRand) {
@ -100,10 +163,21 @@ public final class Ufsecp implements AutoCloseable {
return nativeEcdh(ptr, privkey, pubkey);
}
public byte[] ecdhXonly(byte[] privkey, byte[] pubkey) {
alive();
return nativeEcdhXonly(ptr, privkey, pubkey);
}
public byte[] ecdhRaw(byte[] privkey, byte[] pubkey) {
alive();
return nativeEcdhRaw(ptr, privkey, pubkey);
}
// Hashing
public static byte[] sha256(byte[] data) { return nativeSha256(data); }
public static byte[] hash160(byte[] data) { return nativeHash160(data); }
public static byte[] taggedHash(byte[] tag, byte[] data) { return nativeTaggedHash(tag, data); }
// Addresses
@ -129,6 +203,11 @@ public final class Ufsecp implements AutoCloseable {
return nativeWifEncode(ptr, privkey, compressed, network);
}
public WifDecoded wifDecode(String wif) {
alive();
return nativeWifDecode(ptr, wif);
}
// BIP-32
public byte[] bip32Master(byte[] seed) {
@ -146,36 +225,86 @@ public final class Ufsecp implements AutoCloseable {
return nativeBip32DerivePath(ptr, master, path);
}
public byte[] bip32Privkey(byte[] key) {
alive();
return nativeBip32Privkey(ptr, key);
}
public byte[] bip32Pubkey(byte[] key) {
alive();
return nativeBip32Pubkey(ptr, key);
}
// Taproot
public TaprootOutputKeyResult taprootOutputKey(byte[] internalX, byte[] merkleRoot) {
alive();
return nativeTaprootOutputKey(ptr, internalX, merkleRoot);
}
public byte[] taprootTweakSeckey(byte[] privkey, byte[] merkleRoot) {
alive();
return nativeTaprootTweakSeckey(ptr, privkey, merkleRoot);
}
public boolean taprootVerify(byte[] outputX, int parity, byte[] internalX, byte[] merkleRoot) {
alive();
return nativeTaprootVerify(ptr, outputX, parity, internalX, merkleRoot);
}
// Native declarations
private static native long nativeCreate();
private static native void nativeDestroy(long ptr);
private static native long nativeClone(long ptr);
private static native int nativeLastError(long ptr);
private static native String nativeLastErrorMsg(long ptr);
private static native int nativeVersion();
private static native int nativeAbiVersion();
private static native String nativeVersionString();
private static native byte[] nativePubkeyCreate(long ctx, byte[] privkey);
private static native byte[] nativePubkeyCreateUncompressed(long ctx, byte[] privkey);
private static native byte[] nativePubkeyParse(long ctx, byte[] pubkey);
private static native byte[] nativePubkeyXonly(long ctx, byte[] privkey);
private static native boolean nativeSeckeyVerify(long ctx, byte[] privkey);
private static native byte[] nativeSeckeyNegate(long ctx, byte[] privkey);
private static native byte[] nativeSeckeyTweakAdd(long ctx, byte[] privkey, byte[] tweak);
private static native byte[] nativeSeckeyTweakMul(long ctx, byte[] privkey, byte[] tweak);
private static native byte[] nativeEcdsaSign(long ctx, byte[] msgHash, byte[] privkey);
private static native boolean nativeEcdsaVerify(long ctx, byte[] msgHash, byte[] sig, byte[] pubkey);
private static native byte[] nativeEcdsaSigToDer(long ctx, byte[] sig);
private static native byte[] nativeEcdsaSigFromDer(long ctx, byte[] der);
private static native RecoverableSignature nativeEcdsaSignRecoverable(long ctx, byte[] msgHash, byte[] privkey);
private static native byte[] nativeEcdsaRecover(long ctx, byte[] msgHash, byte[] sig, int recid);
private static native byte[] nativeSchnorrSign(long ctx, byte[] msg, byte[] privkey, byte[] auxRand);
private static native boolean nativeSchnorrVerify(long ctx, byte[] msg, byte[] sig, byte[] pubkeyX);
private static native byte[] nativeEcdh(long ctx, byte[] privkey, byte[] pubkey);
private static native byte[] nativeEcdhXonly(long ctx, byte[] privkey, byte[] pubkey);
private static native byte[] nativeEcdhRaw(long ctx, byte[] privkey, byte[] pubkey);
private static native byte[] nativeSha256(byte[] data);
private static native byte[] nativeHash160(byte[] data);
private static native byte[] nativeTaggedHash(byte[] tag, byte[] data);
private static native String nativeAddrP2pkh(long ctx, byte[] pubkey, int network);
private static native String nativeAddrP2wpkh(long ctx, byte[] pubkey, int network);
private static native String nativeAddrP2tr(long ctx, byte[] xonly, int network);
private static native String nativeWifEncode(long ctx, byte[] privkey, boolean compressed, int network);
private static native WifDecoded nativeWifDecode(long ctx, String wif);
private static native byte[] nativeBip32Master(long ctx, byte[] seed);
private static native byte[] nativeBip32Derive(long ctx, byte[] parent, int index);
private static native byte[] nativeBip32DerivePath(long ctx, byte[] master, String path);
private static native byte[] nativeBip32Privkey(long ctx, byte[] key);
private static native byte[] nativeBip32Pubkey(long ctx, byte[] key);
private static native TaprootOutputKeyResult nativeTaprootOutputKey(long ctx, byte[] internalX, byte[] merkleRoot);
private static native byte[] nativeTaprootTweakSeckey(long ctx, byte[] privkey, byte[] merkleRoot);
private static native boolean nativeTaprootVerify(long ctx, byte[] outputX, int parity, byte[] internalX, byte[] merkleRoot);
}

View File

@ -0,0 +1,18 @@
package com.ultrafast.ufsecp;
/** Decoded WIF private key: raw 32-byte key + compression flag + network id. */
public final class WifDecoded {
private final byte[] privkey;
private final boolean compressed;
private final int network;
public WifDecoded(byte[] privkey, boolean compressed, int network) {
this.privkey = privkey;
this.compressed = compressed;
this.network = network;
}
public byte[] getPrivkey() { return privkey; }
public boolean isCompressed() { return compressed; }
public int getNetwork() { return network; }
}

View File

@ -12,7 +12,6 @@ High-performance Node.js native addon for secp256k1 elliptic curve cryptography,
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Constant-time** — all secret-key operations use CT layer automatically
## Install
@ -111,9 +110,9 @@ const { outputKeyX, parity } = secp.taprootOutputKey(xOnlyPub);
const tweakedPriv = secp.taprootTweakPrivkey(privkey);
```
## Performance
## Architecture Note
Built on hand-optimized C/C++ with platform-specific acceleration (AVX2, SHA-NI, BMI2 on x86; NEON on ARM). All secret-key operations use the constant-time layer — no opt-in required.
Built on hand-optimized C/C++ with platform-specific acceleration (AVX2, SHA-NI, BMI2 on x86; NEON on ARM). The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
| Operation | x86-64 | ARM64 | RISC-V |
|-----------|--------|-------|--------|

62
bindings/php/README.md Normal file
View File

@ -0,0 +1,62 @@
# Ufsecp — PHP
PHP FFI binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
This is the **reference binding** with 100% API coverage.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
- **Context** — create, destroy, clone, last_error, ctx_size
## Requirements
- PHP 7.4+ with FFI extension enabled
- `libufsecp.so` / `ufsecp.dll` / `libufsecp.dylib`
## Quick Start
```php
use Ultrafast\Ufsecp;
$ctx = new Ufsecp();
$privkey = str_repeat("\x00", 31) . "\x01";
$pubkey = $ctx->pubkeyCreate($privkey);
$msgHash = Ufsecp::sha256("hello");
$sig = $ctx->ecdsaSign($msgHash, $privkey);
$valid = $ctx->ecdsaVerify($msgHash, $sig, $pubkey);
$ctx->destroy();
```
## ECDSA Recovery
```php
[$sig, $recid] = $ctx->ecdsaSignRecoverable($msgHash, $privkey);
$recovered = $ctx->ecdsaRecover($msgHash, $sig, $recid);
```
## Taproot (BIP-341)
```php
[$outputKey, $parity] = $ctx->taprootOutputKey($xonlyPub);
$tweaked = $ctx->taprootTweakSeckey($privkey);
$valid = $ctx->taprootVerify($outputKey, $parity, $xonlyPub);
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

68
bindings/python/README.md Normal file
View File

@ -0,0 +1,68 @@
# ufsecp — Python
Python ctypes binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Install
```bash
pip install ufsecp
```
Requires the native `libufsecp.so` / `ufsecp.dll` / `libufsecp.dylib` alongside the package or set `UFSECP_LIB` env var.
## Quick Start
```python
from ufsecp import Ufsecp
with Ufsecp() as ctx:
privkey = bytes(31) + b'\x01'
pubkey = ctx.pubkey_create(privkey)
msg_hash = ctx.sha256(b'hello')
sig = ctx.ecdsa_sign(msg_hash, privkey)
valid = ctx.ecdsa_verify(msg_hash, sig, pubkey)
```
## ECDSA Recovery
```python
rs = ctx.ecdsa_sign_recoverable(msg_hash, privkey)
recovered = ctx.ecdsa_recover(msg_hash, rs.signature, rs.recovery_id)
```
## BIP-32 HD Derivation
```python
master = ctx.bip32_master(seed)
child = ctx.bip32_derive_path(master, "m/44'/0'/0'/0/0")
child_priv = ctx.bip32_privkey(child)
child_pub = ctx.bip32_pubkey(child)
```
## Taproot (BIP-341)
```python
tok = ctx.taproot_output_key(xonly_pub)
tweaked = ctx.taproot_tweak_seckey(privkey)
valid = ctx.taproot_verify(tok.output_key_x, tok.parity, xonly_pub)
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -182,6 +182,25 @@ class Ufsecp:
self._lib.ufsecp_version_string.restype = c_char_p
return self._lib.ufsecp_version_string().decode()
# -- Context extras ---------------------------------------------------
def clone(self) -> 'Ufsecp':
"""Clone this context (deep copy)."""
clone_ptr = c_void_p()
self._throw(self._lib.ufsecp_ctx_clone(self._ctx, byref(clone_ptr)), "ctx_clone")
obj = object.__new__(Ufsecp)
obj._lib = self._lib
obj._ctx = clone_ptr
return obj
def last_error(self) -> int:
"""Return last error code."""
return self._lib.ufsecp_last_error(self._ctx)
def last_error_msg(self) -> str:
"""Return last error message."""
return self._lib.ufsecp_last_error_msg(self._ctx).decode()
# -- Key operations ---------------------------------------------------
def pubkey_create(self, privkey: bytes) -> bytes:
@ -437,6 +456,14 @@ class Ufsecp:
L.ufsecp_ctx_destroy.argtypes = [vp]
L.ufsecp_ctx_destroy.restype = None
L.ufsecp_ctx_clone.argtypes = [vp, pvp]
L.ufsecp_ctx_clone.restype = c_int
L.ufsecp_last_error.argtypes = [vp]
L.ufsecp_last_error.restype = c_int
L.ufsecp_last_error_msg.argtypes = [vp]
L.ufsecp_last_error_msg.restype = c_char_p
L.ufsecp_version.restype = c_uint32
L.ufsecp_abi_version.restype = c_uint32

View File

@ -14,7 +14,6 @@ Uses native C/C++ through JSI (Android NDK + iOS) for maximum performance — no
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256, HASH160, tagged hash
- **Constant-time** — all secret-key operations use CT layer automatically
## Install
@ -85,6 +84,10 @@ const childPriv = secp.bip32GetPrivkey(child);
| iOS | iOS 13+, CocoaPods |
| React Native | >= 0.71.0 |
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -57,6 +57,7 @@ class UfsecpContext {
// ── Version ──────────────────────────────────────────────────────
static version() { return NativeUfsecp.version(); }
static abiVersion() { return NativeUfsecp.abiVersion(); }
static versionString() { return NativeUfsecp.versionString(); }
// ── Key ops ──────────────────────────────────────────────────────
@ -77,6 +78,31 @@ class UfsecpContext {
return NativeUfsecp.seckeyVerify(this._h, privkeyHex);
}
async seckeyNegate(privkeyHex) {
this._alive();
return NativeUfsecp.seckeyNegate(this._h, privkeyHex);
}
async seckeyTweakAdd(privkeyHex, tweakHex) {
this._alive();
return NativeUfsecp.seckeyTweakAdd(this._h, privkeyHex, tweakHex);
}
async seckeyTweakMul(privkeyHex, tweakHex) {
this._alive();
return NativeUfsecp.seckeyTweakMul(this._h, privkeyHex, tweakHex);
}
async pubkeyParse(pubkeyHex) {
this._alive();
return NativeUfsecp.pubkeyParse(this._h, pubkeyHex);
}
async pubkeyXonly(privkeyHex) {
this._alive();
return NativeUfsecp.pubkeyXonly(this._h, privkeyHex);
}
// ── ECDSA ────────────────────────────────────────────────────────
async ecdsaSign(msgHashHex, privkeyHex) {
@ -99,6 +125,16 @@ class UfsecpContext {
return NativeUfsecp.ecdsaRecover(this._h, msgHashHex, sigHex, recid);
}
async ecdsaSigToDer(sigHex) {
this._alive();
return NativeUfsecp.ecdsaSigToDer(this._h, sigHex);
}
async ecdsaSigFromDer(derHex) {
this._alive();
return NativeUfsecp.ecdsaSigFromDer(this._h, derHex);
}
// ── Schnorr ──────────────────────────────────────────────────────
async schnorrSign(msgHex, privkeyHex, auxRandHex) {
@ -118,10 +154,21 @@ class UfsecpContext {
return NativeUfsecp.ecdh(this._h, privkeyHex, pubkeyHex);
}
async ecdhXonly(privkeyHex, pubkeyHex) {
this._alive();
return NativeUfsecp.ecdhXonly(this._h, privkeyHex, pubkeyHex);
}
async ecdhRaw(privkeyHex, pubkeyHex) {
this._alive();
return NativeUfsecp.ecdhRaw(this._h, privkeyHex, pubkeyHex);
}
// ── Hashing ──────────────────────────────────────────────────────
static sha256(dataHex) { return NativeUfsecp.sha256(dataHex); }
static hash160(dataHex) { return NativeUfsecp.hash160(dataHex); }
static taggedHash(tag, dataHex) { return NativeUfsecp.taggedHash(tag, dataHex); }
// ── Addresses ────────────────────────────────────────────────────
@ -136,6 +183,11 @@ class UfsecpContext {
return NativeUfsecp.wifEncode(this._h, privkeyHex, compressed, network);
}
async wifDecode(wifStr) {
this._alive();
return NativeUfsecp.wifDecode(this._h, wifStr);
}
// ── BIP-32 ───────────────────────────────────────────────────────
async bip32Master(seedHex) {
@ -153,6 +205,16 @@ class UfsecpContext {
return NativeUfsecp.bip32DerivePath(this._h, masterHex, path);
}
async bip32Privkey(keyHex) {
this._alive();
return NativeUfsecp.bip32Privkey(this._h, keyHex);
}
async bip32Pubkey(keyHex) {
this._alive();
return NativeUfsecp.bip32Pubkey(this._h, keyHex);
}
// ── Taproot ──────────────────────────────────────────────────────
async taprootOutputKey(internalXHex, merkleRootHex = null) {
@ -165,6 +227,11 @@ class UfsecpContext {
return NativeUfsecp.taprootTweakSeckey(this._h, privkeyHex, merkleRootHex);
}
async taprootVerify(outputXHex, parity, internalXHex, merkleRootHex = null) {
this._alive();
return NativeUfsecp.taprootVerify(this._h, outputXHex, parity, internalXHex, merkleRootHex);
}
// ── Internal ─────────────────────────────────────────────────────
_alive() {

62
bindings/ruby/README.md Normal file
View File

@ -0,0 +1,62 @@
# ufsecp — Ruby
Ruby FFI binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Install
```ruby
gem 'ufsecp'
```
Requires `libufsecp.so` / `ufsecp.dll` / `libufsecp.dylib` on the library path.
## Quick Start
```ruby
require 'ufsecp'
ctx = Ufsecp::Context.new
privkey = "\x00" * 31 + "\x01"
pubkey = ctx.pubkey_create(privkey)
msg_hash = Ufsecp.sha256("hello")
sig = ctx.ecdsa_sign(msg_hash, privkey)
valid = ctx.ecdsa_verify(msg_hash, sig, pubkey)
ctx.destroy
```
## ECDSA Recovery
```ruby
sig, recid = ctx.ecdsa_sign_recoverable(msg_hash, privkey)
recovered = ctx.ecdsa_recover(msg_hash, sig, recid)
```
## Taproot (BIP-341)
```ruby
output_key, parity = ctx.taproot_output_key(xonly_pub)
tweaked = ctx.taproot_tweak_seckey(privkey)
valid = ctx.taproot_verify(output_key, parity, xonly_pub)
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

53
bindings/rust/README.md Normal file
View File

@ -0,0 +1,53 @@
# ufsecp — Rust
Safe Rust wrapper for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography.
Wraps the `ufsecp-sys` FFI crate with a safe, ergonomic API.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Quick Start
```rust
use ufsecp::Context;
let ctx = Context::new()?;
let privkey = [0u8; 31].iter().chain(&[1u8]).copied().collect::<Vec<_>>();
let pubkey = ctx.pubkey_create(&privkey)?;
let msg_hash = Context::sha256(b"hello")?;
let sig = ctx.ecdsa_sign(&msg_hash, &privkey)?;
let valid = ctx.ecdsa_verify(&msg_hash, &sig, &pubkey)?;
```
## ECDSA Recovery
```rust
let (sig, recid) = ctx.ecdsa_sign_recoverable(&msg_hash, &privkey)?;
let recovered = ctx.ecdsa_recover(&msg_hash, &sig, recid)?;
```
## Taproot (BIP-341)
```rust
let (output_key, parity) = ctx.taproot_output_key(&xonly_pub, None)?;
let tweaked = ctx.taproot_tweak_seckey(&privkey, None)?;
let valid = ctx.taproot_verify(&output_key, parity, &xonly_pub, None)?;
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -127,6 +127,20 @@ impl Context {
Ok(Context { ptr })
}
/// Return the last error code stored in this context.
pub fn last_error(&self) -> i32 {
unsafe { ufsecp_sys::ufsecp_last_error(self.ptr) }
}
/// Return the last error message stored in this context.
pub fn last_error_msg(&self) -> &str {
unsafe {
let p = ufsecp_sys::ufsecp_last_error_msg(self.ptr);
if p.is_null() { return ""; }
CStr::from_ptr(p).to_str().unwrap_or("")
}
}
// ── Version ────────────────────────────────────────────────────────
pub fn version() -> u32 { unsafe { ufsecp_sys::ufsecp_version() } }

59
bindings/swift/README.md Normal file
View File

@ -0,0 +1,59 @@
# Ufsecp — Swift
Swift binding for [UltrafastSecp256k1](https://github.com/shrec/UltrafastSecp256k1) — high-performance secp256k1 elliptic curve cryptography via C interop.
## Features
- **ECDSA** — sign, verify, recover, DER serialization (RFC 6979)
- **Schnorr** — BIP-340 sign/verify
- **ECDH** — compressed, x-only, raw shared secret
- **BIP-32** — HD key derivation (master/derive/path/privkey/pubkey)
- **Taproot** — output key tweaking, verification (BIP-341)
- **Addresses** — P2PKH, P2WPKH, P2TR
- **WIF** — encode/decode
- **Hashing** — SHA-256 (hardware-accelerated), HASH160, tagged hash
- **Key tweaking** — negate, add, multiply
## Quick Start
```swift
let ctx = try UfsecpContext()
defer { ctx.destroy() }
let privkey = Data(repeating: 0, count: 31) + Data([0x01])
let pubkey = try ctx.pubkeyCreate(privkey: privkey)
let msgHash = try UfsecpContext.sha256(Data("hello".utf8))
let sig = try ctx.ecdsaSign(msgHash: msgHash, privkey: privkey)
let valid = try ctx.ecdsaVerify(msgHash: msgHash, sig: sig, pubkey: pubkey)
```
## ECDSA Recovery
```swift
let rs = try ctx.ecdsaSignRecoverable(msgHash: msgHash, privkey: privkey)
let recovered = try ctx.ecdsaRecover(msgHash: msgHash, sig: rs.signature, recid: rs.recoveryId)
```
## Schnorr (BIP-340)
```swift
let xonly = try ctx.pubkeyXonly(privkey: privkey)
let schnorrSig = try ctx.schnorrSign(msg: msgHash, privkey: privkey, auxRand: auxRand)
let ok = try ctx.schnorrVerify(msg: msgHash, sig: schnorrSig, pubkeyX: xonly)
```
## Taproot (BIP-341)
```swift
let tok = try ctx.taprootOutputKey(internalX: xonly, merkleRoot: nil)
let tweaked = try ctx.taprootTweakSeckey(privkey: privkey, merkleRoot: nil)
let tapValid = try ctx.taprootVerify(outputX: tok.outputKeyX, parity: tok.parity, internalX: xonly, merkleRoot: nil)
```
## Architecture Note
The C ABI layer uses the **fast** (variable-time) implementation for maximum throughput. A constant-time (CT) layer with identical mathematical operations is available via the C++ headers for applications requiring timing-attack resistance.
## License
MIT

View File

@ -240,6 +240,84 @@ public final class UfsecpContext {
return Data(out)
}
public func ecdhXonly(privkey: Data, pubkey: Data) throws -> Data {
try chk(privkey, 32, "privkey"); try chk(pubkey, 33, "pubkey"); try alive()
var out = [UInt8](repeating: 0, count: 32)
try privkey.withUnsafeBytes { pk in
try pubkey.withUnsafeBytes { pub in
try throwRC(ufsecp_ecdh_xonly(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self),
pub.baseAddress!.assumingMemoryBound(to: UInt8.self), &out), "ecdh_xonly")
}
}
return Data(out)
}
public func ecdhRaw(privkey: Data, pubkey: Data) throws -> Data {
try chk(privkey, 32, "privkey"); try chk(pubkey, 33, "pubkey"); try alive()
var out = [UInt8](repeating: 0, count: 32)
try privkey.withUnsafeBytes { pk in
try pubkey.withUnsafeBytes { pub in
try throwRC(ufsecp_ecdh_raw(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self),
pub.baseAddress!.assumingMemoryBound(to: UInt8.self), &out), "ecdh_raw")
}
}
return Data(out)
}
// MARK: ECDSA DER
public func ecdsaSigToDer(sig: Data) throws -> Data {
try chk(sig, 64, "sig"); try alive()
var der = [UInt8](repeating: 0, count: 72)
var dlen: Int = 72
try sig.withUnsafeBytes { s in
try throwRC(ufsecp_ecdsa_sig_to_der(ctx!,
s.baseAddress!.assumingMemoryBound(to: UInt8.self), &der, &dlen), "ecdsa_sig_to_der")
}
return Data(der.prefix(dlen))
}
public func ecdsaSigFromDer(der: Data) throws -> Data {
try alive()
var sig = [UInt8](repeating: 0, count: 64)
try der.withUnsafeBytes { d in
try throwRC(ufsecp_ecdsa_sig_from_der(ctx!,
d.baseAddress!.assumingMemoryBound(to: UInt8.self), der.count, &sig), "ecdsa_sig_from_der")
}
return Data(sig)
}
// MARK: ECDSA Recovery
public func ecdsaSignRecoverable(msgHash: Data, privkey: Data) throws -> RecoverableSignature {
try chk(msgHash, 32, "msgHash"); try chk(privkey, 32, "privkey"); try alive()
var sig = [UInt8](repeating: 0, count: 64)
var recid: Int32 = 0
try msgHash.withUnsafeBytes { msg in
try privkey.withUnsafeBytes { pk in
try throwRC(ufsecp_ecdsa_sign_recoverable(ctx!,
msg.baseAddress!.assumingMemoryBound(to: UInt8.self),
pk.baseAddress!.assumingMemoryBound(to: UInt8.self), &sig, &recid), "ecdsa_sign_recoverable")
}
}
return RecoverableSignature(signature: Data(sig), recoveryId: recid)
}
public func ecdsaRecover(msgHash: Data, sig: Data, recid: Int32) throws -> Data {
try chk(msgHash, 32, "msgHash"); try chk(sig, 64, "sig"); try alive()
var pub = [UInt8](repeating: 0, count: 33)
try msgHash.withUnsafeBytes { msg in
try sig.withUnsafeBytes { s in
try throwRC(ufsecp_ecdsa_recover(ctx!,
msg.baseAddress!.assumingMemoryBound(to: UInt8.self),
s.baseAddress!.assumingMemoryBound(to: UInt8.self), recid, &pub), "ecdsa_recover")
}
}
return Data(pub)
}
// MARK: Hashing
public static func sha256(_ data: Data) throws -> Data {
@ -258,6 +336,189 @@ public final class UfsecpContext {
return Data(out)
}
public static func taggedHash(tag: String, data: Data) throws -> Data {
var out = [UInt8](repeating: 0, count: 32)
try data.withUnsafeBytes { d in
try throwRC(ufsecp_tagged_hash(tag,
d.baseAddress!.assumingMemoryBound(to: UInt8.self), data.count, &out), "tagged_hash")
}
return Data(out)
}
// MARK: Addresses
public func addrP2pkh(pubkey: Data, network: Network = .mainnet) throws -> String {
try chk(pubkey, 33, "pubkey"); try alive()
var buf = [CChar](repeating: 0, count: 64)
try pubkey.withUnsafeBytes { pk in
try throwRC(ufsecp_addr_p2pkh(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self), &buf, 64, network.rawValue), "addr_p2pkh")
}
return String(cString: buf)
}
public func addrP2wpkh(pubkey: Data, network: Network = .mainnet) throws -> String {
try chk(pubkey, 33, "pubkey"); try alive()
var buf = [CChar](repeating: 0, count: 128)
try pubkey.withUnsafeBytes { pk in
try throwRC(ufsecp_addr_p2wpkh(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self), &buf, 128, network.rawValue), "addr_p2wpkh")
}
return String(cString: buf)
}
public func addrP2tr(xonly: Data, network: Network = .mainnet) throws -> String {
try chk(xonly, 32, "xonly"); try alive()
var buf = [CChar](repeating: 0, count: 128)
try xonly.withUnsafeBytes { x in
try throwRC(ufsecp_addr_p2tr(ctx!,
x.baseAddress!.assumingMemoryBound(to: UInt8.self), &buf, 128, network.rawValue), "addr_p2tr")
}
return String(cString: buf)
}
// MARK: WIF
public func wifEncode(privkey: Data, compressed: Bool = true, network: Network = .mainnet) throws -> String {
try chk(privkey, 32, "privkey"); try alive()
var buf = [CChar](repeating: 0, count: 64)
try privkey.withUnsafeBytes { pk in
try throwRC(ufsecp_wif_encode(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self), compressed ? 1 : 0,
network.rawValue, &buf, 64), "wif_encode")
}
return String(cString: buf)
}
public func wifDecode(wif: String) throws -> WifDecoded {
try alive()
var privkey = [UInt8](repeating: 0, count: 32)
var compressed: Int32 = 0
var net: Int32 = 0
try throwRC(ufsecp_wif_decode(ctx!, wif, &privkey, &compressed, &net), "wif_decode")
return WifDecoded(privkey: Data(privkey), compressed: compressed == 1,
network: Network(rawValue: net) ?? .mainnet)
}
// MARK: BIP-32
public func bip32Master(seed: Data) throws -> Data {
try alive()
var out = [UInt8](repeating: 0, count: 64)
try seed.withUnsafeBytes { s in
try throwRC(ufsecp_bip32_master(ctx!,
s.baseAddress!.assumingMemoryBound(to: UInt8.self), seed.count, &out), "bip32_master")
}
return Data(out)
}
public func bip32Derive(parent: Data, index: UInt32) throws -> Data {
try chk(parent, 64, "parent"); try alive()
var out = [UInt8](repeating: 0, count: 64)
try parent.withUnsafeBytes { p in
try throwRC(ufsecp_bip32_derive(ctx!,
p.baseAddress!.assumingMemoryBound(to: UInt8.self), index, &out), "bip32_derive")
}
return Data(out)
}
public func bip32DerivePath(master: Data, path: String) throws -> Data {
try chk(master, 64, "master"); try alive()
var out = [UInt8](repeating: 0, count: 64)
try master.withUnsafeBytes { m in
try throwRC(ufsecp_bip32_derive_path(ctx!,
m.baseAddress!.assumingMemoryBound(to: UInt8.self), path, &out), "bip32_derive_path")
}
return Data(out)
}
public func bip32Privkey(key: Data) throws -> Data {
try chk(key, 64, "key"); try alive()
var out = [UInt8](repeating: 0, count: 32)
try key.withUnsafeBytes { k in
try throwRC(ufsecp_bip32_privkey(ctx!,
k.baseAddress!.assumingMemoryBound(to: UInt8.self), &out), "bip32_privkey")
}
return Data(out)
}
public func bip32Pubkey(key: Data) throws -> Data {
try chk(key, 64, "key"); try alive()
var out = [UInt8](repeating: 0, count: 33)
try key.withUnsafeBytes { k in
try throwRC(ufsecp_bip32_pubkey(ctx!,
k.baseAddress!.assumingMemoryBound(to: UInt8.self), &out), "bip32_pubkey")
}
return Data(out)
}
// MARK: Taproot
public func taprootOutputKey(internalX: Data, merkleRoot: Data?) throws -> TaprootOutputKeyResult {
try chk(internalX, 32, "internalX"); try alive()
var outx = [UInt8](repeating: 0, count: 32)
var parity: Int32 = 0
if let mr = merkleRoot {
try internalX.withUnsafeBytes { ix in
try mr.withUnsafeBytes { m in
try throwRC(ufsecp_taproot_output_key(ctx!,
ix.baseAddress!.assumingMemoryBound(to: UInt8.self),
m.baseAddress!.assumingMemoryBound(to: UInt8.self), &outx, &parity), "taproot_output_key")
}
}
} else {
try internalX.withUnsafeBytes { ix in
try throwRC(ufsecp_taproot_output_key(ctx!,
ix.baseAddress!.assumingMemoryBound(to: UInt8.self), nil, &outx, &parity), "taproot_output_key")
}
}
return TaprootOutputKeyResult(outputKeyX: Data(outx), parity: parity)
}
public func taprootTweakSeckey(privkey: Data, merkleRoot: Data?) throws -> Data {
try chk(privkey, 32, "privkey"); try alive()
var out = [UInt8](repeating: 0, count: 32)
if let mr = merkleRoot {
try privkey.withUnsafeBytes { pk in
try mr.withUnsafeBytes { m in
try throwRC(ufsecp_taproot_tweak_seckey(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self),
m.baseAddress!.assumingMemoryBound(to: UInt8.self), &out), "taproot_tweak_seckey")
}
}
} else {
try privkey.withUnsafeBytes { pk in
try throwRC(ufsecp_taproot_tweak_seckey(ctx!,
pk.baseAddress!.assumingMemoryBound(to: UInt8.self), nil, &out), "taproot_tweak_seckey")
}
}
return Data(out)
}
public func taprootVerify(outputX: Data, parity: Int32, internalX: Data, merkleRoot: Data?) throws -> Bool {
try chk(outputX, 32, "outputX"); try chk(internalX, 32, "internalX"); try alive()
if let mr = merkleRoot {
return outputX.withUnsafeBytes { ox in
internalX.withUnsafeBytes { ix in
mr.withUnsafeBytes { m in
ufsecp_taproot_verify(ctx!,
ox.baseAddress!.assumingMemoryBound(to: UInt8.self), parity,
ix.baseAddress!.assumingMemoryBound(to: UInt8.self),
m.baseAddress!.assumingMemoryBound(to: UInt8.self), mr.count) == 0
}
}
}
} else {
return outputX.withUnsafeBytes { ox in
internalX.withUnsafeBytes { ix in
ufsecp_taproot_verify(ctx!,
ox.baseAddress!.assumingMemoryBound(to: UInt8.self), parity,
ix.baseAddress!.assumingMemoryBound(to: UInt8.self), nil, 0) == 0
}
}
}
}
// MARK: Internal
private func alive() throws {

View File

@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace secp256k1::fast {
@ -16,6 +18,29 @@ enum class SelftestMode : uint8_t {
stress = 2 // ci + extended iterations, large random sweeps (~10-60 min)
};
// -- Structured selftest result (for bindings / programmatic use) --
struct SelftestCaseResult {
std::string name; // e.g. "scalar_mul_KAT_10_vectors"
bool passed; // true = PASS
std::string detail; // empty on pass; failure description on fail
};
struct SelftestReport {
bool all_passed; // true if every case passed
int total; // number of test cases run
int passed; // number that passed
std::string mode; // "smoke", "ci", or "stress"
uint64_t seed; // PRNG seed used
std::string platform; // e.g. "x86_64 clang-17"
std::vector<SelftestCaseResult> cases; // per-test results
// Render as human-readable multi-line text
std::string to_text() const;
// Render as JSON string
std::string to_json() const;
};
// Run comprehensive self-tests on the library
// Returns true if all tests pass, false otherwise
// Set verbose=true to see detailed test output
@ -26,4 +51,10 @@ bool Selftest(bool verbose);
// Prints repro bundle: commit, compiler, platform, seed, mode.
bool Selftest(bool verbose, SelftestMode mode, uint64_t seed = 0);
// Run self-tests and return a structured report (no stdout output).
// Suitable for bindings (Python, Rust, Node.js, etc.) that need
// a programmatic result rather than console output.
SelftestReport selftest_report(SelftestMode mode = SelftestMode::smoke,
uint64_t seed = 0);
} // namespace secp256k1::fast

View File

@ -1214,6 +1214,48 @@ static bool test_point_advanced(bool verbose) {
return ok;
}
// ---------------------------------------------------------------
// Thread-local report collector for selftest_report()
// When non-null, tally() appends case results into this report.
// ---------------------------------------------------------------
static thread_local SelftestReport* s_active_report = nullptr;
static inline void tally(int& total, int& passed,
const char* name, bool ok) {
total++;
if (ok) passed++;
if (s_active_report) {
s_active_report->cases.push_back({name, ok, ok ? "" : "FAIL"});
}
}
// Platform string (compile-time)
static const char* get_platform_string() {
#if defined(_WIN64)
return "Windows x64";
#elif defined(_WIN32)
return "Windows x86";
#elif defined(__APPLE__) && defined(__aarch64__)
return "macOS ARM64";
#elif defined(__APPLE__)
return "macOS x64";
#elif defined(__linux__) && defined(__riscv)
return "Linux RISC-V";
#elif defined(__linux__) && defined(__aarch64__)
return "Linux ARM64";
#elif defined(__linux__)
return "Linux x64";
#elif defined(SECP256K1_PLATFORM_ESP32) || defined(ESP_PLATFORM)
return "ESP32";
#elif defined(SECP256K1_PLATFORM_STM32)
return "STM32";
#elif defined(__EMSCRIPTEN__)
return "WASM";
#else
return "unknown";
#endif
}
// Main self-test function (legacy API -- delegates to ci mode)
bool Selftest(bool verbose) {
return Selftest(verbose, SelftestMode::ci, 0);
@ -1271,10 +1313,12 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
SELFTEST_PRINT("\nScalar Multiplication Tests:\n");
}
for (const auto& vec : TEST_VECTORS) {
total++;
if (test_scalar_mul(vec, verbose)) {
passed++;
{
int vi = 0;
for (const auto& vec : TEST_VECTORS) {
char vname[48];
std::snprintf(vname, sizeof(vname), "scalar_mul_vector_%d", ++vi);
tally(total, passed, vname, test_scalar_mul(vec, verbose));
}
}
@ -1282,65 +1326,46 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (verbose) {
SELFTEST_PRINT("\nPoint Addition Test:\n");
}
total++;
if (test_addition(verbose)) {
passed++;
}
tally(total, passed, "point_addition", test_addition(verbose));
// Test point subtraction
if (verbose) {
SELFTEST_PRINT("\nPoint Subtraction Test:\n");
}
total++;
if (test_subtraction(verbose)) {
passed++;
}
tally(total, passed, "point_subtraction", test_subtraction(verbose));
// Field arithmetic
total++;
if (test_field_arithmetic(verbose)) passed++;
tally(total, passed, "field_arithmetic", test_field_arithmetic(verbose));
// Scalar arithmetic (basic identities)
total++;
if (test_scalar_arithmetic(verbose)) passed++;
tally(total, passed, "scalar_arithmetic", test_scalar_arithmetic(verbose));
// Point group identities
total++;
if (test_point_identities(verbose)) passed++;
tally(total, passed, "point_identities", test_point_identities(verbose));
// Point serialization
total++;
if (test_point_serialization(verbose)) passed++;
tally(total, passed, "point_serialization", test_point_serialization(verbose));
// Batch inverse (small)
total++;
if (test_batch_inverse(verbose)) passed++;
tally(total, passed, "batch_inverse", test_batch_inverse(verbose));
// Constant-expected point ops
total++;
if (test_addition_constants(verbose)) passed++;
total++;
if (test_subtraction_constants(verbose)) passed++;
total++;
if (test_doubling_constants(verbose)) passed++;
total++;
if (test_negation_constants(verbose)) passed++;
tally(total, passed, "addition_constants", test_addition_constants(verbose));
tally(total, passed, "subtraction_constants", test_subtraction_constants(verbose));
tally(total, passed, "doubling_constants", test_doubling_constants(verbose));
tally(total, passed, "negation_constants", test_negation_constants(verbose));
// Boundary scalar KAT (limb/order edges)
total++;
if (test_boundary_scalar_vectors(verbose)) passed++;
tally(total, passed, "boundary_scalar_vectors", test_boundary_scalar_vectors(verbose));
// Field limb boundaries
total++;
if (test_field_limb_boundaries(verbose)) passed++;
tally(total, passed, "field_limb_boundaries", test_field_limb_boundaries(verbose));
// Extended kG vectors (4G-9G, 15G, 255G)
total++;
if (test_extended_kg_vectors(verbose)) passed++;
tally(total, passed, "extended_kg_vectors", test_extended_kg_vectors(verbose));
// Point advanced (comm/assoc/mixed/dist/edge)
total++;
if (test_point_advanced(verbose)) passed++;
tally(total, passed, "point_advanced", test_point_advanced(verbose));
if (is_smoke) {
// Smoke mode ends here
@ -1363,8 +1388,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
// ===============================================================
// External vectors (optional, environment-driven)
total++;
if (run_external_vectors(verbose)) passed++;
tally(total, passed, "external_vectors", run_external_vectors(verbose));
// Doubling chain vs scalar multiples: for i=1..20, (2^i)G via dbl() equals scalar_mul
{
@ -1378,7 +1402,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!points_equal(cur, exp)) { ok = false; break; }
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "doubling_chain_vs_scalar", ok);
}
// Large scalar cross-checks (fast vs affine fallback)
@ -1400,7 +1424,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!points_equal(fast, ref)) { ok = false; break; }
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "large_scalar_cross_checks", ok);
}
// Squared scalar cases: k^2 * G
@ -1426,42 +1450,42 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!points_equal(fast, ref)) { ok = false; break; }
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "squared_scalar_cases", ok);
}
// Expanded batch inverse (32 elements)
total++; if (test_batch_inverse_expanded(verbose)) passed++;
tally(total, passed, "batch_inverse_expanded", test_batch_inverse_expanded(verbose));
// Bilinearity for K*Q with +/-G
total++; if (test_bilinearity_K_times_Q(verbose)) passed++;
tally(total, passed, "bilinearity_K_times_Q", test_bilinearity_K_times_Q(verbose));
#if !defined(SECP256K1_PLATFORM_ESP32) && !defined(ESP_PLATFORM) && !defined(IDF_VER) && !defined(SECP256K1_PLATFORM_STM32)
// Batch inverse size sweep (21 sizes)
total++; if (test_batch_inverse_sweep(verbose)) passed++;
tally(total, passed, "batch_inverse_sweep", test_batch_inverse_sweep(verbose));
#endif
#if !defined(SECP256K1_PLATFORM_ESP32) && !defined(ESP_PLATFORM) && !defined(IDF_VER) && !defined(SECP256K1_PLATFORM_STM32)
// Fixed-K plan equivalence (GLV-based, not available on embedded)
total++; if (test_fixedK_plan(verbose)) passed++;
tally(total, passed, "fixedK_plan", test_fixedK_plan(verbose));
#endif
// Sequential increment property
total++; if (test_sequential_increment_property(verbose)) passed++;
tally(total, passed, "sequential_increment_property", test_sequential_increment_property(verbose));
// Fast kG vs generic kG (small 1-20 + 20 random)
total++; if (test_fast_vs_generic_kG(verbose)) passed++;
tally(total, passed, "fast_vs_generic_kG", test_fast_vs_generic_kG(verbose));
// Repeated addition consistency (k=2..10)
total++; if (test_repeated_addition_consistency(verbose)) passed++;
tally(total, passed, "repeated_addition_consistency", test_repeated_addition_consistency(verbose));
// Field stress (normalization + random algebraic laws)
total++; if (test_field_stress(verbose)) passed++;
tally(total, passed, "field_stress", test_field_stress(verbose));
// Scalar stress ((n-1)^2=1 + random algebraic laws)
total++; if (test_scalar_stress(verbose)) passed++;
tally(total, passed, "scalar_stress", test_scalar_stress(verbose));
// NAF/wNAF encoding validation
total++; if (test_naf_wnaf(verbose)) passed++;
tally(total, passed, "naf_wnaf", test_naf_wnaf(verbose));
// ===============================================================
// TIER 3: STRESS -- Extended iterations (~10-60 min)
@ -1487,7 +1511,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
}
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "stress_fast_vs_generic_kG_1000", ok);
}
// Stress: extended field algebraic laws (500 random triples)
@ -1510,7 +1534,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!(sq == a * a)) ok = false;
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "stress_field_algebraic_laws_500", ok);
}
// Stress: extended bilinearity K*(Q+/-G) (100 random K,Q pairs)
@ -1536,7 +1560,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!points_equal(Lm, Rm)) { ok = false; break; }
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "stress_bilinearity_100", ok);
}
#if !defined(SECP256K1_PLATFORM_ESP32) && !defined(ESP_PLATFORM) && !defined(IDF_VER) && !defined(SECP256K1_PLATFORM_STM32)
@ -1566,7 +1590,7 @@ bool Selftest(bool verbose, SelftestMode mode, uint64_t seed) {
if (!ok) break;
}
if (verbose) SELFTEST_PRINT(ok ? " PASS\n" : " FAIL\n");
total++; if (ok) passed++;
tally(total, passed, "stress_batch_inverse_8192", ok);
}
#endif
}

View File

@ -450,7 +450,7 @@ cmake -S . -B build-android -G Ninja \
cmake --build build-android -j
```
The library produces `libsecp256k1-fast-cpu.a` for linking into Android apps via JNI.
The library produces `libfastsecp256k1.a` for linking into Android apps via JNI.
---
@ -575,7 +575,7 @@ cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ...
```cmake
add_subdirectory(path/to/UltrafastSecp256k1)
target_link_libraries(your_target PRIVATE secp256k1-fast-cpu)
target_link_libraries(your_target PRIVATE secp256k1::fast)
```
### After Installation
@ -591,7 +591,7 @@ cmake --install build --prefix /opt/secp256k1
```cmake
# In your CMakeLists.txt
find_package(secp256k1-fast REQUIRED)
target_link_libraries(your_target PRIVATE secp256k1::fast-cpu)
target_link_libraries(your_target PRIVATE secp256k1::fastsecp256k1)
```
### pkg-config
@ -632,7 +632,7 @@ echo "deb [signed-by=/etc/apt/keyrings/ultrafastsecp256k1.gpg] \
| sudo tee /etc/apt/sources.list.d/ultrafastsecp256k1.list
sudo apt update
sudo apt install libsecp256k1-fast-dev # headers + static + shared
sudo apt install libufsecp-dev # headers + static + shared
```
### Fedora / RHEL (RPM)
@ -647,7 +647,7 @@ sudo dnf install ./UltrafastSecp256k1-*.rpm
### Arch Linux (AUR)
```bash
yay -S libsecp256k1-fast
yay -S libufsecp
```
### Docker (build from source)

View File

@ -40,7 +40,7 @@ set(SECP256K1_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(secp256k1_fast)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE secp256k1_fast)
target_link_libraries(my_app PRIVATE secp256k1::fast)
```
---
@ -60,7 +60,7 @@ set(SECP256K1_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/secp256k1_fast)
target_link_libraries(my_app PRIVATE secp256k1_fast)
target_link_libraries(my_app PRIVATE secp256k1::fast)
```
---
@ -80,7 +80,7 @@ To use from a vcpkg overlay port or after it's published:
```cmake
find_package(secp256k1-fast CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE secp256k1-fast::secp256k1-fast)
target_link_libraries(my_app PRIVATE secp256k1::fastsecp256k1)
```
---
@ -97,7 +97,7 @@ Then in your project:
```cmake
find_package(secp256k1-fast 3.3 CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE secp256k1-fast::secp256k1-fast)
target_link_libraries(my_app PRIVATE secp256k1::fastsecp256k1)
```
Or via pkg-config:

View File

@ -87,7 +87,7 @@ int main() {
Compile and run:
```bash
g++ -std=c++20 -O3 example.cpp -I build/cpu/include -L build/cpu -lsecp256k1-fast-cpu -o example
g++ -std=c++20 -O3 example.cpp -I build/cpu/include -L build/cpu -lfastsecp256k1 -o example
./example
```
@ -97,14 +97,14 @@ g++ -std=c++20 -O3 example.cpp -I build/cpu/include -L build/cpu -lsecp256k1-fas
```cmake
add_subdirectory(UltrafastSecp256k1)
target_link_libraries(your_target PRIVATE secp256k1-fast-cpu)
target_link_libraries(your_target PRIVATE secp256k1::fast)
```
### As Installed Package
```cmake
find_package(secp256k1-fast REQUIRED)
target_link_libraries(your_target PRIVATE secp256k1::fast-cpu)
target_link_libraries(your_target PRIVATE secp256k1::fastsecp256k1)
```
### pkg-config

View File

@ -19,8 +19,8 @@ cd build && cpack -G DEB
```
Produces:
- `libsecp256k1-fast3_<ver>_<arch>.deb` — shared library
- `libsecp256k1-fast-dev_<ver>_<arch>.deb` — headers + static lib + cmake/pkgconfig
- `libufsecp3_<ver>_<arch>.deb` — shared library
- `libufsecp-dev_<ver>_<arch>.deb` — headers + static lib + cmake/pkgconfig
## Fedora / RHEL / CentOS (.rpm)
@ -29,7 +29,7 @@ Produces:
sudo dnf install cmake ninja-build gcc-c++ rpm-build
# Build RPM from spec
rpmbuild -ba packaging/rpm/libsecp256k1-fast.spec
rpmbuild -ba packaging/rpm/libufsecp.spec
# — or use CPack —
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DSECP256K1_BUILD_SHARED=ON -DSECP256K1_INSTALL=ON
@ -65,13 +65,13 @@ sudo ldconfig
```
After install, applications can find the library via:
- **pkg-config**: `pkg-config --cflags --libs secp256k1-fast`
- **CMake**: `find_package(secp256k1-fast 3 REQUIRED COMPONENTS CPU)`
- **pkg-config**: `pkg-config --cflags --libs ufsecp`
- **CMake**: `find_package(ufsecp 3 REQUIRED)`
## Package naming convention
| Distro | Runtime | Development |
|--------|---------|-------------|
| Debian/Ubuntu | `libsecp256k1-fast3` | `libsecp256k1-fast-dev` |
| Fedora/RHEL | `libsecp256k1-fast` | `libsecp256k1-fast-devel` |
| Arch | `libsecp256k1-fast` | (included in main package) |
| Debian/Ubuntu | `libufsecp3` | `libufsecp-dev` |
| Fedora/RHEL | `libufsecp` | `libufsecp-devel` |
| Arch | `libufsecp` | (included in main package) |

View File

@ -1,5 +1,5 @@
# Maintainer: shrec <shrec@users.noreply.github.com>
pkgname=libsecp256k1-fast
pkgname=libufsecp
pkgver=3.12.1
pkgrel=1
pkgdesc="High-performance secp256k1 elliptic curve cryptography library"
@ -8,7 +8,7 @@ url="https://github.com/shrec/UltrafastSecp256k1"
license=('AGPL-3.0-or-later')
depends=('gcc-libs')
makedepends=('cmake' 'ninja' 'gcc')
provides=('libsecp256k1-fast')
provides=('libufsecp')
source=("$url/archive/v$pkgver/UltrafastSecp256k1-$pkgver.tar.gz")
sha256sums=('SKIP')

View File

@ -1,4 +1,4 @@
libsecp256k1-fast (3.12.1-1) unstable; urgency=medium
libufsecp (3.12.1-1) unstable; urgency=medium
* New upstream release.
* Security: bump wheel 0.45.1 -> 0.46.2 (CVE-2026-24049).

View File

@ -1,4 +1,4 @@
Source: libsecp256k1-fast
Source: libufsecp
Section: libs
Priority: optional
Maintainer: shrec <shrec@users.noreply.github.com>
@ -13,7 +13,7 @@ Vcs-Git: https://github.com/shrec/UltrafastSecp256k1.git
Vcs-Browser: https://github.com/shrec/UltrafastSecp256k1
Rules-Requires-Root: no
Package: libsecp256k1-fast3
Package: libufsecp3
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends}, ${misc:Depends}
@ -26,11 +26,11 @@ Description: High-performance secp256k1 elliptic curve cryptography library
.
This package contains the shared library.
Package: libsecp256k1-fast-dev
Package: libufsecp-dev
Architecture: any
Multi-Arch: same
Section: libdevel
Depends: libsecp256k1-fast3 (= ${binary:Version}), ${misc:Depends}
Depends: libufsecp3 (= ${binary:Version}), ${misc:Depends}
Description: High-performance secp256k1 library — development files
UltrafastSecp256k1 is a high-performance implementation of the secp256k1
elliptic curve used by Bitcoin, Ethereum, and other cryptocurrencies.

View File

@ -1,6 +1,6 @@
%global soversion 3
Name: libsecp256k1-fast
Name: libufsecp
Version: 3.12.1
Release: 1%{?dist}
Summary: High-performance secp256k1 elliptic curve cryptography library

View File

@ -8,5 +8,5 @@ Description: High-performance secp256k1 elliptic curve cryptography library
URL: @PROJECT_HOMEPAGE_URL@
Version: @PROJECT_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lsecp256k1-fast-cpu
Libs: -L${libdir} -lfastsecp256k1
Libs.private: -lpthread