diff --git a/example/.gitignore b/example/.gitignore index 29a3a50..79c113f 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/android/.gitignore b/example/android/.gitignore index 55afd91..046ddc9 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +app/.cxx/ diff --git a/example/integration_test/card_basics_sdk_test.dart b/example/integration_test/card_basics_sdk_test.dart index 6d8c16f..82f8c97 100644 --- a/example/integration_test/card_basics_sdk_test.dart +++ b/example/integration_test/card_basics_sdk_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:ffi'; import 'package:arculus_sdk_flutter/arculus_ffi.dart'; @@ -32,4 +33,27 @@ void main() { expect(hex.toUpperCase(), TestAPDUCommands.selectWallet); }); + + ///[128, 202, 191, 200, 28, 159, 67, 2, 0, 2, 191, 201, 20, 128, 0, 0, 44, 128, 0, 0, 60, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ///80cabfc81c9f43020002bfc9148000002c8000003c800000000000000000000000 + + test("Create correct APDU command for get public key", () { + final pointer = WalletImpl.init(); + final len = SizeTPointer(); + final reqPointer = WalletImpl.getPublicKeyFromPathRequest( + pointer, + utf8.encode("m/44'"), + WalletCurve.ed25519, + len, + ); + + final unsignedBytes = reqPointer.cast().asTypedList(len.getValue()); + + print(unsignedBytes); + + final hex = + unsignedBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(''); + + print(hex); + }); } diff --git a/example/integration_test/sdk_test.dart b/example/integration_test/sdk_test.dart new file mode 100644 index 0000000..df51aa8 --- /dev/null +++ b/example/integration_test/sdk_test.dart @@ -0,0 +1,22 @@ +import 'package:arculus_sdk_flutter/arculus_ffi.dart'; +import 'package:arculus_sdk_flutter/arculus_sdk_flutter.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + ArculusSdkFlutter.init(); + }); + + test("Test get public key", () async { + final wallet = ArculusWallet(); + final publicKey = await wallet.getPublicKeyFromPath( + "m/0'", + WalletCurve.ed25519, + ); + + print(publicKey); + }); +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 72515df..5e866d4 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -6,12 +6,15 @@ PODS: - Flutter - integration_test (0.0.1): - Flutter + - nfc_manager (0.0.1): + - Flutter DEPENDENCIES: - arculus_sdk_flutter (from `.symlinks/plugins/arculus_sdk_flutter/ios`) - Flutter (from `Flutter`) - flutter_nfc_kit (from `.symlinks/plugins/flutter_nfc_kit/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) + - nfc_manager (from `.symlinks/plugins/nfc_manager/ios`) EXTERNAL SOURCES: arculus_sdk_flutter: @@ -22,13 +25,16 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_nfc_kit/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" + nfc_manager: + :path: ".symlinks/plugins/nfc_manager/ios" SPEC CHECKSUMS: - arculus_sdk_flutter: 0cce6a74139ca5c8f9d254d73e43956ca82f6d9d + arculus_sdk_flutter: ccc2ce182683969d2ef90268f444922f3d3ad44d Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_nfc_kit: 3985c93f749b9cb4747479205c2f10bd2f877a11 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + nfc_manager: d7da7cb781f7744b94df5fe9dbca904ac4a0939e PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 41b1bc0..87141f7 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -13,11 +13,11 @@ 6DD9FEB52D3B8E4500E43FE1 /* CSDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DD9FEB42D3B8E4500E43FE1 /* CSDK.xcframework */; }; 6DD9FEB62D3B8E4500E43FE1 /* CSDK.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6DD9FEB42D3B8E4500E43FE1 /* CSDK.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A5AA911D30603EFCD8340D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C573050E8FEC1F19256F181D /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - E4578717F34548FB010B6E0D /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC1290CB7F1FF0693A7E7EC0 /* Pods_RunnerTests.framework */; }; - F5986D985B874E8C1CC211F7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D99A48D80447A0EF58A7C891 /* Pods_Runner.framework */; }; + D5CCCF356002AA19612D44F0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71697F06A4236B96597020DF /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,18 +45,20 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 07D3A7AE062050BE142E3723 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 205283D5CD94A7133E748B38 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 2650305CE8E989355B6DC1F1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3C78EEE0860FCC6109EE4E6A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 44257091BF9CA1DF63BA3AC0 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6018BBFA762D3811E4A1CBA9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 6DD9FEB42D3B8E4500E43FE1 /* CSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = CSDK.xcframework; path = .symlinks/plugins/arculus_sdk_flutter/ios/../../../../../../ios/Frameworks/CSDK.xcframework; sourceTree = ""; }; + 71697F06A4236B96597020DF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8D83BA80A45DFCD60998D3A9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -64,11 +66,10 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B6338A9D752D5BDC61E41883 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - CC1290CB7F1FF0693A7E7EC0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D8B887809786242178924FC9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - D99A48D80447A0EF58A7C891 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F5EE9CC3948E864F920F523D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + C56FCB942D536FB400A873F9 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + C573050E8FEC1F19256F181D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CD2B49E5F724B94F885ED833 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + FF5D84551B391C1EBC71B4A3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -76,7 +77,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E4578717F34548FB010B6E0D /* Pods_RunnerTests.framework in Frameworks */, + D5CCCF356002AA19612D44F0 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -85,7 +86,7 @@ buildActionMask = 2147483647; files = ( 6DD9FEB52D3B8E4500E43FE1 /* CSDK.xcframework in Frameworks */, - F5986D985B874E8C1CC211F7 /* Pods_Runner.framework in Frameworks */, + 78A5AA911D30603EFCD8340D /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,12 +96,12 @@ 27F75471883DE625FA92CFF9 /* Pods */ = { isa = PBXGroup; children = ( - 205283D5CD94A7133E748B38 /* Pods-Runner.debug.xcconfig */, - 07D3A7AE062050BE142E3723 /* Pods-Runner.release.xcconfig */, - D8B887809786242178924FC9 /* Pods-Runner.profile.xcconfig */, - B6338A9D752D5BDC61E41883 /* Pods-RunnerTests.debug.xcconfig */, - 8D83BA80A45DFCD60998D3A9 /* Pods-RunnerTests.release.xcconfig */, - F5EE9CC3948E864F920F523D /* Pods-RunnerTests.profile.xcconfig */, + 6018BBFA762D3811E4A1CBA9 /* Pods-Runner.debug.xcconfig */, + 2650305CE8E989355B6DC1F1 /* Pods-Runner.release.xcconfig */, + FF5D84551B391C1EBC71B4A3 /* Pods-Runner.profile.xcconfig */, + 44257091BF9CA1DF63BA3AC0 /* Pods-RunnerTests.debug.xcconfig */, + CD2B49E5F724B94F885ED833 /* Pods-RunnerTests.release.xcconfig */, + 3C78EEE0860FCC6109EE4E6A /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -117,8 +118,8 @@ isa = PBXGroup; children = ( 6DD9FEB42D3B8E4500E43FE1 /* CSDK.xcframework */, - D99A48D80447A0EF58A7C891 /* Pods_Runner.framework */, - CC1290CB7F1FF0693A7E7EC0 /* Pods_RunnerTests.framework */, + C573050E8FEC1F19256F181D /* Pods_Runner.framework */, + 71697F06A4236B96597020DF /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -158,6 +159,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + C56FCB942D536FB400A873F9 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -177,7 +179,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 3E1B75C39C4E29D702C45AA1 /* [CP] Check Pods Manifest.lock */, + 3696C8D1B247A67846C71D56 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 92EBC3AA429DAB59563656FD /* Frameworks */, @@ -196,14 +198,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 5FF0AF9CFFA7C67F7CF84069 /* [CP] Check Pods Manifest.lock */, + 7D3056517A16CDE2CBF08247 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 508FA6E87E2FC2F48721658F /* [CP] Embed Pods Frameworks */, + 7F1578BD1332CDF5AB8C5C9C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -275,6 +277,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 3696C8D1B247A67846C71D56 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -291,7 +315,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 3E1B75C39C4E29D702C45AA1 /* [CP] Check Pods Manifest.lock */ = { + 7D3056517A16CDE2CBF08247 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -306,14 +330,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 508FA6E87E2FC2F48721658F /* [CP] Embed Pods Frameworks */ = { + 7F1578BD1332CDF5AB8C5C9C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -330,28 +354,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 5FF0AF9CFFA7C67F7CF84069 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -475,15 +477,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = K937M52YLJ; + DEVELOPMENT_TEAM = 3VJMM8QNZ2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -493,7 +496,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B6338A9D752D5BDC61E41883 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 44257091BF9CA1DF63BA3AC0 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -511,7 +514,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8D83BA80A45DFCD60998D3A9 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = CD2B49E5F724B94F885ED833 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -527,7 +530,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F5EE9CC3948E864F920F523D /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 3C78EEE0860FCC6109EE4E6A /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -658,15 +661,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = K937M52YLJ; + DEVELOPMENT_TEAM = 3VJMM8QNZ2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -681,15 +685,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = K937M52YLJ; + DEVELOPMENT_TEAM = 3VJMM8QNZ2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.byneapp.arculusSdkFlutterExampleTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 30ccadc..4036e20 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,18 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NFCReaderUsageDescription + This app uses NFC to read data from NFC tags. + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + D2760000850101 + + com.apple.developer.nfc.readersession.felica.systemcodes + + 12FC + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +55,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..2bb4dee --- /dev/null +++ b/example/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.nfc.readersession.formats + + TAG + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index c84eeb7..475f4a3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,10 +19,13 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - late ArculusWallet wallet; + late ArculusWallet _wallet; + + String _gguid = ''; + @override void initState() { - wallet = ArculusWallet(); + _wallet = ArculusWallet(); super.initState(); } @@ -31,21 +34,34 @@ class _MyAppState extends State { return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('Arculus example app'), + title: const Text('SDK checks'), ), floatingActionButton: FloatingActionButton( - onPressed: () { - // wallet.getPublicKeyFromPath( - // "m/0'", - // WalletCurve.secp256k1, - // ); + onPressed: () async { + final result = await _wallet.getPublicKeyFromPath( + "m/0'", + WalletCurve.secp256k1, + ); //wallet.selectWalletAID(); }, child: const Icon(Icons.add), ), body: Center( - child: Text('Running on:'), + child: Column( + children: [ + Text('GGUID is: $_gguid'), + ElevatedButton( + onPressed: () async { + final gguid = await _wallet.cardGetGGUID(); + setState(() { + _gguid = gguid; + }); + }, + child: const Text('Get GGUID (WalletGetGGUIDRequest)'), + ), + ], + ), ), ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 307400d..a8c2fd7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -12,42 +12,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" convert: dependency: transitive description: @@ -76,10 +76,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -92,10 +92,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: @@ -126,10 +126,10 @@ packages: dependency: transitive description: name: flutter_nfc_kit - sha256: "256f6be0e42a17e505b2b43f78e5ce49f4a7eae661be0459439717a5993fd34a" + sha256: "3cc4059626fa672031261512299458dd274de4ccb57a7f0ee0951ddd70a048e5" url: "https://pub.dev" source: hosted - version: "3.6.0-rc.6" + version: "3.6.0" flutter_test: dependency: "direct dev" description: flutter @@ -162,18 +162,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -202,10 +202,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -218,10 +218,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" ndef: dependency: transitive description: @@ -234,18 +234,18 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -258,23 +258,23 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -287,26 +287,26 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -319,18 +319,18 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" typed_data: dependency: transitive description: @@ -359,18 +359,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" sdks: - dart: ">=3.5.4 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/ios/Classes/arculus_plugin.h b/ios/Classes/arculus_plugin.h new file mode 100644 index 0000000..42a4ad0 --- /dev/null +++ b/ios/Classes/arculus_plugin.h @@ -0,0 +1,10 @@ +#ifndef ArculusPlugin_h +#define ArculusPlugin_h + +#import +#import "csdk.h" + +@interface ArculusPlugin : NSObject +@end + +#endif /* ArculusPlugin_h */ \ No newline at end of file diff --git a/ios/Classes/arculus_plugin.m b/ios/Classes/arculus_plugin.m new file mode 100644 index 0000000..8572dc3 --- /dev/null +++ b/ios/Classes/arculus_plugin.m @@ -0,0 +1,25 @@ +#import "arculus_plugin.h" +#import +@interface ArculusPlugin () +@property (nonatomic, strong) NFCNDEFReaderSession *nfcSession; +@end + +@implementation ArculusPlugin + ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"arculus_plugin" binaryMessenger:[registrar messenger]]; + ArculusPlugin* instance = [[ArculusPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"walletInit" isEqualToString:call.method]) { + AppletObj *wallet = WalletInit(); + result(@((uintptr_t)wallet)); // Send pointer as int + } + else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/ios/Classes/csdk/csdk.h b/ios/Classes/csdk/csdk.h new file mode 100644 index 0000000..d9a1428 --- /dev/null +++ b/ios/Classes/csdk/csdk.h @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2021-2023 Arculus Holdings, L.L.C. All Rights Reserved. + * + * This software is confidential and proprietary information of Arculus Holdings, L.L.C. + * All use and disclosure to third parties is subject to the confidentiality provisions + * of the license agreement accompanying the associated software. + * + * This copyright notice and disclaimer shall be included with all copies of this + * software used in derivative works. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR + * A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THIS SOFTWARE OR THE USE, MODIFICATION, DISTRIBUTION, OR OTHER DEALINGS IN THIS + * SOFTWARE OR ITS DERIVATIVES. + */ + + /** + * @file csdk.h + * + * @brief Header file linked to the csdk.c file. Public API + * + * Notes: XXXXXXRequest Usually provides the bytes to send, the Client sends bytes over NFC + * Notes: XXXXXXResponse Usually used to parse response bytes provided by client + */ + +#ifndef __CSDK_H__ +#define __CSDK_H__ + +#ifdef CSDK_EXPORTS +#define CSDK_API __declspec(dllexport) +#else +#define CSDK_API extern +#endif + +#undef AFX_DATA +#define AFX_DATA AFX_EXT_DATA +// +#undef AFX_DATA +#define AFX_DATA + +#include +#include +#include +#include "csdk_types.h" + +/** + * @brief Allocates and populates a Wallet struct that must be freed by calling WalletFree() + * @return AppletObj wallet pointer + */ +CSDK_API AppletObj* WalletInit(); + +/** + * @brief Frees memory allocated for wallet + * @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) + */ +CSDK_API int WalletFree(AppletObj *wallet); + +/** + * @brief Create an initialize session request object to send to the card, must be called after WalletInit() + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[out] len of the request session object returned + * @return Pointer to the InitSession request object + */ +CSDK_API uint8_t *WalletInitSessionRequest(AppletObj *wallet, size_t *len); + + +/** + * @brief Processes the initialize session request object from WalletInitSessionRequest after the card has processed it + * Must be successful (CSDK_OK returned) in order to call other CSDK_API calls + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the card + * @param[in] responseLength : length in bytes of the response + * @return CSDK_OK on success, ERR_* error code on failure. + */ +CSDK_API int WalletInitSessionResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + +/** + * @brief Parse RC to give the right message associated to the reasonCode + * + * @param RC reason code + * @param[out] message pointer to byte array, to fill out the error message regarding the reason code + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return CSDK_API WalletErrorMessage CSDK_OK or CSDK_ERR_* + */ +CSDK_API int WalletErrorMessage(int rc, const char **message, size_t *len); + +/** + * @brief Create Wallet. Used to initialize the Hardware Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @param[in] nbrOfWords the number of mnemonic words to create the wallet. 12 to 24, if set to 0 , default value is 12; + * @return CSDK_API command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + */ +CSDK_API uint8_t* WalletCreateWalletRequest(AppletObj *wallet, size_t *len, size_t nbrOfWords); + +/** + * @brief Create Wallet. Used to initialize the Hardware Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @param[out] mnemonicSentenceLength: length of the returnd mnemonic words + * @return CSDK_API WalletCreateWalletResponse - mnemonic words non NULL terminated string + */ +CSDK_API uint8_t* WalletCreateWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *mnemonicSentenceLength); + +/** + * @brief Generate Seed From a mnemonic sentence. + * + * @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) + * @param[in] mnemonicSentence byte array (pointer) containing concatenated words, space separated + * @param mnemonicSentenceLength Length of provided words data + * @param[in] passphrase (OPTIONAL: put to null) byte array (pointer) containing passphrase + * @param passphraseLength (OPTIONAL: put to 0) Length of provided passphrase data + * @param[out] seedLength of seed filled in as part of the response when calling this function + * @return seed pointer to a byte array. + */ +CSDK_API uint8_t* WalletSeedFromMnemonicSentence(AppletObj *wallet, const unsigned char *mnemonicSentence, const size_t mnemonicSentenceLength, + const unsigned char *passphrase, const size_t passphraseLength, size_t *seedLength); + +/** + * @brief Seed Create Wallet. Used to initialize the Hardware Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @param[in] nbrOfWords the number of mnemonic words to create the wallet. 12 to 24, if set to 0 , default value is 12; + * @return CSDK_API command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + */ +CSDK_API uint8_t* WalletSeedCreateWalletRequest(AppletObj *wallet, size_t *len, size_t nbrOfWords); + +/** + * @brief Create Wallet. Used to initialize the Hardware Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @param[out] mnemonicSentenceLength: length of the returnd mnemonic words + * @return CSDK_API WalletCreateWalletResponse - mnemonic words non NULL terminated string + */ +CSDK_API uint8_t* WalletSeedCreateWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *mnemonicSentenceLength); + +/** + * @brief Init Recover wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param nbrOfWords amount of recovery words + * @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client +*/ +CSDK_API uint8_t* WalletInitRecoverWalletRequest(AppletObj *wallet, size_t nbrOfWords, size_t *len); + +/** + * @brief Init Recover wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return CSDK_API WalletInitRecoverWalletResponse CSDK_OK or ERR_* + */ +CSDK_API int WalletInitRecoverWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + + +/** + * @brief Get Firmware Version Request + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + */ +CSDK_API uint8_t *WalletGetFirmwareVersionRequest(AppletObj *wallet, size_t *len); + +/** + * @brief Get Firmware Version Response + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @param[out] versionLength: the length of the Firmware Version + * @return the Version or NULL in case of error + */ +CSDK_API uint8_t* WalletGetFirmwareVersionResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *versionLength); + +/** +* @brief Finish Recover Wallet +* +* @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls +* @param[in] mnemonicSentence byte array (pointer) containing concatenated words +* @param mnemonicSentenceLength Length of provided mnemonicSentence data +* @param[in] passphrase (OPTIONAL: put to null) byte array (pointer) containing passphrase +* @param passphraseLength (OPTIONAL: put to 0) Length of provided passphrase data +* @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) +* @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client +*/ +CSDK_API uint8_t* WalletFinishRecoverWalletRequest(AppletObj *wallet, const unsigned char *mnemonicSentence, const size_t mnemonicSentenceLength, const unsigned char *passphrase, const size_t passphraseLength, size_t *len); + +/** + * @brief Parse response to Finish Recover Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return CSDK_API WalletFinishRecoverWalletResponse CSDK_OK or ERR_* + */ +CSDK_API int WalletFinishRecoverWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + +/** +* @brief Seed Finish Recover Wallet +* +* @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls +* @param[in] seed pointer to +* @param[in] seedLength +* @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) +* @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client +*/ +CSDK_API uint8_t* WalletSeedFinishRecoverWalletRequest(AppletObj *wallet, const unsigned char *seed, const size_t seedLength, size_t *len); + +/** + * @brief Parse response to Finish Recover Wallet + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return CSDK_API WalletFinishRecoverWalletResponse CSDK_OK or ERR_* + */ +CSDK_API int WalletSeedFinishRecoverWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + + +/** +* @brief Get GGUID of the wallet. Select wallet needs to be called prior to this. +* +* @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls +* @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) +* @return The APDU command to send to the card to execute a create wallet +*/ +CSDK_API uint8_t* WalletGetGGUIDRequest(AppletObj *wallet, size_t *len); + +/** + * @brief Parse the response of Get GGUID of the wallet. + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @param[out] GGUIDLength: the length of the GGUID + * @return the GGUID or NULL in case of error + */ +CSDK_API uint8_t* WalletGetGGUIDResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *GGUIDLength); + +/** +* @brief Select Wallet applet +* +* @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) +* @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client +*/ +CSDK_API uint8_t* WalletSelectWalletRequest(AppletObj *wallet, const uint8_t *aid, size_t *len); + +/** + * @brief Parse response to Wallet Select response + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return resps pointer to OperationSelectResponse* struct, will be filled by the function after parsing provided response + */ +CSDK_API OperationSelectResponse* WalletSelectWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + + +/** + * @brief Verify PIN + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] pin and pinLen + * @param[out] command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return CSDK_API WalletVerifyPINRequest or NULL in case of error + */ +CSDK_API uint8_t* WalletVerifyPINRequest(AppletObj *wallet, const uint8_t *pin, size_t pinLen, size_t *len); + +/** +* @brief parse response from comm and verify a pin, if wrong pin, the nbr of remaining tries can be shown to user (see params) +* + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls +* @param[in] response : Response to be parsed, sent by the client +* @param[in] responseLength : length in bytes of the response +* @param[out] nbrOfTries : Number of tries remaining (3 by default, less in case of one or more, wrong pin in a row). At 0, the pin is blocked +* @return CSDK_OK in case of success, ERR_WRONG_PIN (-108) in case of wrong pin, then you can announce the number of tries remaining (see nbrOfTries Param) +*/ +CSDK_API int WalletVerifyPINResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *nbrOfTries); + +/** + * @brief Store Data PIN + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param pin and pinlen + * @param[out] command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return CSDK_API WalletStoreDataPINRequest or NULL in case of error + */ +CSDK_API uint8_t* WalletStoreDataPINRequest(AppletObj *wallet, const uint8_t *pin, size_t pinLen, size_t *len); + +/** + * @brief Parse response to Store Data PIN + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return CSDK_API WalletStoreDataPINResponse CSDK_OK or ERR_* + */ +CSDK_API int WalletStoreDataPINResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + +/** +* @brief Function used to retrieve Public Key of a certain given path. +* +* @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls +* @param[in] bipPath Path in ascii (and decimal for elts): ex: "m/44h/60h/0h/0/0" or "m/44'/60'/0'/0/0" +* @param[in] bipPathLength The length of the BIP path +* @param[in] Curve to use 0 default, 1 Secp256k1, 2 ED25519, 3 NIST256P1 +* @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) +* @return The command to be sent to the card +*/ +CSDK_API uint8_t* WalletGetPublicKeyFromPathRequest(AppletObj *wallet, const uint8_t *bipPath, size_t bipPathLength, uint16_t curve, size_t *len); + +/** + * @brief Function used to retrieve Public Key of a certain path. This command is a Get Data (INS : 0xCA) with tag BFC5] + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : response from the card when passed a request generated from WalletGetPublicKeyFromPathRequest + * @param[in] responseLength : length in bytes of the response + * @return The extended key (pubkey + chaincode) or NULL in case of error + */ +CSDK_API ExtendedKey* WalletGetPublicKeyFromPathResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + +/** + * @brief Reset Wallet. reset the wallet on card + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return CSDK_API command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + */ +CSDK_API uint8_t* WalletResetWalletRequest(AppletObj *wallet, size_t *len); + +/** + * @brief Reset Wallet. reset the wallet on card + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @return CSDK_API return CSDK_OK or ERR_* + */ +CSDK_API int WalletResetWalletResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength); + +/** + * @brief Create command for signing requested hash + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] bip_path bip_path Path in ascii (and decimal for elts): ex: "m/44h/60h/0h/0/0" or "m/44'/60'/0'/0/0" + * @param[in] bip_path_pength The length of the BIP path + * @param[in] curve Curve to use: (MSB: 1=secp256k1, 2=ed25519, 3=nist256p1, LSB: variation) + * @param[in] algorithm Hash Algorithm to use: 0 default/undefined, 1 ECDSA, 2 EDDSA, 3 EC Schnorr, 4 Ristretto, 5 Cardano + * @param[in] hash pointer to the hash to be signed + * @param[in] hash_length length of the hash alg + * @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + * @return Pointer to the command or NULL in case of error + */ +CSDK_API uint8_t* WalletSignHashRequest(AppletObj *wallet, const uint8_t *bip_path, size_t bip_path_pength, + uint16_t curve, uint8_t algorithm, + const uint8_t *hash, const size_t hash_length, size_t *len); + +/** + * @brief Process the sign hash response + * + * @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + * @param[in] response : Response to be parsed, sent by the client + * @param[in] responseLength : length in bytes of the response + * @param[out] signedHashLength : length in bytes of the signed Hash + * @return Pointer to the hash signature, NULL in case of error + */ +CSDK_API uint8_t* WalletSignHashResponse(AppletObj *wallet, const uint8_t *response, size_t responseLength, size_t *signedHashLength); + +// Helper functions for Android to get PubKey and Chain Code +CSDK_API uint8_t* ExtendedKey_getPubKey(ExtendedKey *extendedKey, size_t *len); +CSDK_API uint8_t* ExtendedKey_getChainCode(ExtendedKey *extendedKey, size_t *len); + + + +#endif diff --git a/ios/Classes/csdk/csdk_types.h b/ios/Classes/csdk/csdk_types.h new file mode 100644 index 0000000..c1ddb93 --- /dev/null +++ b/ios/Classes/csdk/csdk_types.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021-2023 Arculus Holdings, L.L.C. All Rights Reserved. + * + * This software is confidential and proprietary information of Arculus Holdings, L.L.C. + * All use and disclosure to third parties is subject to the confidentiality provisions + * of the license agreement accompanying the associated software. + * + * This copyright notice and disclaimer shall be included with all copies of this + * software used in derivative works. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR + * A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THIS SOFTWARE OR THE USE, MODIFICATION, DISTRIBUTION, OR OTHER DEALINGS IN THIS + * SOFTWARE OR ITS DERIVATIVES. + */ + + /** + * @file csdk_types.h + * + * @brief Public API for csdk type + * + * Notes: XXXXXXRequest Usually provides the bytes to send, the Client sends bytes over NFC + * Notes: XXXXXXResponse Usually used to parse response bytes provided by client + */ +#ifndef INCLUDE_CSDK_TYPES_H_ +#define INCLUDE_CSDK_TYPES_H_ + +#include +#include +#include + +/** + * @brief AppletObj is the Context Object for interacting with the Arculus Card + * */ +typedef struct AppletObj AppletObj; + + +#define CSDK_OK 0 /**< Success return code */ +/************************************************************************************** + + ERRORS DEFINITION + +***************************************************************************************/ +#define CSDK_ERR_NULL_POINTER -100 /**< ERR_NULL_POINTER error code */ +#define CSDK_ERR_NULL_APPLETOBJ -101 /**< ERR_NULL_WALLET error code */ +#define CSDK_ERR_NULL_CALLOC -102 /**< ERR_NULL_CALLOC error code */ +#define CSDK_ERR_WRONG_RESPONSE_LENGTH -103 /**< ERR_WRONG_RESPONSE_LENGTH error code */ +#define CSDK_ERR_WRONG_RESPONSE_DATA -104 /**< ERR_WRONG_RESPONSE_DATA error code */ +#define CSDK_ERR_WRONG_STATUS_WORD -105 /**< ERR_WRONG_STATUS_WORD error code */ +#define CSDK_ERR_WRONG_DATA_LENGTH -106 /**< ERR_WRONG_DATA_LENGTH error code */ +#define CSDK_ERR_WRONG_PARAM_LENGTH -107 /**< ERR_WRONG_PARAM_LENGTH error code */ +#define CSDK_ERR_WRONG_PIN -108 /**< ERR_WRONG_PIN error code */ +#define CSDK_ERR_INVALID_PARAM -109 +#define CSDK_ERR_ENCRYPTION_NOT_INIT -110 + + +/** WALLET Applet Id, for WalletSelectWalletResponse */ +#define WALLET_AID_LEN 10 + +/** Max length of public key or chain key code */ +#define KEY_MAXLEN 33 +/** Max length of wallet PIN code */ +#define WALLET_PIN_MAXLEN 12 +/** Min length of wallet PIN code */ +#define WALLET_PIN_MINLEN 4 + +// ** Useful bitshifting Macros for BIP39 mnemonic sentence */ +#define WBIT(x) (1LL << ((x)-1)) +#define WALLET_NUM_WORDS_BITMASK (WBIT(12)|WBIT(15)|WBIT(18)|WBIT(21)|WBIT(24)) + +/** + * @brief Structure to allow having return Structure containing both ChainCode Key and the Public Key in case re-derivation is needed. + * */ +typedef struct { + unsigned char publicKey[KEY_MAXLEN]; + unsigned char chainCodeKey[KEY_MAXLEN]; + size_t pubKeyLe; + size_t chainCodeLe; +} ExtendedKey; + +/** + * @brief Structure to grouping required data structs for applet selection + * */ +typedef struct { + unsigned char *ApplicationAID; + int ApplicationAIDLength; +} OperationSelectResponse; + + +#endif /* INCLUDE_CSDK_TYPES_H_ */ diff --git a/ios/arculus_sdk_flutter.podspec b/ios/arculus_sdk_flutter.podspec index 1620bbd..c58fcb6 100644 --- a/ios/arculus_sdk_flutter.podspec +++ b/ios/arculus_sdk_flutter.podspec @@ -16,6 +16,8 @@ A new Flutter plugin project. s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '13.0' + s.static_framework = true + s.ios.frameworks = 'CoreNFC' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/lib/arculus_ffi.dart b/lib/arculus_ffi.dart index fb09d25..f14b969 100644 --- a/lib/arculus_ffi.dart +++ b/lib/arculus_ffi.dart @@ -1,9 +1,9 @@ library arculus_sdk_ffi; import 'dart:ffi'; -import 'dart:io'; import 'dart:typed_data'; +import 'package:arculus_sdk_flutter/core/model/extended_key.dart'; import 'package:ffi/ffi.dart'; part 'core/wallet.ffi.dart'; diff --git a/lib/arculus_sdk_flutter.dart b/lib/arculus_sdk_flutter.dart index 4161f12..3e1dbee 100644 --- a/lib/arculus_sdk_flutter.dart +++ b/lib/arculus_sdk_flutter.dart @@ -1,10 +1,15 @@ library arculus_sdk_flutter; +import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; -import 'dart:typed_data'; + +import 'package:arculus_sdk_flutter/core/model/extended_key.dart'; +import 'package:ffi/ffi.dart'; import 'package:arculus_sdk_flutter/arculus_ffi.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; part 'core/wallet.dart'; diff --git a/lib/core/model/extended_key.dart b/lib/core/model/extended_key.dart new file mode 100644 index 0000000..208fe17 --- /dev/null +++ b/lib/core/model/extended_key.dart @@ -0,0 +1,16 @@ +import 'dart:ffi'; + +/// @brief Structure to allow having return Structure containing both ChainCode Key and the Public Key in case re-derivation is needed. +final class ExtendedKey extends Struct { + @Array.multi([33]) + external Array publicKey; + + @Array.multi([33]) + external Array chainCodeKey; + + @Size() + external int pubKeyLe; + + @Size() + external int chainCodeLe; +} diff --git a/lib/core/wallet.dart b/lib/core/wallet.dart index b205817..26a8880 100644 --- a/lib/core/wallet.dart +++ b/lib/core/wallet.dart @@ -20,24 +20,152 @@ class ArculusWallet { _pointer = WalletImpl.init(); } - Future getPublicKeyFromPath(String path, WalletCurve curve) async { - final sizeRef = SizeTPointer(); + Future getPublicKeyFromPath( + String path, + WalletCurve curve, + ) async { + await _startPolling(); - final pathBytes = Uint8List.fromList(path.codeUnits); + final len = SizeTPointer(); + + final pathBytes = utf8.encode(path); + + await _cardSelectWallet(); - final reqPointer = WalletImpl.getPublicKeyFromPath( + final reqPointer = WalletImpl.getPublicKeyFromPathRequest( _pointer, pathBytes, curve, - sizeRef, + len, + ); + + final adpuCommand = reqPointer.cast().asTypedList(len.getValue()); + + print(adpuCommand); + + final result = await _sendCommand(adpuCommand); + + // Returnning result is wrong, there must be an issue with the APDU command + print(result); + print(result.length); + + final extendedKey = WalletImpl.getPublicKeyFromPathResponse( + _pointer, + result.toPointerUint8(), + len.getValue(), + ); + + if (extendedKey == nullptr) { + print('Failed to get extended key'); + throw Exception('Failed to get extended key'); + } + + final pubKeyLen = SizeTPointer(); + + final pubKey = WalletImpl.extendedKeyGetPubKey( + extendedKey, + pubKeyLen, + ); + + if (pubKey == nullptr) { + throw Exception('Failed to get public key'); + } + + // Copy the data to a Dart Uint8List + final pubKeyBytes = + pubKey.cast().asTypedList(pubKeyLen.getValue()).toList(); + final resultBytes = Uint8List.fromList(pubKeyBytes); + + // Clean up the memory + + await _endPolling(); + + // final pubKeyBytes = pubKey.cast().asTypedList(pubKeyLen.getValue()); + + // print(pubKeyBytes); + + return resultBytes; + } + + // Helper method to convert FFI Array to hex string + String _ffiArrayToHex(Array array, int length) { + final buffer = []; + for (var i = 0; i < length; i++) { + buffer.add(array[i]); + } + return buffer + .map((e) => e.toRadixString(16).padLeft(2, '0').toUpperCase()) + .join(''); + } + + // NFC commnads + + Future _startPolling() async { + final status = await FlutterNfcKit.nfcAvailability; + if (status == NFCAvailability.available) { + final tag = await FlutterNfcKit.poll( + timeout: const Duration(seconds: 60), + iosMultipleTagMessage: "Multiple tags found", + iosAlertMessage: "Hold your phone near the card", + // Make sure we're enabling ISO 7816 / ISO 14443-4 (smart card) support + readIso14443A: true, + readIso14443B: true, + readIso15693: true, + readIso18092: true, + ); + print("NFC tag found: ${tag.type}, ID: ${tag.id}"); + print("NFC tag standard: ${tag.standard}"); + print("NFC tag ATQA: ${tag.atqa}, SAK: ${tag.sak}"); + + return tag; + } + throw PlatformException(code: "404", message: "NFC not available"); + } + + String _uint8ListToHexString(Uint8List data) { + return data + .map((byte) => byte.toRadixString(16).padLeft(2, '0').toUpperCase()) + .join(''); + } + + Future _sendCommand(Uint8List command) async { + final apduStr = _uint8ListToHexString(command); + print("Sending command -->: $apduStr"); + final res = await FlutterNfcKit.transceive( + _uint8ListToHexString(command), + // Add any additional parameters that might be needed + timeout: const Duration(seconds: 5), ); - final length = sizeRef.getValue(); + final response = _hexStringToUint8List(res); + + final responseStr = _uint8ListToHexString(response); + print("Response <--: $responseStr"); - final unsignedBytes = reqPointer.cast().asTypedList(length); + if (response.length < 2 || + response[response.length - 2] != 0x90 || + response[response.length - 1] != 0x00) { + throw Exception("sendReceive bad status"); + } + + return response; + } + + Uint8List _hexStringToUint8List(String hex) { + List bytes = []; + for (int i = 0; i < hex.length; i += 2) { + if (i + 2 <= hex.length) { + bytes.add(int.parse(hex.substring(i, i + 2), radix: 16)); + } + } + return Uint8List.fromList(bytes); } - void selectWalletAID() async { + Future _endPolling() async { + await FlutterNfcKit.finish(); + } + + Future _cardSelectWalletAID() async { final len = SizeTPointer(); final reqPointer = WalletImpl.selectWalletRequest( _pointer, @@ -45,25 +173,86 @@ class ArculusWallet { len, ); - final unsignedBytes = reqPointer.cast().asTypedList(len.getValue()); + final adpuCommand = reqPointer.cast().asTypedList(len.getValue()); + + final result = await _sendCommand(adpuCommand); + + final walletSelectResponse = WalletImpl.selectWalletResponse( + _pointer, + result, + result.length, + ); - // final poll = await FlutterNfcKit.poll(); + final aidPointer = walletSelectResponse.ref.ApplicationAID; + final _ = aidPointer.cast().toDartString(); + } - // final response = await FlutterNfcKit.transceive(unsignedBytes); + Future _cardEncryptSession() async { + final len = SizeTPointer(); + final reqPointer = WalletImpl.walletInitSessionRequest( + _pointer, + len, + ); + + final adpuCommand = reqPointer.cast().asTypedList(len.getValue()); + + print("Encrypting session: ${_uint8ListToHexString(adpuCommand)}"); + + final result = await _sendCommand(adpuCommand); + + final _ = WalletImpl.walletInitSessionResponse( + _pointer, + result.toPointerUint8(), + result.length, + ); + } + + Future _cardSelectWallet() async { + await _cardSelectWalletAID(); + await _cardEncryptSession(); + } - // final decoded = WalletImpl.selectWalletResponse( - // _pointer, - // response.toPointerUint8(), - // response.length, - // ); + Future cardGetGGUID() async { + await _startPolling(); + + await _cardSelectWallet(); + + final len = SizeTPointer(); + + final reqPointer = WalletImpl.walletGetGGUIDRequest( + _pointer, + len, + ); + + final adpuCommand = reqPointer.cast().asTypedList(len.getValue()); + + final result = await _sendCommand(adpuCommand); + + final walletGetGGUIDResponse = WalletImpl.walletGetGGUIDResponse( + _pointer, + result.toPointerUint8(), + result.length, + len, + ); + + await _endPolling(); + + final gguidLength = len.getValue(); + final gguidBytes = + walletGetGGUIDResponse.cast().asTypedList(gguidLength); + + // Convert to uppercase hexadecimal string + final gguidHex = gguidBytes + .map((e) => e.toRadixString(16).padLeft(2, '0').toUpperCase()) + .join(''); + + return gguidHex; + } - // print( - // "Request pointer: ${reqPointer.cast().asTypedList(len.getValue())}."); - // print("Length: ${len.getValue()}"); + Future cardGetPublicKey() async { + await _startPolling(); - // print("Unsigned bytes: $unsignedBytes"); - // print( - // "Hex output: ${unsignedBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}"); + await _cardSelectWallet(); } void free() { diff --git a/lib/core/wallet.ffi.dart b/lib/core/wallet.ffi.dart index 402828d..1c20ef9 100644 --- a/lib/core/wallet.ffi.dart +++ b/lib/core/wallet.ffi.dart @@ -58,6 +58,40 @@ abstract class WalletFFI { _WalletGetPublicKeyFromPathRequestPtr.asFunction< _dart_walletGetPublicKeyFromPathRequest>(); + /// @brief Function used to retrieve Public Key of a certain path. This command is a Get Data (INS : 0xCA) with tag BFC5] + /// + /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + /// @param[in] response : response from the card when passed a request generated from WalletGetPublicKeyFromPathRequest + /// @param[in] responseLength : length in bytes of the response + /// @return The extended key (pubkey + chaincode) or NULL in case of error + static Pointer walletGetPublicKeyFromPathResponse( + Pointer wallet, + Pointer response, + int responseLength, + ) { + return _WalletGetPublicKeyFromPathResponse( + wallet, + response, + responseLength, + ); + } + + static final _WalletGetPublicKeyFromPathResponsePtr = _lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + Int, + )>>('WalletGetPublicKeyFromPathResponse'); + + static final _WalletGetPublicKeyFromPathResponse = + _WalletGetPublicKeyFromPathResponsePtr.asFunction< + Pointer Function( + Pointer, + Pointer, + int, + )>(); + /// @brief Select Wallet applet /// /// @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) @@ -90,12 +124,12 @@ abstract class WalletFFI { static Pointer walletSelectWalletResponse( Pointer wallet, Pointer response, - int responseLength, + int len, ) { return _WalletSelectWalletResponse( wallet, response, - responseLength, + len, ); } @@ -105,8 +139,149 @@ abstract class WalletFFI { static final _WalletSelectWalletResponse = _WalletSelectWalletResponsePtr .asFunction<_dart_walletSelectWalletResponse>(); + + /// @brief Create an initialize session request object to send to the card, must be called after WalletInit() + /// + /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + /// @param[out] len of the request session object returned + /// @return Pointer to the InitSession request object + static Pointer walletInitSessionRequest( + Pointer wallet, + Pointer len, + ) { + return _WalletInitSessionRequest( + wallet, + len, + ); + } + + static final _WalletInitSessionRequestPtr = _lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>('WalletInitSessionRequest'); + + static final _WalletInitSessionRequest = + _WalletInitSessionRequestPtr.asFunction< + Pointer Function( + Pointer, + Pointer, + )>(); + + /// @brief Processes the initialize session request object from WalletInitSessionRequest after the card has processed it + /// Must be successful (CSDK_OK returned) in order to call other CSDK_API calls + /// + /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + /// @param[in] response : Response to be parsed, sent by the card + /// @param[in] responseLength : length in bytes of the response + /// @return CSDK_OK on success, ERR_* error code on failure. + static int walletInitSessionResponse( + Pointer wallet, + Pointer response, + int responseLength, + ) { + return _WalletInitSessionResponse( + wallet, + response, + responseLength, + ); + } + + static final _WalletInitSessionResponsePtr = _lookup< + NativeFunction, Pointer, Size)>>( + 'WalletInitSessionResponse'); + static final _WalletInitSessionResponse = _WalletInitSessionResponsePtr + .asFunction, Pointer, int)>(); + + /// @brief Get GGUID of the wallet. Select wallet needs to be called prior to this. + /// + /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + /// @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + /// @return The APDU command to send to the card to execute a create wallet + static Pointer walletGetGGUIDRequest( + Pointer wallet, + Pointer len, + ) { + return _WalletGetGGUIDRequest( + wallet, + len, + ); + } + + static final _WalletGetGGUIDRequestPtr = _lookup< + NativeFunction< + Pointer Function( + Pointer, Pointer)>>('WalletGetGGUIDRequest'); + static final _WalletGetGGUIDRequest = _WalletGetGGUIDRequestPtr.asFunction< + Pointer Function(Pointer, Pointer)>(); + + /// @brief Parse the response of Get GGUID of the wallet. + /// + /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls + /// @param[in] response : Response to be parsed, sent by the client + /// @param[in] responseLength : length in bytes of the response + /// @param[out] GGUIDLength: the length of the GGUID + /// @return the GGUID or NULL in case of error + static Pointer walletGetGGUIDResponse( + Pointer wallet, + Pointer response, + int responseLength, + Pointer GGUIDLength, + ) { + return _WalletGetGGUIDResponse( + wallet, + response, + responseLength, + GGUIDLength, + ); + } + + static final _WalletGetGGUIDResponsePtr = + _lookup>( + 'WalletGetGGUIDResponse'); + + static final _WalletGetGGUIDResponse = + _WalletGetGGUIDResponsePtr.asFunction<_dart_walletGetGGUIDResponse>(); + + static Pointer ExtendedKey_getPubKey( + Pointer extendedKey, + Pointer len, + ) { + return _ExtendedKey_getPubKey( + extendedKey, + len, + ); + } + + static final _ExtendedKey_getPubKeyPtr = _lookup< + NativeFunction< + Pointer Function( + Pointer, + Pointer, + )>>('ExtendedKey_getPubKey'); + + static final _ExtendedKey_getPubKey = _ExtendedKey_getPubKeyPtr.asFunction< + Pointer Function( + Pointer, + Pointer, + )>(); } +typedef _c_WalletGetGGUIDResponse = Pointer Function( + Pointer wallet, + Pointer response, + Int responseLength, + Pointer GGUIDLength, +); + +typedef _dart_walletGetGGUIDResponse = Pointer Function( + Pointer wallet, + Pointer response, + int responseLength, + Pointer GGUIDLength, +); + /// @brief Structure to grouping required data structs for applet selection final class OperationSelectResponse extends Struct { external Pointer ApplicationAID; @@ -155,12 +330,12 @@ typedef _c_walletSelectWalletResponse = Pointer Function( Pointer wallet, Pointer response, - Int responseLength, + Int len, ); typedef _dart_walletSelectWalletResponse = Pointer Function( Pointer wallet, Pointer response, - int responseLength, + int len, ); diff --git a/lib/core/wallet.impl.dart b/lib/core/wallet.impl.dart index 2439d7a..0652e11 100644 --- a/lib/core/wallet.impl.dart +++ b/lib/core/wallet.impl.dart @@ -9,29 +9,35 @@ class WalletImpl extends WalletFFI { return WalletFFI.walletFree(wallet); } - static Pointer getPublicKeyFromPath( + static Pointer getPublicKeyFromPathRequest( Pointer wallet, Uint8List path, WalletCurve curve, SizeTPointer len, ) { - // Ensure curve is treated as uint16_t - final curveValue = curve.value & 0xFFFF; - - final pathPointer = path.toPointerUint8(); - final result = WalletFFI.walletGetPublicKeyFromPathRequest( wallet, - pathPointer, + path.toPointerUint8(), path.length, - curveValue, + curve.value, len.pointer, ); - calloc.free(pathPointer); return result; } + static Pointer getPublicKeyFromPathResponse( + Pointer wallet, + Pointer response, + int responseLength, + ) { + return WalletFFI.walletGetPublicKeyFromPathResponse( + wallet, + response, + responseLength, + ); + } + static Pointer selectWalletRequest( Pointer wallet, Uint8List aid, @@ -45,14 +51,64 @@ class WalletImpl extends WalletFFI { } static Pointer selectWalletResponse( + Pointer wallet, + Uint8List response, + int len, + ) { + return WalletFFI.walletSelectWalletResponse( + wallet, + response.toPointerUint8(), + len, + ); + } + + static Pointer walletInitSessionRequest( + Pointer wallet, + SizeTPointer len, + ) { + return WalletFFI.walletInitSessionRequest( + wallet, + len.pointer, + ); + } + + static int walletInitSessionResponse( Pointer wallet, Pointer response, int responseLength, ) { - return WalletFFI.walletSelectWalletResponse( + return WalletFFI.walletInitSessionResponse( + wallet, + response, + responseLength, + ); + } + + static Pointer walletGetGGUIDRequest( + Pointer wallet, + SizeTPointer len, + ) { + return WalletFFI.walletGetGGUIDRequest(wallet, len.pointer); + } + + static Pointer walletGetGGUIDResponse( + Pointer wallet, + Pointer response, + int responseLength, + SizeTPointer len, + ) { + return WalletFFI.walletGetGGUIDResponse( wallet, response, responseLength, + len.pointer, ); } + + static Pointer extendedKeyGetPubKey( + Pointer extendedKey, + SizeTPointer len, + ) { + return WalletFFI.ExtendedKey_getPubKey(extendedKey, len.pointer); + } } diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index ffca03d..7a4016e 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -1545,6 +1545,34 @@ class Arculus { int Function(ffi.Pointer, int, int, int, ffi.Pointer, va_list)>(); + /// @brief Allocates and populates a Wallet struct that must be freed by calling WalletFree() + /// @return AppletObj wallet pointer + ffi.Pointer WalletInit() { + return _WalletInit(); + } + + late final _WalletInitPtr = + _lookup Function()>>( + 'WalletInit'); + late final _WalletInit = + _WalletInitPtr.asFunction Function()>(); + + /// @brief Frees memory allocated for wallet + /// @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) + int WalletFree( + ffi.Pointer wallet, + ) { + return _WalletFree( + wallet, + ); + } + + late final _WalletFreePtr = + _lookup)>>( + 'WalletFree'); + late final _WalletFree = + _WalletFreePtr.asFunction)>(); + /// @brief Create an initialize session request object to send to the card, must be called after WalletInit() /// /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls @@ -2085,6 +2113,33 @@ class Arculus { ffi.Pointer Function(ffi.Pointer, ffi.Pointer, int, ffi.Pointer)>(); + /// @brief Select Wallet applet + /// + /// @param wallet AppletObj wallet structure containing list of pointers(ex: apdu commands...etc) + /// @return command pointer to a char*. The function with fill this "char array" with the appropriate APDU command to be sent by the client + ffi.Pointer WalletSelectWalletRequest( + ffi.Pointer wallet, + ffi.Pointer aid, + ffi.Pointer len, + ) { + return _WalletSelectWalletRequest( + wallet, + aid, + len, + ); + } + + late final _WalletSelectWalletRequestPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>('WalletSelectWalletRequest'); + late final _WalletSelectWalletRequest = + _WalletSelectWalletRequestPtr.asFunction< + ffi.Pointer Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>(); + /// @brief Parse response to Wallet Select response /// /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls @@ -2232,34 +2287,42 @@ class Arculus { _WalletStoreDataPINResponsePtr.asFunction< int Function(ffi.Pointer, ffi.Pointer, int)>(); - /// @brief Function used to retrieve Public Key of a certain path. This command is a Get Data (INS : 0xCA) with tag BFC5] + /// @brief Function used to retrieve Public Key of a certain given path. /// /// @param wallet AppletObj context object created by WalletInit() used in all subsequent API calls - /// @param[in] response : response from the card when passed a request generated from WalletGetPublicKeyFromPathRequest - /// @param[in] responseLength : length in bytes of the response - /// @return The extended key (pubkey + chaincode) or NULL in case of error - ffi.Pointer WalletGetPublicKeyFromPathResponse( + /// @param[in] bipPath Path in ascii (and decimal for elts): ex: "m/44h/60h/0h/0/0" or "m/44'/60'/0'/0/0" + /// @param[in] bipPathLength The length of the BIP path + /// @param[in] Curve to use 0 default, 1 Secp256k1, 2 ED25519, 3 NIST256P1 + /// @param[out] len the function fill out this value with the length of the command (or response for functions handling responses) + /// @return The command to be sent to the card + ffi.Pointer WalletGetPublicKeyFromPathRequest( ffi.Pointer wallet, - ffi.Pointer response, - int responseLength, + ffi.Pointer bipPath, + int bipPathLength, + int curve, + ffi.Pointer len, ) { - return _WalletGetPublicKeyFromPathResponse( + return _WalletGetPublicKeyFromPathRequest( wallet, - response, - responseLength, + bipPath, + bipPathLength, + curve, + len, ); } - late final _WalletGetPublicKeyFromPathResponsePtr = _lookup< + late final _WalletGetPublicKeyFromPathRequestPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( + ffi.Pointer Function( ffi.Pointer, ffi.Pointer, - ffi.Size)>>('WalletGetPublicKeyFromPathResponse'); - late final _WalletGetPublicKeyFromPathResponse = - _WalletGetPublicKeyFromPathResponsePtr.asFunction< - ffi.Pointer Function( - ffi.Pointer, ffi.Pointer, int)>(); + ffi.Size, + ffi.Uint16, + ffi.Pointer)>>('WalletGetPublicKeyFromPathRequest'); + late final _WalletGetPublicKeyFromPathRequest = + _WalletGetPublicKeyFromPathRequestPtr.asFunction< + ffi.Pointer Function(ffi.Pointer, + ffi.Pointer, int, int, ffi.Pointer)>(); /// @brief Reset Wallet. reset the wallet on card /// @@ -2398,15 +2461,15 @@ class Arculus { ffi.Pointer Function(ffi.Pointer, ffi.Pointer, int, ffi.Pointer)>(); - ffi.Pointer ExtendedKey_getPubKey( - ffi.Pointer extendedKey, - ffi.Pointer len, - ) { - return _ExtendedKey_getPubKey( - extendedKey, - len, - ); - } + // ffi.Pointer ExtendedKey_getPubKey( + // ffi.Pointer extendedKey, + // ffi.Pointer len, + // ) { + // return _ExtendedKey_getPubKey( + // extendedKey, + // len, + // ); + // } late final _ExtendedKey_getPubKeyPtr = _lookup< ffi.NativeFunction<