DEV Community

Phat
Phat

Posted on

Implement WitnessCalc in native apps Pt.2

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:

Image description

Our project will be under the Pods/Development Pods:

Image description

scroll down and search for our module:

Image description

open RnWtnscalcs.mm file which is the analogue of our RnWtnscalcsModule.kt file in android project

Image description

As we haven’t implemented our generated Spec file yet, the xCode will offer you to fix that:

Image description

Image description

To go to our spec file, you could just cmd+click on RnWtnscalcs implementation

Image description

and cmd+click on NativeRnWtnscalcsSpec

Image description

Now let’s get back to our RnWtnscalcs.mm file and implement plus function

Image description

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

Image description

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

Image description

Image description

And rebuild our project for ios:

npx expo prebuild --platform ios --clean && npx expo run:ios
Enter fullscreen mode Exit fullscreen mode

after start running this command, ur xcode will offer you to re-save project, so wait till the generation ends, and click-re-save

Image description

And then “Read from disk”

Image description

Finally we got our functions works:

Image description


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:

Image description

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

Image description

we will se build variants here, and if you testing on real device - choose iOS e.g.

./build_gmp.sh ios
Enter fullscreen mode Exit fullscreen mode

for simulators would be:

./build_gmp.sh ios_simulator
Enter fullscreen mode Exit fullscreen mode

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

Image description

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
Enter fullscreen mode Exit fullscreen mode

After compile - we will see freshly created build_witnesscalc_ios folder, and new subfolders under ./depends/gmp directory

Image description

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:

Image description

chose your variant and copy to our module/wtnscalcs/cpp folder

Image description

Now, open the ./build_witnesscalc_ios folder in xCode

you should see something like this:

Image description

we need to chose witnesscalc_authStatic variant:

Image description

and build for our iPhone 15 Pro simulator

Image description

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”

Image description

It’s also the library, that witnesscalc_authStatic depends on

build it, and then open products folder, our builded libraries would be there:

Image description

copy them to our ./modules/wtnscalcs/cpp folder next to libgmp.a file:

Image description

Then, create bridge.h file:

Image description

#ifndef bridge_h
#define bridge_h

#include "witnesscalc_auth.h"

#endif /* bridge_h */
Enter fullscreen mode Exit fullscreen mode

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

Image description

select Asset Catalog under Resources section

Image description

open that file and press “add”(plus symbol at the bottom-left corner), chose Data Set

Image description

Image description

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

Image description

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++' }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

Image description

So, our preparations done and we could move to the code:

Create WtnsUtils m and h files under ios folder as it shown above

Image description

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 */
Enter fullscreen mode Exit fullscreen mode

import it in WtnsUtils.m file and create our WtnsUtils class

Image description

#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

Enter fullscreen mode Exit fullscreen mode

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

Image description

And here we will get access to our bundle, that we defined in podspec file

s.resource_bundles = {
  "RnWtnscalcsDatAssets" => ["ios/datAssets.xcassets"]
}
Enter fullscreen mode Exit fullscreen mode

Image description

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]);
}
Enter fullscreen mode Exit fullscreen mode

Now, import WtnsUtils.h file in RnWtnscalcs.mm

Image description

and implement our generateAuthWtns method:

Image description

- (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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, rebuild the app, and run

npx expo prebuild --clean && npx pod-install && npx expo run:ios
Enter fullscreen mode Exit fullscreen mode

Image description

🎉🎉🎉

Part 3 (.AAR & .xcframework) | Wrap everything up to simplify development and delivery process

Top comments (0)