Implement WitnessCalc in native apps Pt.2
Phat
Posted on November 21, 2024
Previously we have implemented witness calculation in android native
Now, let’s do the same for iOS
Open our root ios folder in xCode and after indexing we will see:
Our project will be under the Pods/Development Pods:
scroll down and search for our module:
open RnWtnscalcs.mm
file which is the analogue of our RnWtnscalcsModule.kt
file in android project
As we haven’t implemented our generated Spec file yet, the xCode will offer you to fix that:
To go to our spec file, you could just cmd+click on RnWtnscalcs
implementation
and cmd+click on NativeRnWtnscalcsSpec
Now let’s get back to our RnWtnscalcs.mm file and implement plus function
as it is objective-c, we could just import our RnWtnscalcs.h
file and be able to call functions straight from RnWtnscalcs.cpp
file
and mock generateAuthWtns for a while
now, in android project, from the android studio, let’s remove “#include ” from rn-wtnscalcs.cpp
if your file contains it, and move it to cpp-adapter.cpp if it not exists there
this include related only to android project and shouldn't be in common cpp file
And rebuild our project for ios:
npx expo prebuild --platform ios --clean && npx expo run:ios
after start running this command, ur xcode will offer you to re-save project, so wait till the generation ends, and click-re-save
And then “Read from disk”
Finally we got our functions works:
Okay, after we get familiar with iOS part of implementing methods, let’s integrate witnesscalc
Let’s see, how it will looks like at the end:
You could see familiar things here, e.g. WtnsUtils
, or “dat” in datAssets.xcassets
, but let’s get it step by step:
first things first - the library. And that is the most confusing part
We need a libgmp
- the helper library, that our witnesscalc_auth
depends on
move to out witnesscalc
repo and open ./build_gmp.sh
file
we will se build variants here, and if you testing on real device - choose iOS e.g.
./build_gmp.sh ios
for simulators would be:
./build_gmp.sh ios_simulator
Why?
Because xCode wouldn’t let you build the app for Simulator if the library was compiled for iOS and vise-versa
Next, we need to build the witnesscalc_auth
itself, for iOS, as we did for android
And here we have 2 options, both can run on device and simulator, but it depends on “which one" exactly, for example “ios” option is running only on devices and simulators on arm64 architecture, such as iPhone 15, 14, …etc, (and simulators too)
same thing for x86_64
In this guide we will test on iPhone 15 Pro simulator, so:
make ios
After compile - we will see freshly created build_witnesscalc_ios
folder, and new subfolders under ./depends/gmp
directory
build_witnesscalc_ios
folder is where our “target” library will be
and the ./depends/gmp
folder is where our helper library is, let’s start with it first:
you need a libgmp.a
file:
chose your variant and copy to our module/wtnscalcs/cpp folder
Now, open the ./build_witnesscalc_ios folder in xCode
you should see something like this:
we need to chose witnesscalc_authStatic
variant:
and build for our iPhone 15 Pro simulator
Unfortunately we couldn’t chose “Any iOS simulator device” cuz it contains x86_64, and the libs we have compiled are not support it.
but for production you could build apropriate variants in witnesscalc
repo and chose any iOS device(arm64)
.
So, let's run build for witnesscalc_authStatic
and also “fr”
It’s also the library, that witnesscalc_authStatic
depends on
build it, and then open products folder, our builded libraries would be there:
copy them to our ./modules/wtnscalcs/cpp
folder next to libgmp.a
file:
Then, create bridge.h file:
#ifndef bridge_h
#define bridge_h
#include "witnesscalc_auth.h"
#endif /* bridge_h */
it will let our iOS project access this libraries
Now, open root project ios folder in xCode and add new File to our module ios folder
select Asset Catalog under Resources section
open that file and press “add”(plus symbol at the bottom-left corner), chose Data Set
name it “authDat” as it shown above and drag-n-drop our auth.dat
file inside, the same auth.dat
file, which we use in android app, just copy it.
Now we need to configure our podspec
file, so our changes and linking will not vanish out after each pod-install
or app rebuild
s.resource_bundles = {
"RnWtnscalcsDatAssets" => ["ios/datAssets.xcassets"]
}
s.source_files = "ios/**/*.{h,m,mm,swift}", "cpp/**/*.{hpp,cpp,c,h}"
s.vendored_libraries = "cpp/*.a"
s.preserve_paths = ['cpp/*.a']
s.libraries = "gmp"
# Add GMP headers
s.public_header_files = 'cpp/*.h'
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Public/rn-wtnscalcs/cpp"' }
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'OTHER_LINKING_FLAGS' => '-lc++' }
These configurations will provide correct linking to our libraries and datAssets.
After that, get back to our react-native “frontend" app, and run:
npx pod-install
wait till-the end and “re-save” our xCode project as we did before.
Don’t be afraid if the file structure in your module will change, it’s okay, for example mine was like that after pod-install:
So, our preparations done and we could move to the code:
Create WtnsUtils
m
and h
files under ios folder as it shown above
define our future WtnsUtils
class and import witnesscalc_auth.h
file
#ifndef WtnsUtils_h
#define WtnsUtils_h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "../cpp/witnesscalc_auth.h"
//NS_ASSUME_NONNULL_BEGIN
@interface WtnsUtils : NSObject
+ (NSData *)calcWtnsAuth:(NSData *)privateInputsJson error:(NSError **)error;
@end
//NS_ASSUME_NONNULL_END
#endif /* WtnsUtils_h */
import it in WtnsUtils.m
file and create our WtnsUtils
class
#import <Foundation/Foundation.h>
#import "WtnsUtils.h"
@implementation WtnsUtils
static const NSUInteger ERROR_SIZE = 256;
static const unsigned long WITNESS_SIZE = 100 * 1024 * 1024;
+ (void)initialize {
if (self == [WtnsUtils self]) {
NSLog(@"WtnsUtils initialized");
}
}
+ (NSData *)calcWtnsAuth:(NSData *)privateInputsJson error:(NSError **)error {
NSBundle *bundle = [NSBundle bundleWithIdentifier:@"org.cocoapods.RnWtnscalcsDatAssets"];
// Check if bundle is loaded correctly
if (!bundle) {
NSLog(@"Bundle not found");
if (error) {
*error = [NSError errorWithDomain:@"WtnsUtilsErrorDomain" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Resource bundle not found"}];
}
return nil;
}
// Attempt to load the data asset
NSDataAsset *authDat = [[NSDataAsset alloc] initWithName:@"authDat" bundle:bundle];
if (!authDat) {
NSLog(@"authDat file not found in bundle");
if (error) {
*error = [NSError errorWithDomain:@"WtnsUtilsErrorDomain" code:1 userInfo:@{NSLocalizedDescriptionKey: @"authDat file not found in bundle"}];
}
return nil;
}
return [self _calcWtnsAuth:authDat.data privateInputsJson:privateInputsJson error:error];
}
+ (NSData *)_calcWtnsAuth:(NSData *)descriptionFileData privateInputsJson:(NSData *)privateInputsJson error:(NSError **)error {
unsigned long *wtnsSize = (unsigned long *)malloc(sizeof(unsigned long));
*wtnsSize = WITNESS_SIZE;
char *wtnsBuffer = (char *)malloc(WITNESS_SIZE);
char *errorBuffer = (char *)malloc(ERROR_SIZE);
int result = witnesscalc_auth(
[descriptionFileData bytes], (unsigned long)[descriptionFileData length],
[privateInputsJson bytes], (unsigned long)[privateInputsJson length],
wtnsBuffer, wtnsSize,
errorBuffer, ERROR_SIZE);
if (result != WITNESSCALC_OK) {
[self handleWitnessError:result errorBuffer:errorBuffer wtnsSize:wtnsSize error:error];
free(wtnsSize);
free(wtnsBuffer);
free(errorBuffer);
return nil;
}
NSData *resultData = [NSData dataWithBytes:wtnsBuffer length:(NSUInteger)*wtnsSize];
free(wtnsSize);
free(wtnsBuffer);
free(errorBuffer);
return resultData;
}
+ (void)handleWitnessError:(int32_t)result errorBuffer:(char *)errorBuffer wtnsSize:(unsigned long *)wtnsSize error:(NSError **)error {
if (result == WITNESSCALC_ERROR) {
NSString *errorMessage = [[NSString alloc] initWithBytes:errorBuffer length:ERROR_SIZE encoding:NSUTF8StringEncoding];
if (errorMessage) {
errorMessage = [errorMessage stringByReplacingOccurrencesOfString:@"\0" withString:@""];
if (error) {
*error = [NSError errorWithDomain:@"com.example.WtnsUtils" code:result userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
}
}
} else if (result == WITNESSCALC_ERROR_SHORT_BUFFER) {
NSString *errorMessage = [NSString stringWithFormat:@"Buffer too short, should be at least: %lu", *wtnsSize];
if (error) {
*error = [NSError errorWithDomain:@"com.example.WtnsUtils" code:result userInfo:@{NSLocalizedDescriptionKey: errorMessage}];
}
}
}
@end
as you see, we could access witnesscalc_auth
method straight from .a
library, cuz obj-c can interact with c++, so we don’t need any bindings and CMakeFiles like we did for android
And here we will get access to our bundle, that we defined in podspec file
s.resource_bundles = {
"RnWtnscalcsDatAssets" => ["ios/datAssets.xcassets"]
}
and search for authDat asset, where our auth.dat
file is.
note: that ur bundle identifier could be named differently
if you unsure, you could log all your bundle identifier and find the right one:
NSArray *bundles = [NSBundle allBundles];
for (NSBundle *bundle in bundles) {
NSLog(@"Bundle Identifier: %@", [bundle bundleIdentifier]);
}
Now, import WtnsUtils.h
file in RnWtnscalcs.mm
and implement our generateAuthWtns method:
- (void)generateAuthWtns:(NSString *)jsonInputsBase64 resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
NSError *error = nil;
// Convert base64 encoded string to NSData
NSData *jsonData = [[NSData alloc] initWithBase64EncodedString:jsonInputsBase64 options:0];
if (!jsonData) {
reject(@"error", @"Failed to decode base64 string", nil);
return;
}
NSData *result = [WtnsUtils calcWtnsAuth:jsonData error:&error];
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
reject(@"error", error.localizedDescription, error);
} else {
// Encode result to base64 and resolve
NSString *resultBase64 = [result base64EncodedStringWithOptions:0];
resolve(resultBase64);
}
}
Now, rebuild the app, and run
npx expo prebuild --clean && npx pod-install && npx expo run:ios
🎉🎉🎉
Part 3 (.AAR & .xcframework) | Wrap everything up to simplify development and delivery process
Posted on November 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.