@l.x/hashcash-native 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/NitroHashcashNative.podspec +56 -0
  2. package/README.md +24 -0
  3. package/android/CMakeLists.txt +29 -0
  4. package/android/build.gradle +141 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/hashcashnative/HybridHashcash.kt +113 -0
  10. package/android/src/main/java/com/margelo/nitro/hashcashnative/NitroHashcashNativePackage.kt +18 -0
  11. package/ios/Bridge.h +8 -0
  12. package/ios/HybridHashcash.swift +133 -0
  13. package/lib/benchmark.d.ts +74 -0
  14. package/lib/benchmark.d.ts.map +1 -0
  15. package/lib/benchmark.js +92 -0
  16. package/lib/benchmark.js.map +1 -0
  17. package/lib/index.d.ts +11 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +10 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/specs/Hashcash.nitro.d.ts +59 -0
  22. package/lib/specs/Hashcash.nitro.d.ts.map +1 -0
  23. package/lib/specs/Hashcash.nitro.js +2 -0
  24. package/lib/specs/Hashcash.nitro.js.map +1 -0
  25. package/nitro.json +18 -0
  26. package/nitrogen/generated/.gitattributes +1 -0
  27. package/nitrogen/generated/android/NitroHashcashNative+autolinking.cmake +81 -0
  28. package/nitrogen/generated/android/NitroHashcashNative+autolinking.gradle +27 -0
  29. package/nitrogen/generated/android/NitroHashcashNativeOnLoad.cpp +44 -0
  30. package/nitrogen/generated/android/NitroHashcashNativeOnLoad.hpp +25 -0
  31. package/nitrogen/generated/android/c++/JFindProofParams.hpp +68 -0
  32. package/nitrogen/generated/android/c++/JHashcashChallenge.hpp +69 -0
  33. package/nitrogen/generated/android/c++/JHashcashProofResult.hpp +69 -0
  34. package/nitrogen/generated/android/c++/JHybridHashcashSpec.cpp +81 -0
  35. package/nitrogen/generated/android/c++/JHybridHashcashSpec.hpp +66 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/FindProofParams.kt +44 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HashcashChallenge.kt +47 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HashcashProofResult.kt +47 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HybridHashcashSpec.kt +62 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/NitroHashcashNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/ios/NitroHashcashNative+autolinking.rb +60 -0
  42. package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Bridge.cpp +49 -0
  43. package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Bridge.hpp +154 -0
  44. package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Umbrella.hpp +55 -0
  45. package/nitrogen/generated/ios/NitroHashcashNativeAutolinking.mm +33 -0
  46. package/nitrogen/generated/ios/NitroHashcashNativeAutolinking.swift +25 -0
  47. package/nitrogen/generated/ios/c++/HybridHashcashSpecSwift.cpp +11 -0
  48. package/nitrogen/generated/ios/c++/HybridHashcashSpecSwift.hpp +92 -0
  49. package/nitrogen/generated/ios/swift/FindProofParams.swift +82 -0
  50. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
  51. package/nitrogen/generated/ios/swift/Func_void_std__optional_HashcashProofResult_.swift +47 -0
  52. package/nitrogen/generated/ios/swift/HashcashChallenge.swift +69 -0
  53. package/nitrogen/generated/ios/swift/HashcashProofResult.swift +69 -0
  54. package/nitrogen/generated/ios/swift/HybridHashcashSpec.swift +57 -0
  55. package/nitrogen/generated/ios/swift/HybridHashcashSpec_cxx.swift +155 -0
  56. package/nitrogen/generated/shared/c++/FindProofParams.hpp +85 -0
  57. package/nitrogen/generated/shared/c++/HashcashChallenge.hpp +87 -0
  58. package/nitrogen/generated/shared/c++/HashcashProofResult.hpp +87 -0
  59. package/nitrogen/generated/shared/c++/HybridHashcashSpec.cpp +22 -0
  60. package/nitrogen/generated/shared/c++/HybridHashcashSpec.hpp +69 -0
  61. package/package.json +73 -1
  62. package/react-native.config.js +16 -0
  63. package/src/benchmark.ts +152 -0
  64. package/src/index.ts +23 -0
  65. package/src/specs/Hashcash.nitro.ts +64 -0
  66. package/index.d.ts +0 -1
  67. package/index.js +0 -1
@@ -0,0 +1,56 @@
1
+ require "json"
2
+
3
+ # Dynamically resolve the nitro_pod_utils path from the workspace root
4
+ # (static require_relative doesn't work for workspace packages due to symlinks)
5
+ # Pod::Config.instance.installation_root is the ios folder, so we go up to find workspace root
6
+ workspace_root = File.expand_path("../../..", Pod::Config.instance.installation_root.to_s)
7
+ nitro_utils_path = File.join(workspace_root, "node_modules", "react-native-nitro-modules", "nitro_pod_utils")
8
+ require nitro_utils_path
9
+
10
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
11
+
12
+ Pod::Spec.new do |s|
13
+ s.name = "NitroHashcashNative"
14
+ s.version = package["version"]
15
+ s.summary = package["description"]
16
+ s.homepage = package["homepage"]
17
+ s.license = package["license"]
18
+ s.authors = package["author"]
19
+
20
+ s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 }
21
+ s.source = { :git => "https://github.com/Uniswap/universe.git", :tag => "#{s.version}" }
22
+
23
+ s.source_files = [
24
+ # Implementation (Swift)
25
+ "ios/**/*.{swift}",
26
+ # Autolinking/Registration (Objective-C++)
27
+ "ios/**/*.{m,mm}",
28
+ # Implementation (C++ objects)
29
+ "cpp/**/*.{hpp,cpp}",
30
+ ]
31
+
32
+ load 'nitrogen/generated/ios/NitroHashcashNative+autolinking.rb'
33
+ add_nitrogen_files(s)
34
+
35
+ xcconfig = {
36
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
37
+ "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
38
+ "DEFINES_MODULE" => "YES",
39
+ }
40
+
41
+ if has_react_native()
42
+ react_native_version = get_react_native_version()
43
+ if (react_native_version < 80)
44
+ current_header_search_paths = Array(xcconfig["HEADER_SEARCH_PATHS"])
45
+ xcconfig["HEADER_SEARCH_PATHS"] = current_header_search_paths + ["${PODS_ROOT}/RCT-Folly"]
46
+ xcconfig["GCC_PREPROCESSOR_DEFINITIONS"] = "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
47
+ xcconfig["OTHER_CPLUSPLUSFLAGS"] = "$(inherited) -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1"
48
+ end
49
+ end
50
+
51
+ s.pod_target_xcconfig = xcconfig
52
+
53
+ s.dependency 'React-jsi'
54
+ s.dependency 'React-callinvoker'
55
+ install_modules_dependencies(s)
56
+ end
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # hashcash-native
2
+
3
+ A React Native Nitro Module providing native hashcash proof-of-work solving for iOS and Android.
4
+
5
+ ## Overview
6
+
7
+ This package provides native implementations for computing hashcash proofs, which are used for proof-of-work challenges. Native implementations offer significantly better performance than JavaScript-based solutions.
8
+
9
+ ## Structure
10
+
11
+ - `android/` - Android JNI/Kotlin implementation
12
+ - `ios/` - iOS Swift implementation
13
+ - `cpp/` - Cross-platform C++ implementation (to be added)
14
+ - `src/` - TypeScript interface definitions
15
+ - `nitrogen/` - Generated Nitro bindings
16
+
17
+ ## Development
18
+
19
+ This package uses [Nitro Modules](https://nitro.margelo.com/) for native bindings.
20
+
21
+ ```bash
22
+ # Generate Nitro bindings
23
+ bun run specs
24
+ ```
@@ -0,0 +1,29 @@
1
+ project(NitroHashcashNative)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set (PACKAGE_NAME NitroHashcashNative)
5
+ set (CMAKE_VERBOSE_MAKEFILE ON)
6
+ set (CMAKE_CXX_STANDARD 20)
7
+
8
+ # Define C++ library and add all sources
9
+ add_library(${PACKAGE_NAME} SHARED
10
+ src/main/cpp/cpp-adapter.cpp
11
+ )
12
+
13
+ # Add Nitrogen specs :)
14
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroHashcashNative+autolinking.cmake)
15
+
16
+ # Set up local includes
17
+ include_directories(
18
+ "src/main/cpp"
19
+ "../cpp"
20
+ )
21
+
22
+ find_library(LOG_LIB log)
23
+
24
+ # Link all libraries together
25
+ target_link_libraries(
26
+ ${PACKAGE_NAME}
27
+ ${LOG_LIB}
28
+ android # <-- Android core
29
+ )
@@ -0,0 +1,141 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:8.13.1"
9
+ }
10
+ }
11
+
12
+ def reactNativeArchitectures() {
13
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
14
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: 'org.jetbrains.kotlin.android'
23
+ apply from: '../nitrogen/generated/android/NitroHashcashNative+autolinking.gradle'
24
+ apply from: "./fix-prefab.gradle"
25
+
26
+ if (isNewArchitectureEnabled()) {
27
+ apply plugin: "com.facebook.react"
28
+ }
29
+
30
+ def getExtOrDefault(name) {
31
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroHashcashNative_" + name]
32
+ }
33
+
34
+ def getExtOrIntegerDefault(name) {
35
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroHashcashNative_" + name]).toInteger()
36
+ }
37
+
38
+ android {
39
+ namespace "com.margelo.nitro.hashcashnative"
40
+
41
+ ndkVersion getExtOrDefault("ndkVersion")
42
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
43
+
44
+ defaultConfig {
45
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
46
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
47
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
48
+
49
+ externalNativeBuild {
50
+ cmake {
51
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
52
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
53
+ abiFilters (*reactNativeArchitectures())
54
+
55
+ buildTypes {
56
+ debug {
57
+ cppFlags "-O1 -g"
58
+ }
59
+ release {
60
+ cppFlags "-O2"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ externalNativeBuild {
68
+ cmake {
69
+ path "CMakeLists.txt"
70
+ }
71
+ }
72
+
73
+ packagingOptions {
74
+ excludes = [
75
+ "META-INF",
76
+ "META-INF/**",
77
+ "**/libc++_shared.so",
78
+ "**/libfbjni.so",
79
+ "**/libjsi.so",
80
+ "**/libfolly_json.so",
81
+ "**/libfolly_runtime.so",
82
+ "**/libglog.so",
83
+ "**/libhermes.so",
84
+ "**/libhermes-executor-debug.so",
85
+ "**/libhermes_executor.so",
86
+ "**/libreactnative.so",
87
+ "**/libreactnativejni.so",
88
+ "**/libturbomodulejsijni.so",
89
+ "**/libreact_nativemodule_core.so",
90
+ "**/libjscexecutor.so"
91
+ ]
92
+ }
93
+
94
+ buildFeatures {
95
+ buildConfig true
96
+ prefab true
97
+ }
98
+
99
+ buildTypes {
100
+ release {
101
+ minifyEnabled false
102
+ }
103
+ }
104
+
105
+ lintOptions {
106
+ disable "GradleCompatible"
107
+ }
108
+
109
+ compileOptions {
110
+ sourceCompatibility JavaVersion.VERSION_1_8
111
+ targetCompatibility JavaVersion.VERSION_1_8
112
+ }
113
+
114
+ sourceSets {
115
+ main {
116
+ if (isNewArchitectureEnabled()) {
117
+ java.srcDirs += [
118
+ // React Codegen files
119
+ "${project.buildDir}/generated/source/codegen/java"
120
+ ]
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ repositories {
127
+ mavenCentral()
128
+ google()
129
+ }
130
+
131
+
132
+ dependencies {
133
+ // For < 0.71, this will be from the local maven repo
134
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
135
+ //noinspection GradleDynamicVersion
136
+ implementation "com.facebook.react:react-native:+"
137
+
138
+ // Add a dependency on NitroModules
139
+ implementation project(":react-native-nitro-modules")
140
+ }
141
+
@@ -0,0 +1,51 @@
1
+ tasks.configureEach { task ->
2
+ // Make sure that we generate our prefab publication file only after having built the native library
3
+ // so that not a header publication file, but a full configuration publication will be generated, which
4
+ // will include the .so file
5
+
6
+ def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
7
+ def matcher = task.name =~ prefabConfigurePattern
8
+ if (matcher.matches()) {
9
+ def variantName = matcher[0][1]
10
+ task.outputs.upToDateWhen { false }
11
+ task.dependsOn("externalNativeBuild${variantName}")
12
+ }
13
+ }
14
+
15
+ afterEvaluate {
16
+ def abis = reactNativeArchitectures()
17
+ rootProject.allprojects.each { proj ->
18
+ if (proj === rootProject) return
19
+
20
+ def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
21
+ config.dependencies.any { dep ->
22
+ dep.group == project.group && dep.name == project.name
23
+ }
24
+ }
25
+ if (!dependsOnThisLib && proj != project) return
26
+
27
+ if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
28
+ return
29
+ }
30
+
31
+ def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
32
+ // Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
33
+ // generate a libnameConfig.cmake file that will contain our native library (.so).
34
+ // See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
35
+ variants.all { variant ->
36
+ def variantName = variant.name
37
+ abis.each { abi ->
38
+ def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
39
+ if (!searchDir.exists()) return
40
+ def matches = []
41
+ searchDir.eachDir { randomDir ->
42
+ def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
43
+ if (prefabFile.exists()) matches << prefabFile
44
+ }
45
+ matches.each { prefabConfig ->
46
+ prefabConfig.setLastModified(System.currentTimeMillis())
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,5 @@
1
+ NitroHashcashNative_kotlinVersion=2.0.21
2
+ NitroHashcashNative_minSdkVersion=23
3
+ NitroHashcashNative_targetSdkVersion=35
4
+ NitroHashcashNative_compileSdkVersion=35
5
+ NitroHashcashNative_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,6 @@
1
+ #include <jni.h>
2
+ #include "NitroHashcashNativeOnLoad.hpp"
3
+
4
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::hashcashnative::initialize(vm);
6
+ }
@@ -0,0 +1,113 @@
1
+ package com.margelo.nitro.hashcashnative
2
+
3
+ import com.margelo.nitro.core.Promise
4
+ import java.security.MessageDigest
5
+ import java.util.Base64
6
+ import java.util.concurrent.atomic.AtomicBoolean
7
+
8
+ /**
9
+ * Native hashcash proof-of-work solver for Android.
10
+ *
11
+ * Uses java.security.MessageDigest for SHA256 hashing. The computation
12
+ * runs on a background thread via Nitro's Promise.async to avoid blocking
13
+ * the main/JS thread.
14
+ *
15
+ * Performance notes:
16
+ * - Cancellation is checked every 1000 iterations to balance responsiveness
17
+ * with cancellation check overhead
18
+ * - AtomicBoolean provides thread-safe access to the cancellation flag
19
+ * - Native MessageDigest is significantly faster than JS crypto
20
+ */
21
+ class HybridHashcash : HybridHashcashSpec() {
22
+ /**
23
+ * Thread-safe cancellation flag using AtomicBoolean.
24
+ * Checked every 1000 iterations to allow responsive cancellation
25
+ * without excessive overhead.
26
+ */
27
+ private val cancelled = AtomicBoolean(false)
28
+
29
+ /**
30
+ * Find a proof-of-work solution for the given challenge.
31
+ *
32
+ * Iterates through counter values computing SHA256 hashes until
33
+ * one is found with the required number of leading zero bytes.
34
+ */
35
+ override fun findProof(params: FindProofParams): Promise<HashcashProofResult?> {
36
+ return Promise.async {
37
+ cancelled.set(false)
38
+
39
+ val challenge = params.challenge
40
+ val rangeStart = params.rangeStart?.toInt() ?: 0
41
+ val rangeSize = params.rangeSize?.toInt() ?: challenge.maxProofLength.toInt()
42
+ val rangeEnd = rangeStart + rangeSize
43
+
44
+ val startTime = System.currentTimeMillis()
45
+ val digest = MessageDigest.getInstance("SHA-256")
46
+
47
+ for (counter in rangeStart until rangeEnd) {
48
+ // Check cancellation every 1000 iterations
49
+ if (counter % 1000 == 0 && cancelled.get()) {
50
+ return@async null
51
+ }
52
+
53
+ // Compute hash for this counter
54
+ val hash = computeHash(digest, challenge.subject, challenge.nonce, counter)
55
+
56
+ // Check if hash meets difficulty requirement
57
+ if (checkDifficulty(hash, challenge.difficulty.toInt())) {
58
+ val timeMs = (System.currentTimeMillis() - startTime).toDouble()
59
+
60
+ return@async HashcashProofResult(
61
+ counter = counter.toString(),
62
+ hashBase64 = Base64.getEncoder().encodeToString(hash),
63
+ attempts = (counter - rangeStart + 1).toDouble(),
64
+ timeMs = timeMs
65
+ )
66
+ }
67
+ }
68
+
69
+ null
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Cancel any in-progress proof search.
75
+ */
76
+ override fun cancel() {
77
+ cancelled.set(true)
78
+ }
79
+
80
+ // MARK: - Private Helpers
81
+
82
+ /**
83
+ * Compute SHA256 hash of "{subject}:{nonce}:{counter}"
84
+ */
85
+ private fun computeHash(
86
+ digest: MessageDigest,
87
+ subject: String,
88
+ nonce: String,
89
+ counter: Int
90
+ ): ByteArray {
91
+ val solutionString = "$subject:$nonce:$counter"
92
+ digest.reset()
93
+ return digest.digest(solutionString.toByteArray(Charsets.UTF_8))
94
+ }
95
+
96
+ /**
97
+ * Check if hash has required number of leading zero bytes.
98
+ *
99
+ * difficulty=1 means first byte must be 0x00
100
+ * difficulty=2 means first two bytes must be 0x00, etc.
101
+ */
102
+ private fun checkDifficulty(hash: ByteArray, difficulty: Int): Boolean {
103
+ if (hash.size < difficulty) return false
104
+
105
+ for (i in 0 until difficulty) {
106
+ if (hash[i] != 0.toByte()) {
107
+ return false
108
+ }
109
+ }
110
+
111
+ return true
112
+ }
113
+ }
@@ -0,0 +1,18 @@
1
+ package com.margelo.nitro.hashcashnative
2
+
3
+ import com.facebook.react.bridge.NativeModule
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.module.model.ReactModuleInfoProvider
6
+ import com.facebook.react.BaseReactPackage
7
+
8
+ class NitroHashcashNativePackage : BaseReactPackage() {
9
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
10
+
11
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
12
+
13
+ companion object {
14
+ init {
15
+ NitroHashcashNativeOnLoad.initializeNative()
16
+ }
17
+ }
18
+ }
package/ios/Bridge.h ADDED
@@ -0,0 +1,8 @@
1
+ //
2
+ // Bridge.h
3
+ // NitroHashcashNative
4
+ //
5
+ // Created by Uniswap Labs
6
+ //
7
+
8
+ #pragma once
@@ -0,0 +1,133 @@
1
+ import Foundation
2
+ import CommonCrypto
3
+ import NitroModules
4
+
5
+ /**
6
+ * Native hashcash proof-of-work solver for iOS.
7
+ *
8
+ * Uses CommonCrypto for SHA256 hashing. The computation runs on a
9
+ * background thread via Nitro's Promise.async to avoid blocking
10
+ * the main/JS thread.
11
+ *
12
+ * Performance notes:
13
+ * - Cancellation is checked every 1000 iterations to balance responsiveness
14
+ * with cancellation check overhead
15
+ * - NSLock provides thread-safe access to the cancellation flag
16
+ * (Swift's standard pattern for thread-safe booleans, equivalent to
17
+ * Java's AtomicBoolean pattern)
18
+ * - Native CommonCrypto is significantly faster than JS crypto
19
+ */
20
+ class HybridHashcash: HybridHashcashSpec {
21
+ /// Thread-safe cancellation flag. Checked every 1000 iterations
22
+ /// to allow responsive cancellation without excessive overhead.
23
+ private var cancelled = false
24
+
25
+ /// NSLock for thread-safe cancellation flag access.
26
+ /// Swift's standard pattern for synchronizing access to shared state.
27
+ private let lock = NSLock()
28
+
29
+ private var isCancelled: Bool {
30
+ lock.lock()
31
+ defer { lock.unlock() }
32
+ return cancelled
33
+ }
34
+
35
+ private func setCancelled(_ value: Bool) {
36
+ lock.lock()
37
+ defer { lock.unlock() }
38
+ cancelled = value
39
+ }
40
+
41
+ /**
42
+ * Find a proof-of-work solution for the given challenge.
43
+ *
44
+ * Iterates through counter values computing SHA256 hashes until
45
+ * one is found with the required number of leading zero bytes.
46
+ */
47
+ func findProof(params: FindProofParams) throws -> Promise<HashcashProofResult?> {
48
+ return Promise.async { [weak self] in
49
+ guard let self = self else { return nil }
50
+
51
+ self.setCancelled(false)
52
+
53
+ let challenge = params.challenge
54
+ let rangeStart = Int(params.rangeStart ?? 0)
55
+ let rangeSize = Int(params.rangeSize ?? challenge.maxProofLength)
56
+ let rangeEnd = rangeStart + rangeSize
57
+
58
+ let startTime = CFAbsoluteTimeGetCurrent()
59
+
60
+ for counter in rangeStart..<rangeEnd {
61
+ // Check cancellation every 1000 iterations
62
+ if counter % 1000 == 0 && self.isCancelled {
63
+ return nil
64
+ }
65
+
66
+ // Compute hash for this counter
67
+ let hash = self.computeHash(
68
+ subject: challenge.subject,
69
+ nonce: challenge.nonce,
70
+ counter: counter
71
+ )
72
+
73
+ // Check if hash meets difficulty requirement
74
+ if self.checkDifficulty(hash: hash, difficulty: Int(challenge.difficulty)) {
75
+ let timeMs = (CFAbsoluteTimeGetCurrent() - startTime) * 1000
76
+
77
+ return HashcashProofResult(
78
+ counter: String(counter),
79
+ hashBase64: hash.base64EncodedString(),
80
+ attempts: Double(counter - rangeStart + 1),
81
+ timeMs: timeMs
82
+ )
83
+ }
84
+ }
85
+
86
+ return nil
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Cancel any in-progress proof search.
92
+ */
93
+ func cancel() throws {
94
+ setCancelled(true)
95
+ }
96
+
97
+ // MARK: - Private Helpers
98
+
99
+ /**
100
+ * Compute SHA256 hash of "{subject}:{nonce}:{counter}"
101
+ */
102
+ private func computeHash(subject: String, nonce: String, counter: Int) -> Data {
103
+ let solutionString = "\(subject):\(nonce):\(counter)"
104
+ guard let data = solutionString.data(using: .utf8) else {
105
+ return Data()
106
+ }
107
+
108
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
109
+ data.withUnsafeBytes { bytes in
110
+ _ = CC_SHA256(bytes.baseAddress, CC_LONG(data.count), &hash)
111
+ }
112
+
113
+ return Data(hash)
114
+ }
115
+
116
+ /**
117
+ * Check if hash has required number of leading zero bytes.
118
+ *
119
+ * difficulty=1 means first byte must be 0x00
120
+ * difficulty=2 means first two bytes must be 0x00, etc.
121
+ */
122
+ private func checkDifficulty(hash: Data, difficulty: Int) -> Bool {
123
+ guard hash.count >= difficulty else { return false }
124
+
125
+ for i in 0..<difficulty {
126
+ if hash[i] != 0 {
127
+ return false
128
+ }
129
+ }
130
+
131
+ return true
132
+ }
133
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Hashcash Benchmark Utility
3
+ *
4
+ * Compares native vs JS hashcash implementation performance.
5
+ * Run this in your app to measure the speedup from native code.
6
+ */
7
+ /** biome-ignore-all lint/suspicious/noConsole: dev only file */
8
+ interface BenchmarkResult {
9
+ implementation: 'native' | 'js';
10
+ difficulty: number;
11
+ counter: string | null;
12
+ attempts: number;
13
+ timeMs: number;
14
+ }
15
+ interface BenchmarkSummary {
16
+ native: BenchmarkResult;
17
+ js: BenchmarkResult;
18
+ speedup: number;
19
+ }
20
+ /**
21
+ * Run a single benchmark with the native implementation
22
+ */
23
+ export declare function benchmarkNative(difficulty: number): Promise<BenchmarkResult>;
24
+ /**
25
+ * Run a single benchmark with the JS implementation
26
+ * Requires passing in the findProof function from @l.x/sessions
27
+ */
28
+ export declare function benchmarkJS(difficulty: number, findProof: (params: {
29
+ challenge: {
30
+ difficulty: number;
31
+ subject: string;
32
+ algorithm: 'sha256';
33
+ nonce: string;
34
+ max_proof_length: number;
35
+ };
36
+ }) => {
37
+ counter: string;
38
+ attempts: number;
39
+ timeMs: number;
40
+ } | null): Promise<BenchmarkResult>;
41
+ /**
42
+ * Run a comparison benchmark between native and JS implementations
43
+ */
44
+ export declare function runBenchmark(difficulty: number, jsFindProof: (params: {
45
+ challenge: {
46
+ difficulty: number;
47
+ subject: string;
48
+ algorithm: 'sha256';
49
+ nonce: string;
50
+ max_proof_length: number;
51
+ };
52
+ }) => {
53
+ counter: string;
54
+ attempts: number;
55
+ timeMs: number;
56
+ } | null): Promise<BenchmarkSummary>;
57
+ /**
58
+ * Run benchmarks at multiple difficulty levels
59
+ */
60
+ export declare function runFullBenchmark(jsFindProof: (params: {
61
+ challenge: {
62
+ difficulty: number;
63
+ subject: string;
64
+ algorithm: 'sha256';
65
+ nonce: string;
66
+ max_proof_length: number;
67
+ };
68
+ }) => {
69
+ counter: string;
70
+ attempts: number;
71
+ timeMs: number;
72
+ } | null): Promise<void>;
73
+ export {};
74
+ //# sourceMappingURL=benchmark.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark.d.ts","sourceRoot":"","sources":["../src/benchmark.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,gEAAgE;AAIhE,UAAU,eAAe;IACvB,cAAc,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,eAAe,CAAA;IACvB,EAAE,EAAE,eAAe,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAmBlF;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,CAAC,MAAM,EAAE;IAClB,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,QAAQ,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,gBAAgB,EAAE,MAAM,CAAA;KACzB,CAAA;CACF,KAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACjE,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,CAAC,MAAM,EAAE;IACpB,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,QAAQ,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,gBAAgB,EAAE,MAAM,CAAA;KACzB,CAAA;CACF,KAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACjE,OAAO,CAAC,gBAAgB,CAAC,CAyB3B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,CAAC,MAAM,EAAE;IACpB,SAAS,EAAE;QACT,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,QAAQ,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,gBAAgB,EAAE,MAAM,CAAA;KACzB,CAAA;CACF,KAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACjE,OAAO,CAAC,IAAI,CAAC,CAcf"}