@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.
- package/NitroHashcashNative.podspec +56 -0
- package/README.md +24 -0
- package/android/CMakeLists.txt +29 -0
- package/android/build.gradle +141 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/hashcashnative/HybridHashcash.kt +113 -0
- package/android/src/main/java/com/margelo/nitro/hashcashnative/NitroHashcashNativePackage.kt +18 -0
- package/ios/Bridge.h +8 -0
- package/ios/HybridHashcash.swift +133 -0
- package/lib/benchmark.d.ts +74 -0
- package/lib/benchmark.d.ts.map +1 -0
- package/lib/benchmark.js +92 -0
- package/lib/benchmark.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +10 -0
- package/lib/index.js.map +1 -0
- package/lib/specs/Hashcash.nitro.d.ts +59 -0
- package/lib/specs/Hashcash.nitro.d.ts.map +1 -0
- package/lib/specs/Hashcash.nitro.js +2 -0
- package/lib/specs/Hashcash.nitro.js.map +1 -0
- package/nitro.json +18 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroHashcashNative+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroHashcashNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroHashcashNativeOnLoad.cpp +44 -0
- package/nitrogen/generated/android/NitroHashcashNativeOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JFindProofParams.hpp +68 -0
- package/nitrogen/generated/android/c++/JHashcashChallenge.hpp +69 -0
- package/nitrogen/generated/android/c++/JHashcashProofResult.hpp +69 -0
- package/nitrogen/generated/android/c++/JHybridHashcashSpec.cpp +81 -0
- package/nitrogen/generated/android/c++/JHybridHashcashSpec.hpp +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/FindProofParams.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HashcashChallenge.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HashcashProofResult.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/HybridHashcashSpec.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/hashcashnative/NitroHashcashNativeOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroHashcashNative+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Bridge.cpp +49 -0
- package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Bridge.hpp +154 -0
- package/nitrogen/generated/ios/NitroHashcashNative-Swift-Cxx-Umbrella.hpp +55 -0
- package/nitrogen/generated/ios/NitroHashcashNativeAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroHashcashNativeAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridHashcashSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridHashcashSpecSwift.hpp +92 -0
- package/nitrogen/generated/ios/swift/FindProofParams.swift +82 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_HashcashProofResult_.swift +47 -0
- package/nitrogen/generated/ios/swift/HashcashChallenge.swift +69 -0
- package/nitrogen/generated/ios/swift/HashcashProofResult.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridHashcashSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridHashcashSpec_cxx.swift +155 -0
- package/nitrogen/generated/shared/c++/FindProofParams.hpp +85 -0
- package/nitrogen/generated/shared/c++/HashcashChallenge.hpp +87 -0
- package/nitrogen/generated/shared/c++/HashcashProofResult.hpp +87 -0
- package/nitrogen/generated/shared/c++/HybridHashcashSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridHashcashSpec.hpp +69 -0
- package/package.json +73 -1
- package/react-native.config.js +16 -0
- package/src/benchmark.ts +152 -0
- package/src/index.ts +23 -0
- package/src/specs/Hashcash.nitro.ts +64 -0
- package/index.d.ts +0 -1
- 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,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,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"}
|