UltrafastSecp256k1/examples/nodejs_example/example.js
Vano Chkheidze d16f254f8f
fix: Metal device validation + GPU audit presets + docs + examples (#146)
* fix: CUDA RIPEMD160 r2 table + ECDH y-parity + GPU-side conversion

- hash160.cuh: fix transposed r2[46..47] (was 13,4 -> correct 4,13)
- ecdh.cuh: compute y-parity for SHA-256(02/03||x) to match CPU ecdh_compute
- gpu_backend_cuda.cu: GPU-side Jacobian<->compressed conversion
  via batch_jac_to_compressed_kernel and batch_compressed_to_jac_kernel;
  fix bytes_to_scalar/bytes_to_field byte order;
  add msm_reduce_and_compress_kernel for GPU-side MSM accumulation;
  remove dead host-side FieldElement code
- gpu/CMakeLists.txt: remove gpu_backend_cuda_host.cpp
- bindings/rust: add hex dev-dep, fix abi_version() call in smoke test
- bindings/nodejs: add koffi-based smoke test (Node 22 compatible)

GPU test results: 154 passed, 0 failed
  gpu_abi_gate:          44/44
  gpu_ops_equivalence:   55/55
  gpu_host_api_negative: 55/55

Binding tests:
  Rust (cargo test):     13/13
  Node.js (koffi):       12/12

* examples: add 6-language example suite (C, Python, Rust, Node.js, Go, Java)

Comprehensive CPU + GPU examples for all supported binding languages:

- C:       Direct C ABI calls, 16 demo sections (CPU + GPU)
- Python:  ctypes + Ufsecp wrapper, 14 sections (CPU + GPU)
- Rust:    Safe ufsecp crate wrapper, 9 sections (CPU only)
- Node.js: koffi FFI, 12 sections (CPU + GPU)
- Go:      Pure cgo, 14 sections (CPU + GPU)
- Java:    JNA FFI, 14 sections (CPU + GPU)

Each example covers: key generation, ECDSA (sign/verify/recover/DER),
Schnorr (BIP-340), ECDH, hashing (SHA-256, Hash160), Bitcoin addresses
(P2PKH, P2WPKH, P2TR), WIF encoding, BIP-32 HD derivation, and Taproot.

GPU examples demonstrate: backend discovery, batch key generation,
batch ECDSA verify, batch Hash160, and multi-scalar multiplication.

All 6 examples tested and verified against RTX 5060 Ti (CUDA + OpenCL).

* examples: add GPU+Pedersen to all 6 languages, expand README

- Rust: add GPU sections [10]-[15] + Pedersen, add GPU+Pedersen FFI to ufsecp-sys
- Node.js: add BIP-32, Taproot, Pedersen sections [8]-[10], renumber GPU [11]-[15]
- Python: add Pedersen section [10] via direct ctypes
- Go: add Pedersen section [10] via cgo
- Java: add Pedersen JNA declarations + section [10]
- Rust safe wrapper: add Context::as_ptr() for GPU FFI access
- examples/README.md: comprehensive rewrite with all 6 languages, build/run
  instructions, feature coverage matrix, embedded platforms, troubleshooting
- README.md: add Highlights, Performance, Architecture stack diagram,
  Hardware Compatibility table (16 platforms), Embedded Targets, Examples
  index, Use Cases section; expand Architecture with bindings + source tree

All 6 examples tested: C 16/16, Rust 15/15, Python 15/15, Node.js 15/15,
Go 15/15, Java 15/15 -- all sections pass including GPU operations.

* docs: fix GPU backend maturity labels, add docs links, clean repo hygiene

- .gitignore: add rules for example build artifacts (binaries, node_modules,
  target/, Cargo.lock, package-lock.json, .class files)
- README.md: fix GPU overclaims -- OpenCL is partial (4/6 ops), Metal is
  experimental (discovery only); add GPU API, Validation Matrix, Feature
  Maturity, Supported Guarantees, Examples to Documentation table
- FEATURE_MATURITY.md: fix contradictions -- ECDSA/Schnorr verify GPU column
  corrected from 'all 3' to 'CUDA' (OpenCL UNSUPPORTED per ufsecp_gpu.h);
  BIP-32 HD GPU corrected from 'all 3' to '-' (no GPU C ABI path)
- GPU_VALIDATION_MATRIX.md: add CI and Local Verification table documenting
  that CUDA/OpenCL tests pass locally (RTX 5060 Ti), GH Actions lacks GPU
  runners; all 4 GPU C ABI tests (gpu_abi_gate, gpu_ops_equivalence,
  gpu_host_api_negative, gpu_backend_matrix) confirmed in ctest matrix (49
  total) and pass

* fix: Metal device index validation + canonical GPU audit presets

- Metal backend: reject out-of-range device_index in init() before
  creating MetalRuntime (fixes gpu_host_api_negative on macOS CI)
- CMakePresets.json: add cuda-audit, cuda-audit-5060ti configure/build
  presets and testPresets for reproducible 49-test GPU verification
- docs/BUILDING.md: document canonical GPU audit build path
- docs/LOCAL_CI.md: add GPU proof-path quick-reference
- docs/README.md: update docs index entry

---------

Co-authored-by: shrec <shrec@users.noreply.github.com>
2026-03-16 02:42:54 +04:00

379 lines
16 KiB
JavaScript

/**
* UltrafastSecp256k1 -- Node.js Example (CPU + GPU)
*
* Demonstrates the ufsecp C ABI via koffi FFI: key ops, ECDSA, Schnorr,
* ECDH, hashing, Bitcoin addresses, WIF, BIP-32, Taproot, Pedersen,
* and GPU batch operations.
*
* Requirements:
* npm install koffi
*
* Run:
* UFSECP_LIB=../../build-linux/include/ufsecp/libufsecp.so node example.js
*/
'use strict';
const koffi = require('koffi');
const path = require('path');
// ── Load Library ─────────────────────────────────────────────────────────
const LIB_PATH = process.env.UFSECP_LIB ||
path.join(__dirname, '..', '..', 'build-linux', 'include', 'ufsecp', 'libufsecp.so');
const lib = koffi.load(LIB_PATH);
// ── CPU Function Declarations ────────────────────────────────────────────
const UFSECP_OK = 0;
// Context
const ctx_create = lib.func('int ufsecp_ctx_create(_Out_ void **ctx)');
const ctx_destroy = lib.func('void ufsecp_ctx_destroy(void *ctx)');
const abi_version = lib.func('uint32_t ufsecp_abi_version()');
const version_string = lib.func('const char *ufsecp_version_string()');
const error_str = lib.func('const char *ufsecp_error_str(int code)');
// Keys
const seckey_verify = lib.func('int ufsecp_seckey_verify(void *ctx, const uint8_t *k32)');
const pubkey_create = lib.func('int ufsecp_pubkey_create(void *ctx, const uint8_t *sk32, uint8_t *pk33)');
const pubkey_xonly = lib.func('int ufsecp_pubkey_xonly(void *ctx, const uint8_t *sk32, uint8_t *xo32)');
// ECDSA
const ecdsa_sign = lib.func('int ufsecp_ecdsa_sign(void *ctx, const uint8_t *msg32, const uint8_t *sk32, uint8_t *sig64)');
const ecdsa_verify = lib.func('int ufsecp_ecdsa_verify(void *ctx, const uint8_t *msg32, const uint8_t *sig64, const uint8_t *pk33)');
const ecdsa_sign_rec = lib.func('int ufsecp_ecdsa_sign_recoverable(void *ctx, const uint8_t *msg32, const uint8_t *sk32, uint8_t *sig64, _Out_ int *recid)');
const ecdsa_recover = lib.func('int ufsecp_ecdsa_recover(void *ctx, const uint8_t *msg32, const uint8_t *sig64, int recid, uint8_t *pk33)');
const ecdsa_to_der = lib.func('int ufsecp_ecdsa_sig_to_der(void *ctx, const uint8_t *sig64, uint8_t *der, _Inout_ size_t *len)');
// Schnorr
const schnorr_sign = lib.func('int ufsecp_schnorr_sign(void *ctx, const uint8_t *msg32, const uint8_t *sk32, const uint8_t *aux32, uint8_t *sig64)');
const schnorr_verify = lib.func('int ufsecp_schnorr_verify(void *ctx, const uint8_t *msg32, const uint8_t *sig64, const uint8_t *xo32)');
// ECDH
const ecdh = lib.func('int ufsecp_ecdh(void *ctx, const uint8_t *sk32, const uint8_t *pk33, uint8_t *secret32)');
// Hashing
const sha256 = lib.func('int ufsecp_sha256(const uint8_t *data, size_t len, uint8_t *digest32)');
const hash160 = lib.func('int ufsecp_hash160(const uint8_t *data, size_t len, uint8_t *digest20)');
// Addresses
const addr_p2pkh = lib.func('int ufsecp_addr_p2pkh(void *ctx, const uint8_t *pk33, int net, uint8_t *addr, _Inout_ size_t *len)');
const addr_p2wpkh = lib.func('int ufsecp_addr_p2wpkh(void *ctx, const uint8_t *pk33, int net, uint8_t *addr, _Inout_ size_t *len)');
const addr_p2tr = lib.func('int ufsecp_addr_p2tr(void *ctx, const uint8_t *xo32, int net, uint8_t *addr, _Inout_ size_t *len)');
// WIF
const wif_encode = lib.func('int ufsecp_wif_encode(void *ctx, const uint8_t *sk32, int comp, int net, uint8_t *wif, _Inout_ size_t *len)');
// BIP-32
const bip32_master = lib.func('int ufsecp_bip32_master(void *ctx, const uint8_t *seed, size_t seed_len, uint8_t *key82)');
const bip32_derive = lib.func('int ufsecp_bip32_derive_path(void *ctx, const uint8_t *master82, const char *path, uint8_t *key82)');
const bip32_privkey = lib.func('int ufsecp_bip32_privkey(void *ctx, const uint8_t *key82, uint8_t *priv32)');
const bip32_pubkey = lib.func('int ufsecp_bip32_pubkey(void *ctx, const uint8_t *key82, uint8_t *pub33)');
// Taproot
const taproot_output = lib.func('int ufsecp_taproot_output_key(void *ctx, const uint8_t *ix32, const uint8_t *mr, uint8_t *ox32, _Out_ int *parity)');
const taproot_verify = lib.func('int ufsecp_taproot_verify(void *ctx, const uint8_t *ox32, int parity, const uint8_t *ix32, const uint8_t *mr, size_t mr_len)');
// Pedersen
const pedersen_commit = lib.func('int ufsecp_pedersen_commit(void *ctx, const uint8_t *v32, const uint8_t *b32, uint8_t *c33)');
const pedersen_verify = lib.func('int ufsecp_pedersen_verify(void *ctx, const uint8_t *c33, const uint8_t *v32, const uint8_t *b32)');
// GPU
const gpu_backend_count = lib.func('uint32_t ufsecp_gpu_backend_count(_Out_ uint32_t *ids, uint32_t max)');
const gpu_backend_name = lib.func('const char *ufsecp_gpu_backend_name(uint32_t bid)');
const gpu_is_available = lib.func('int ufsecp_gpu_is_available(uint32_t bid)');
const gpu_device_count = lib.func('uint32_t ufsecp_gpu_device_count(uint32_t bid)');
const gpu_ctx_create = lib.func('int ufsecp_gpu_ctx_create(_Out_ void **ctx, uint32_t bid, uint32_t dev)');
const gpu_ctx_destroy = lib.func('void ufsecp_gpu_ctx_destroy(void *ctx)');
const gpu_generator_mul = lib.func('int ufsecp_gpu_generator_mul_batch(void *ctx, const uint8_t *s32, size_t n, uint8_t *pk33)');
const gpu_ecdsa_verify = lib.func('int ufsecp_gpu_ecdsa_verify_batch(void *ctx, const uint8_t *msg, const uint8_t *pk, const uint8_t *sig, size_t n, uint8_t *res)');
const gpu_hash160 = lib.func('int ufsecp_gpu_hash160_pubkey_batch(void *ctx, const uint8_t *pk33, size_t n, uint8_t *h20)');
const gpu_msm = lib.func('int ufsecp_gpu_msm(void *ctx, const uint8_t *s32, const uint8_t *p33, size_t n, uint8_t *out33)');
const gpu_error_str = lib.func('const char *ufsecp_gpu_error_str(int code)');
// ── Helpers ──────────────────────────────────────────────────────────────
function hex(buf) { return Buffer.from(buf).toString('hex'); }
function check(rc, op) {
if (rc !== UFSECP_OK) {
throw new Error(`${op} failed: ${error_str(rc)} (code ${rc})`);
}
}
function createCtx() {
const pp = [null];
check(ctx_create(pp), 'ctx_create');
return pp[0];
}
function getAddr(fn, ctx, key, net) {
const buf = Buffer.alloc(128);
const len = [128];
check(fn(ctx, key, net, buf, len), 'addr');
return buf.slice(0, len[0]).toString();
}
// ── Golden Vectors ───────────────────────────────────────────────────────
const PRIVKEY = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
const PRIVKEY2 = Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex');
// ── CPU Demo ─────────────────────────────────────────────────────────────
function demoCPU() {
console.log('=== CPU Operations ===\n');
const ctx = createCtx();
// 1. Key Generation
console.log('[1] Key Generation');
const pub33 = Buffer.alloc(33);
const xonly = Buffer.alloc(32);
check(pubkey_create(ctx, PRIVKEY, pub33), 'pubkey_create');
check(pubkey_xonly(ctx, PRIVKEY, xonly), 'pubkey_xonly');
console.log(` Private key: ${hex(PRIVKEY)}`);
console.log(` Compressed (33B): ${hex(pub33)}`);
console.log(` X-only (32B): ${hex(xonly)}`);
console.log();
// 2. ECDSA
console.log('[2] ECDSA Sign / Verify (RFC 6979)');
const msg = Buffer.alloc(32);
check(sha256(Buffer.from('Hello UltrafastSecp256k1!'), 24, msg), 'sha256');
console.log(` Message hash: ${hex(msg)}`);
const sig = Buffer.alloc(64);
check(ecdsa_sign(ctx, msg, PRIVKEY, sig), 'ecdsa_sign');
console.log(` ECDSA signature: ${hex(sig)}`);
const vrc = ecdsa_verify(ctx, msg, sig, pub33);
console.log(` Verify: ${vrc === UFSECP_OK ? 'VALID' : 'INVALID'}`);
// DER encoding
const der = Buffer.alloc(72);
const derLen = [72];
check(ecdsa_to_der(ctx, sig, der, derLen), 'sig_to_der');
console.log(` DER length: ${derLen[0]} bytes`);
// Recovery
const rsig = Buffer.alloc(64);
const recid = [0];
check(ecdsa_sign_rec(ctx, msg, PRIVKEY, rsig, recid), 'sign_recoverable');
const recovered = Buffer.alloc(33);
check(ecdsa_recover(ctx, msg, rsig, recid[0], recovered), 'recover');
console.log(` Recovery: recid=${recid[0]}, match=${recovered.equals(pub33) ? 'YES' : 'NO'}`);
console.log();
// 3. Schnorr
console.log('[3] Schnorr Sign / Verify (BIP-340)');
const aux = Buffer.alloc(32);
const schnorrSig = Buffer.alloc(64);
check(schnorr_sign(ctx, msg, PRIVKEY, aux, schnorrSig), 'schnorr_sign');
console.log(` Schnorr signature: ${hex(schnorrSig)}`);
const sv = schnorr_verify(ctx, msg, schnorrSig, xonly);
console.log(` Verify: ${sv === UFSECP_OK ? 'VALID' : 'INVALID'}`);
console.log();
// 4. ECDH
console.log('[4] ECDH Key Agreement');
const pub2 = Buffer.alloc(33);
check(pubkey_create(ctx, PRIVKEY2, pub2), 'pubkey2');
const secretA = Buffer.alloc(32);
const secretB = Buffer.alloc(32);
check(ecdh(ctx, PRIVKEY, pub2, secretA), 'ecdh_a');
check(ecdh(ctx, PRIVKEY2, pub33, secretB), 'ecdh_b');
console.log(` Secret (A->B): ${hex(secretA)}`);
console.log(` Secret (B->A): ${hex(secretB)}`);
console.log(` Match: ${secretA.equals(secretB) ? 'YES' : 'NO'}`);
console.log();
// 5. Hashing
console.log('[5] Hashing');
const sha = Buffer.alloc(32);
const h160 = Buffer.alloc(20);
check(sha256(pub33, 33, sha), 'sha256_pub');
check(hash160(pub33, 33, h160), 'hash160_pub');
console.log(` SHA-256(pubkey): ${hex(sha)}`);
console.log(` Hash160(pubkey): ${hex(h160)}`);
console.log();
// 6. Bitcoin Addresses
console.log('[6] Bitcoin Addresses');
console.log(` P2PKH: ${getAddr(addr_p2pkh, ctx, pub33, 0)}`);
console.log(` P2WPKH: ${getAddr(addr_p2wpkh, ctx, pub33, 0)}`);
console.log(` P2TR: ${getAddr(addr_p2tr, ctx, xonly, 0)}`);
console.log();
// 7. WIF
console.log('[7] WIF Encoding');
const wifBuf = Buffer.alloc(128);
const wifLen = [128];
check(wif_encode(ctx, PRIVKEY, 1, 0, wifBuf, wifLen), 'wif_encode');
const wif = wifBuf.slice(0, wifLen[0]).toString();
console.log(` WIF: ${wif}`);
console.log();
// 8. BIP-32
console.log('[8] BIP-32 HD Key Derivation');
const seed = Buffer.alloc(64, 0x42);
const masterKey = Buffer.alloc(82);
check(bip32_master(ctx, seed, 64, masterKey), 'bip32_master');
const childKey = Buffer.alloc(82);
check(bip32_derive(ctx, masterKey, "m/44'/0'/0'/0/0", childKey), 'bip32_derive');
const childPriv = Buffer.alloc(32);
const childPub = Buffer.alloc(33);
check(bip32_privkey(ctx, childKey, childPriv), 'bip32_privkey');
check(bip32_pubkey(ctx, childKey, childPub), 'bip32_pubkey');
console.log(` BIP-32 child priv: ${hex(childPriv)}`);
console.log(` BIP-32 child pub: ${hex(childPub)}`);
console.log();
// 9. Taproot
console.log('[9] Taproot (BIP-341)');
const tapOut = Buffer.alloc(32);
const tapParity = [0];
check(taproot_output(ctx, xonly, null, tapOut, tapParity), 'taproot_output');
console.log(` Output key: ${hex(tapOut)}`);
console.log(` Parity: ${tapParity[0]}`);
const tapVrc = taproot_verify(ctx, tapOut, tapParity[0], xonly, null, 0);
console.log(` Verify: ${tapVrc === UFSECP_OK ? 'VALID' : 'INVALID'}`);
console.log();
// 10. Pedersen
console.log('[10] Pedersen Commitment');
const pedVal = Buffer.alloc(32); pedVal[31] = 42;
const pedBlind = Buffer.alloc(32); pedBlind[31] = 7;
const pedCommit = Buffer.alloc(33);
check(pedersen_commit(ctx, pedVal, pedBlind, pedCommit), 'pedersen_commit');
console.log(` Commitment: ${hex(pedCommit)}`);
const pvrc = pedersen_verify(ctx, pedCommit, pedVal, pedBlind);
console.log(` Verify: ${pvrc === UFSECP_OK ? 'VALID' : 'INVALID'}`);
console.log();
ctx_destroy(ctx);
}
// ── GPU Demo ─────────────────────────────────────────────────────────────
function demoGPU() {
console.log('=== GPU Operations ===\n');
// 11. Backend Discovery
console.log('[11] GPU Backend Discovery');
const bids = new Uint32Array(4);
const nBackends = gpu_backend_count(bids, 4);
console.log(` Backends compiled: ${nBackends}`);
let useBackend = 0;
for (let i = 0; i < nBackends; i++) {
const bid = bids[i];
const name = gpu_backend_name(bid);
const avail = gpu_is_available(bid);
const devs = gpu_device_count(bid);
console.log(` Backend ${bid}: ${name.padEnd(8)} available=${avail} devices=${devs}`);
if (avail && !useBackend) useBackend = bid;
}
if (!useBackend) {
console.log(' No GPU backends available -- skipping GPU demos.\n');
return;
}
// Create GPU context
const gpp = [null];
const grc = gpu_ctx_create(gpp, useBackend, 0);
if (grc !== UFSECP_OK) {
console.log(` GPU context creation failed: ${gpu_error_str(grc)}`);
return;
}
const gpu = gpp[0];
const N = 4;
// 12. Batch Key Generation
console.log('\n[12] GPU Batch Key Generation (4 keys)');
const scalars = Buffer.alloc(N * 32);
for (let i = 0; i < N; i++) scalars[i * 32 + 31] = i + 1;
const pubkeys = Buffer.alloc(N * 33);
let rc = gpu_generator_mul(gpu, scalars, N, pubkeys);
if (rc === UFSECP_OK) {
for (let i = 0; i < N; i++) {
console.log(` GPU pubkey[${i}]: ${hex(pubkeys.slice(i*33, (i+1)*33))}`);
}
} else {
console.log(` gpu_generator_mul_batch: ${gpu_error_str(rc)}`);
}
// 13. ECDSA Batch Verify
console.log('\n[13] GPU ECDSA Batch Verify');
const cpuCtx = createCtx();
const msgs = Buffer.alloc(N * 32);
const sigs = Buffer.alloc(N * 64);
const pubs = Buffer.alloc(N * 33);
for (let i = 0; i < N; i++) {
const msgHash = Buffer.alloc(32);
sha256(Buffer.from([i]), 1, msgHash);
msgHash.copy(msgs, i * 32);
const sk = Buffer.alloc(32);
sk[31] = i + 1;
const s = Buffer.alloc(64);
ecdsa_sign(cpuCtx, msgHash, sk, s);
s.copy(sigs, i * 64);
pubkeys.copy(pubs, i * 33, i * 33, (i + 1) * 33);
}
ctx_destroy(cpuCtx);
const results = Buffer.alloc(N);
rc = gpu_ecdsa_verify(gpu, msgs, pubs, sigs, N, results);
if (rc === UFSECP_OK) {
const parts = [];
for (let i = 0; i < N; i++) parts.push(`[${i}]=${results[i] ? 'VALID' : 'INVALID'}`);
console.log(` Results: ${parts.join(' ')}`);
} else {
console.log(` gpu_ecdsa_verify_batch: ${gpu_error_str(rc)}`);
}
// 14. Hash160 Batch
console.log('\n[14] GPU Hash160 Batch');
const hashes = Buffer.alloc(N * 20);
rc = gpu_hash160(gpu, pubkeys, N, hashes);
if (rc === UFSECP_OK) {
for (let i = 0; i < N; i++) {
console.log(` Hash160[${i}]: ${hex(hashes.slice(i*20, (i+1)*20))}`);
}
} else {
console.log(` gpu_hash160_pubkey_batch: ${gpu_error_str(rc)}`);
}
// 15. MSM
console.log('\n[15] GPU Multi-Scalar Multiplication');
const msmResult = Buffer.alloc(33);
rc = gpu_msm(gpu, scalars, pubkeys, N, msmResult);
if (rc === UFSECP_OK) {
console.log(` MSM result: ${hex(msmResult)}`);
} else {
console.log(` gpu_msm: ${gpu_error_str(rc)}`);
}
gpu_ctx_destroy(gpu);
console.log();
}
// ── Main ─────────────────────────────────────────────────────────────────
console.log('UltrafastSecp256k1 -- Node.js Example');
console.log(`ABI version: ${abi_version()}`);
console.log(`Library: ${version_string()}`);
console.log();
demoCPU();
demoGPU();
console.log('All examples completed successfully.');