Compare commits
15 Commits
master
...
charlesmch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dae1e36cb6 | ||
|
|
130ac42821 | ||
|
|
03ea44a99b | ||
|
|
94eb2cb5da | ||
|
|
3d847eb677 | ||
|
|
3c26d2a6e1 | ||
|
|
4bdbd2c560 | ||
|
|
34a26ffa93 | ||
|
|
dcabc9b5d5 | ||
|
|
e83b092234 | ||
|
|
50e3975cec | ||
|
|
8cc6b00e62 | ||
|
|
1ade44de3c | ||
|
|
5ef554b105 | ||
|
|
af52c0bbe0 |
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,4 +0,0 @@
|
||||
[submodule "Vendor/libmobilecoin-ios-artifacts"]
|
||||
path = Vendor/libmobilecoin-ios-artifacts
|
||||
url = https://github.com/mobilecoinofficial/libmobilecoin-ios-artifacts.git
|
||||
shallow = true
|
||||
@ -1,514 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
2706868D2474C4D800B82C57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2706868C2474C4D800B82C57 /* AppDelegate.swift */; };
|
||||
270686932474C4DA00B82C57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 270686922474C4DA00B82C57 /* Assets.xcassets */; };
|
||||
270686962474C4DA00B82C57 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 270686952474C4DA00B82C57 /* Preview Assets.xcassets */; };
|
||||
270686992474C4DA00B82C57 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 270686972474C4DA00B82C57 /* LaunchScreen.storyboard */; };
|
||||
27A1FABF24E8B89C001A0614 /* Performance Tests.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 27A1FABE24E8B89C001A0614 /* Performance Tests.xctestplan */; };
|
||||
F9800F2C17E6D5B2B021EB8D /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C0382FE8E8E7AAAC02CECA3 /* Pods_Example.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
05E35D1FAC2827984EE8B421 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
270686892474C4D800B82C57 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2706868C2474C4D800B82C57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
270686922474C4DA00B82C57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
270686952474C4DA00B82C57 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
270686982474C4DA00B82C57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
2706869A2474C4DA00B82C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
27A1FABE24E8B89C001A0614 /* Performance Tests.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Performance Tests.xctestplan"; sourceTree = "<group>"; };
|
||||
27A39C3C24C2622600B44786 /* Integration Tests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Integration Tests.xctestplan"; sourceTree = "<group>"; };
|
||||
27A39C3D24C2622600B44786 /* Unit Tests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Unit Tests.xctestplan"; sourceTree = "<group>"; };
|
||||
3AF40DB60A0ADA2CB879AE38 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4C0382FE8E8E7AAAC02CECA3 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FC106CAA4E993C2D6301C5CA /* Pods-Example.testable release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.testable release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.testable release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
270686862474C4D800B82C57 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F9800F2C17E6D5B2B021EB8D /* Pods_Example.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
270686802474C4D800B82C57 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2706868B2474C4D800B82C57 /* Example */,
|
||||
27A39C3B24C2622600B44786 /* TestPlans */,
|
||||
2706868A2474C4D800B82C57 /* Products */,
|
||||
4256C0AB87AF2BC2F24F1B92 /* Pods */,
|
||||
9E89A7B3975ECA671C9D8346 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2706868A2474C4D800B82C57 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
270686892474C4D800B82C57 /* Example.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2706868B2474C4D800B82C57 /* Example */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2706868C2474C4D800B82C57 /* AppDelegate.swift */,
|
||||
270686922474C4DA00B82C57 /* Assets.xcassets */,
|
||||
270686972474C4DA00B82C57 /* LaunchScreen.storyboard */,
|
||||
2706869A2474C4DA00B82C57 /* Info.plist */,
|
||||
270686942474C4DA00B82C57 /* Preview Content */,
|
||||
);
|
||||
path = Example;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
270686942474C4DA00B82C57 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
270686952474C4DA00B82C57 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
27A39C3B24C2622600B44786 /* TestPlans */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27A39C3D24C2622600B44786 /* Unit Tests.xctestplan */,
|
||||
27A39C3C24C2622600B44786 /* Integration Tests.xctestplan */,
|
||||
27A1FABE24E8B89C001A0614 /* Performance Tests.xctestplan */,
|
||||
);
|
||||
path = TestPlans;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4256C0AB87AF2BC2F24F1B92 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05E35D1FAC2827984EE8B421 /* Pods-Example.debug.xcconfig */,
|
||||
3AF40DB60A0ADA2CB879AE38 /* Pods-Example.release.xcconfig */,
|
||||
FC106CAA4E993C2D6301C5CA /* Pods-Example.testable release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E89A7B3975ECA671C9D8346 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C0382FE8E8E7AAAC02CECA3 /* Pods_Example.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
270686882474C4D800B82C57 /* Example */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 2706869D2474C4DA00B82C57 /* Build configuration list for PBXNativeTarget "Example" */;
|
||||
buildPhases = (
|
||||
A960891E414B6399548D3852 /* [CP] Check Pods Manifest.lock */,
|
||||
270686852474C4D800B82C57 /* Sources */,
|
||||
270686862474C4D800B82C57 /* Frameworks */,
|
||||
270686872474C4D800B82C57 /* Resources */,
|
||||
1851CDD4271ED50E0D6D8B7B /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Example;
|
||||
productName = Example;
|
||||
productReference = 270686892474C4D800B82C57 /* Example.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
270686812474C4D800B82C57 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1140;
|
||||
LastUpgradeCheck = 1250;
|
||||
ORGANIZATIONNAME = "MobileCoin Inc.";
|
||||
TargetAttributes = {
|
||||
270686882474C4D800B82C57 = {
|
||||
CreatedOnToolsVersion = 11.4.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 270686842474C4D800B82C57 /* Build configuration list for PBXProject "Example" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 270686802474C4D800B82C57;
|
||||
productRefGroup = 2706868A2474C4D800B82C57 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
270686882474C4D800B82C57 /* Example */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
270686872474C4D800B82C57 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
270686992474C4DA00B82C57 /* LaunchScreen.storyboard in Resources */,
|
||||
27A1FABF24E8B89C001A0614 /* Performance Tests.xctestplan in Resources */,
|
||||
270686962474C4DA00B82C57 /* Preview Assets.xcassets in Resources */,
|
||||
270686932474C4DA00B82C57 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
1851CDD4271ED50E0D6D8B7B /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A960891E414B6399548D3852 /* [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-Example-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;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
270686852474C4D800B82C57 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2706868D2474C4D800B82C57 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
270686972474C4DA00B82C57 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
270686982474C4DA00B82C57 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
2706869B2474C4DA00B82C57 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
2706869C2474C4DA00B82C57 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
2706869E2474C4DA00B82C57 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 05E35D1FAC2827984EE8B421 /* Pods-Example.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8JT9JJD9Y5;
|
||||
ENABLE_ONLY_ACTIVE_RESOURCES = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mobilecoin.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
2706869F2474C4DA00B82C57 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 3AF40DB60A0ADA2CB879AE38 /* Pods-Example.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8JT9JJD9Y5;
|
||||
ENABLE_ONLY_ACTIVE_RESOURCES = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mobilecoin.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
274D3FAD24E8B5DD004E2F4A /* Testable Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = "Testable Release";
|
||||
};
|
||||
274D3FAE24E8B5DD004E2F4A /* Testable Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FC106CAA4E993C2D6301C5CA /* Pods-Example.testable release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8JT9JJD9Y5;
|
||||
ENABLE_ONLY_ACTIVE_RESOURCES = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mobilecoin.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = "Testable Release";
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
270686842474C4D800B82C57 /* Build configuration list for PBXProject "Example" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
2706869B2474C4DA00B82C57 /* Debug */,
|
||||
2706869C2474C4DA00B82C57 /* Release */,
|
||||
274D3FAD24E8B5DD004E2F4A /* Testable Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
2706869D2474C4DA00B82C57 /* Build configuration list for PBXNativeTarget "Example" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
2706869E2474C4DA00B82C57 /* Debug */,
|
||||
2706869F2474C4DA00B82C57 /* Release */,
|
||||
274D3FAE24E8B5DD004E2F4A /* Testable Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 270686812474C4D800B82C57 /* Project object */;
|
||||
}
|
||||
10
Example/Example.xcworkspace/contents.xcworkspacedata
generated
10
Example/Example.xcworkspace/contents.xcworkspacedata
generated
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Example.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FILEHEADER</key>
|
||||
<string>
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "45F542481CBFF9E786B4F9148AC9D6F2"
|
||||
BuildableName = "MobileCoin-Unit-Core-IntegrationTests.xctest"
|
||||
BlueprintName = "MobileCoin-Unit-Core-IntegrationTests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:TestPlans/Integration Tests.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "45F542481CBFF9E786B4F9148AC9D6F2"
|
||||
BuildableName = "MobileCoin-Unit-Core-IntegrationTests.xctest"
|
||||
BlueprintName = "MobileCoin-Unit-Core-IntegrationTests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Testable Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E8A0545E992AC3A0A6B313DAF029D326"
|
||||
BuildableName = "MobileCoin-UI-Core-PerformanceTests.xctest"
|
||||
BlueprintName = "MobileCoin-UI-Core-PerformanceTests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Testable Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:TestPlans/Performance Tests.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E8A0545E992AC3A0A6B313DAF029D326"
|
||||
BuildableName = "MobileCoin-UI-Core-PerformanceTests.xctest"
|
||||
BlueprintName = "MobileCoin-UI-Core-PerformanceTests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Testable Release">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Testable Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C10663274EBBCA6839AB2898903FF5CB"
|
||||
BuildableName = "MobileCoin-Unit-Core-Tests.xctest"
|
||||
BlueprintName = "MobileCoin-Unit-Core-Tests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:TestPlans/Unit Tests.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C10663274EBBCA6839AB2898903FF5CB"
|
||||
BuildableName = "MobileCoin-Unit-Core-Tests.xctest"
|
||||
BlueprintName = "MobileCoin-Unit-Core-Tests"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Testable Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
source 'https://rubygems.org' do
|
||||
gem 'cocoapods', '~> 1.11'
|
||||
gem 'cocoapods-binary', :git => 'https://github.com/mobilecoinofficial/cocoapods-binary.git', :tag => 'v0.4.4.rev5'
|
||||
gem 'cocoapods-repo-update'
|
||||
gem 'cocoapods-keys'
|
||||
gem 'fastlane'
|
||||
end
|
||||
@ -1,306 +0,0 @@
|
||||
GIT
|
||||
remote: https://github.com/mobilecoinofficial/cocoapods-binary.git
|
||||
revision: 748a64cc890cfaee59cdd67e80d9ade82b1b179b
|
||||
tag: v0.4.4.rev5
|
||||
specs:
|
||||
cocoapods-binary (0.4.4)
|
||||
cocoapods (>= 1.5.0, < 2.0)
|
||||
fourflusher (~> 2.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
|
||||
GEM
|
||||
specs:
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.4)
|
||||
rexml
|
||||
RubyInline (3.12.5)
|
||||
ZenTest (~> 4.3)
|
||||
ZenTest (4.12.0)
|
||||
activesupport (6.1.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.520.0)
|
||||
aws-sdk-core (3.121.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.50.0)
|
||||
aws-sdk-core (~> 3, >= 3.121.2)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.104.0)
|
||||
aws-sdk-core (~> 3, >= 3.121.2)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.4.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.11.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.11.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.4.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 1.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.11.2)
|
||||
activesupport (>= 5.0, < 7)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.5.1)
|
||||
cocoapods-keys (2.2.1)
|
||||
dotenv
|
||||
osx_keychain
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-repo-update (0.0.4)
|
||||
cocoapods (~> 1.0, >= 1.3.0)
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.1.9)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.88.0)
|
||||
faraday (1.8.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0.1)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.1)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.5)
|
||||
fastlane (2.197.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
ffi (1.15.4)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.13.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-core (0.4.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.8.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.6.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-apis-storage_v1 (0.9.0)
|
||||
google-apis-core (>= 0.4, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.34.1)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.1.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.4)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.4.0)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.14.4)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.1)
|
||||
osx_keychain (1.0.2)
|
||||
RubyInline (~> 3)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.6)
|
||||
representable (3.1.1)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.16.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
trailblazer-option (0.1.1)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.21.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (~> 1.11)!
|
||||
cocoapods-binary!
|
||||
cocoapods-keys!
|
||||
cocoapods-repo-update!
|
||||
fastlane!
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.24
|
||||
@ -1,43 +0,0 @@
|
||||
.PHONY: default
|
||||
default: setup bootstrap build test
|
||||
|
||||
.PHONY: setup
|
||||
setup:
|
||||
bundle install
|
||||
|
||||
.PHONY: bootstrap
|
||||
bootstrap:
|
||||
bundle exec pod install
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
bundle exec fastlane gym \
|
||||
--scheme "Unit Tests" \
|
||||
--skip_archive \
|
||||
--skip_codesigning
|
||||
bundle exec fastlane gym \
|
||||
--scheme "Integration Tests" \
|
||||
--skip_archive \
|
||||
--skip_codesigning
|
||||
bundle exec fastlane gym \
|
||||
--scheme "Performance Tests" \
|
||||
--skip_archive \
|
||||
--skip_codesigning
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
bundle exec fastlane scan \
|
||||
--scheme "Unit Tests"
|
||||
bundle exec fastlane scan \
|
||||
--scheme "Performance Tests"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@[ ! -e test_output ] || rm -r test_output
|
||||
|
||||
# Maintenance commands
|
||||
|
||||
.PHONY: upgrade-deps
|
||||
upgrade-deps:
|
||||
bundle update
|
||||
bundle exec pod update
|
||||
@ -1,92 +0,0 @@
|
||||
source 'https://cdn.cocoapods.org/'
|
||||
|
||||
platform :ios, '10.0'
|
||||
|
||||
plugin 'cocoapods-repo-update'
|
||||
keep_source_code_for_prebuilt_frameworks!
|
||||
plugin 'cocoapods-keys', {
|
||||
:project => "MobileCoin",
|
||||
:keys => [
|
||||
"devNetworkAuthUsername",
|
||||
"devNetworkAuthPassword",
|
||||
"testNetTestAccountMnemonicsCommaSeparated",
|
||||
]}
|
||||
|
||||
use_frameworks!
|
||||
|
||||
ENV['MC_ENABLE_SWIFTLINT_SCRIPT'] = '1'
|
||||
ENV['MC_ENABLE_WARN_LONG_COMPILE_TIMES'] = '1'
|
||||
|
||||
target 'Example' do
|
||||
pod 'MobileCoin', path: '..'
|
||||
pod 'MobileCoin/Core', path: '..', testspecs: ['Tests', 'IntegrationTests', 'PerformanceTests']
|
||||
# pod 'MobileCoin', podspec: '../MobileCoin.podspec'
|
||||
# pod 'MobileCoin/Core', podspec: '../MobileCoin.podspec', testspecs: ['Tests', 'IntegrationTests']
|
||||
# pod 'MobileCoin', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git'
|
||||
# pod 'MobileCoin/Core', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git', testspecs: ['Tests', 'IntegrationTests']
|
||||
|
||||
pod 'LibMobileCoin'
|
||||
# pod 'LibMobileCoin', path: '../Vendor/libmobilecoin-ios-artifacts'
|
||||
# pod 'LibMobileCoin', podspec: '../Vendor/libmobilecoin-ios-artifacts/LibMobileCoin.podspec'
|
||||
# pod 'LibMobileCoin', git: 'https://github.com/the-real-adammork/libmobilecoin-ios-artifacts.git'
|
||||
|
||||
pod 'gRPC-Swift'
|
||||
pod 'SwiftProtobuf'
|
||||
pod 'SwiftLint'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
# Enable building tests using Testable Release build configuration
|
||||
installer.pods_project.targets.each do |target|
|
||||
next unless target.name == 'MobileCoin'
|
||||
target.build_configurations.each do |config|
|
||||
next unless config.name == 'Testable Release'
|
||||
config.build_settings['ENABLE_TESTABILITY'] = 'YES'
|
||||
end
|
||||
end
|
||||
|
||||
# Enable running performance tests on a physical device
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if target.name == 'AppHost-MobileCoin-UI-Tests'
|
||||
config.build_settings['DEVELOPMENT_TEAM'] = '8JT9JJD9Y5'
|
||||
elsif target.name.start_with? 'MobileCoin-UI-'
|
||||
config.build_settings['DEVELOPMENT_TEAM'] = '8JT9JJD9Y5'
|
||||
config.build_settings.delete('CODE_SIGN_IDENTITY[sdk=iphoneos*]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Bump deployment target to 9.0 for pods that explicitly specify 8.0, since 9.0 is the minimum
|
||||
# that Xcode 12 supports.
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
if Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']) < Gem::Version.new('9.0')
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Add Keys framework to Integration Tests for injecting values using cocoapods-keys
|
||||
installer.pods_project.targets.each do |target|
|
||||
next unless target.name == 'MobileCoin-Unit-Core-IntegrationTests'
|
||||
installer.pods_project.targets.each do |keys_target|
|
||||
next unless keys_target.name == 'Keys'
|
||||
target.add_dependency(keys_target)
|
||||
end
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings["FRAMEWORK_SEARCH_PATHS"] ||= "$(inherited)"
|
||||
config.build_settings["FRAMEWORK_SEARCH_PATHS"] << ' "${PODS_CONFIGURATION_BUILD_DIR}/Keys"'
|
||||
config.build_settings["OTHER_LDFLAGS"] ||= "$(inherited)"
|
||||
config.build_settings["OTHER_LDFLAGS"] << ' -framework "Keys"'
|
||||
end
|
||||
end
|
||||
|
||||
# Disable bitcode on test targets
|
||||
installer.pods_project.targets.each do |target|
|
||||
next unless ['MobileCoin-Unit-Core-IntegrationTests', 'MobileCoin-UI-Core-PerformanceTests', 'MobileCoin-Unit-Core-Tests'].find_index(target.name) != nil
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,204 +0,0 @@
|
||||
PODS:
|
||||
- _NIODataStructures (2.32.3)
|
||||
- CGRPCZlib (1.5.0)
|
||||
- CNIOAtomics (2.32.3)
|
||||
- CNIOBoringSSL (2.15.1)
|
||||
- CNIOBoringSSLShims (2.15.1):
|
||||
- CNIOBoringSSL (= 2.15.1)
|
||||
- CNIODarwin (2.32.3)
|
||||
- CNIOHTTPParser (2.32.3)
|
||||
- CNIOLinux (2.32.3)
|
||||
- CNIOWindows (2.32.3)
|
||||
- gRPC-Swift (1.5.0):
|
||||
- CGRPCZlib (= 1.5.0)
|
||||
- Logging (< 2.0.0, >= 1.4.0)
|
||||
- SwiftNIO (< 3.0.0, >= 2.32.0)
|
||||
- SwiftNIOExtras (< 2.0.0, >= 1.4.0)
|
||||
- SwiftNIOHTTP2 (< 2.0.0, >= 1.18.2)
|
||||
- SwiftNIOSSL (< 3.0.0, >= 2.14.0)
|
||||
- SwiftNIOTransportServices (< 2.0.0, >= 1.11.1)
|
||||
- SwiftProtobuf (< 2.0.0, >= 1.9.0)
|
||||
- Keys (1.0.1)
|
||||
- LibMobileCoin (1.2.0-pre3):
|
||||
- gRPC-Swift
|
||||
- SwiftProtobuf (~> 1.5)
|
||||
- Logging (1.4.0)
|
||||
- MobileCoin (1.2.0-pre2):
|
||||
- MobileCoin/Core (= 1.2.0-pre2)
|
||||
- MobileCoin/Core (1.2.0-pre2):
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin (~> 1.2.0-pre3)
|
||||
- Logging (~> 1.4)
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
- SwiftNIOHPACK
|
||||
- SwiftNIOHTTP1
|
||||
- SwiftProtobuf
|
||||
- MobileCoin/Core/IntegrationTests (1.2.0-pre2):
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin (~> 1.2.0-pre3)
|
||||
- Logging (~> 1.4)
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
- SwiftNIOHPACK
|
||||
- SwiftNIOHTTP1
|
||||
- SwiftProtobuf
|
||||
- MobileCoin/Core/PerformanceTests (1.2.0-pre2):
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin (~> 1.2.0-pre3)
|
||||
- Logging (~> 1.4)
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
- SwiftNIOHPACK
|
||||
- SwiftNIOHTTP1
|
||||
- SwiftProtobuf
|
||||
- MobileCoin/Core/Tests (1.2.0-pre2):
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin (~> 1.2.0-pre3)
|
||||
- Logging (~> 1.4)
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
- SwiftNIOHPACK
|
||||
- SwiftNIOHTTP1
|
||||
- SwiftProtobuf
|
||||
- SwiftLint (0.45.0)
|
||||
- SwiftNIO (2.32.3):
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOEmbedded (= 2.32.3)
|
||||
- SwiftNIOPosix (= 2.32.3)
|
||||
- SwiftNIOConcurrencyHelpers (2.32.3):
|
||||
- CNIOAtomics (= 2.32.3)
|
||||
- SwiftNIOCore (2.32.3):
|
||||
- CNIOLinux (= 2.32.3)
|
||||
- SwiftNIOConcurrencyHelpers (= 2.32.3)
|
||||
- SwiftNIOEmbedded (2.32.3):
|
||||
- _NIODataStructures (= 2.32.3)
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOExtras (1.10.2):
|
||||
- SwiftNIO (< 3, >= 2.32.0)
|
||||
- SwiftNIOFoundationCompat (2.32.3):
|
||||
- SwiftNIO (= 2.32.3)
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOHPACK (1.18.3):
|
||||
- SwiftNIO (< 3, >= 2.32.0)
|
||||
- SwiftNIOConcurrencyHelpers (< 3, >= 2.32.0)
|
||||
- SwiftNIOCore (< 3, >= 2.32.0)
|
||||
- SwiftNIOHTTP1 (< 3, >= 2.32.0)
|
||||
- SwiftNIOHTTP1 (2.32.3):
|
||||
- CNIOHTTPParser (= 2.32.3)
|
||||
- SwiftNIO (= 2.32.3)
|
||||
- SwiftNIOConcurrencyHelpers (= 2.32.3)
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOHTTP2 (1.18.3):
|
||||
- SwiftNIO (< 3, >= 2.32.0)
|
||||
- SwiftNIOConcurrencyHelpers (< 3, >= 2.32.0)
|
||||
- SwiftNIOCore (< 3, >= 2.32.0)
|
||||
- SwiftNIOHPACK (= 1.18.3)
|
||||
- SwiftNIOHTTP1 (< 3, >= 2.32.0)
|
||||
- SwiftNIOTLS (< 3, >= 2.32.0)
|
||||
- SwiftNIOPosix (2.32.3):
|
||||
- _NIODataStructures (= 2.32.3)
|
||||
- CNIODarwin (= 2.32.3)
|
||||
- CNIOLinux (= 2.32.3)
|
||||
- CNIOWindows (= 2.32.3)
|
||||
- SwiftNIOConcurrencyHelpers (= 2.32.3)
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOSSL (2.15.1):
|
||||
- CNIOBoringSSL (= 2.15.1)
|
||||
- CNIOBoringSSLShims (= 2.15.1)
|
||||
- SwiftNIO (< 3, >= 2.32.0)
|
||||
- SwiftNIOConcurrencyHelpers (< 3, >= 2.32.0)
|
||||
- SwiftNIOCore (< 3, >= 2.32.0)
|
||||
- SwiftNIOTLS (< 3, >= 2.32.0)
|
||||
- SwiftNIOTLS (2.32.3):
|
||||
- SwiftNIO (= 2.32.3)
|
||||
- SwiftNIOCore (= 2.32.3)
|
||||
- SwiftNIOTransportServices (1.11.3):
|
||||
- SwiftNIO (< 3, >= 2.32.0)
|
||||
- SwiftNIOConcurrencyHelpers (< 3, >= 2.32.0)
|
||||
- SwiftNIOFoundationCompat (< 3, >= 2.32.0)
|
||||
- SwiftNIOTLS (< 3, >= 2.32.0)
|
||||
- SwiftProtobuf (1.18.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- gRPC-Swift
|
||||
- Keys (from `Pods/CocoaPodsKeys`)
|
||||
- LibMobileCoin
|
||||
- MobileCoin (from `..`)
|
||||
- MobileCoin/Core (from `..`)
|
||||
- MobileCoin/Core/IntegrationTests (from `..`)
|
||||
- MobileCoin/Core/PerformanceTests (from `..`)
|
||||
- MobileCoin/Core/Tests (from `..`)
|
||||
- SwiftLint
|
||||
- SwiftProtobuf
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- _NIODataStructures
|
||||
- CGRPCZlib
|
||||
- CNIOAtomics
|
||||
- CNIOBoringSSL
|
||||
- CNIOBoringSSLShims
|
||||
- CNIODarwin
|
||||
- CNIOHTTPParser
|
||||
- CNIOLinux
|
||||
- CNIOWindows
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin
|
||||
- Logging
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
- SwiftNIOConcurrencyHelpers
|
||||
- SwiftNIOCore
|
||||
- SwiftNIOEmbedded
|
||||
- SwiftNIOExtras
|
||||
- SwiftNIOFoundationCompat
|
||||
- SwiftNIOHPACK
|
||||
- SwiftNIOHTTP1
|
||||
- SwiftNIOHTTP2
|
||||
- SwiftNIOPosix
|
||||
- SwiftNIOSSL
|
||||
- SwiftNIOTLS
|
||||
- SwiftNIOTransportServices
|
||||
- SwiftProtobuf
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Keys:
|
||||
:path: Pods/CocoaPodsKeys
|
||||
MobileCoin:
|
||||
:path: ".."
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
_NIODataStructures: e2077c7dc7c1d6c93e698c85fe04d663a17f53a4
|
||||
CGRPCZlib: db324e4e4e71262d48faceb86b52dd7d4f71ff62
|
||||
CNIOAtomics: 4dde57e1838a29a9b23ef91617505f34751cdbe5
|
||||
CNIOBoringSSL: c99129423da079a9eb74bcfc7cfec41a6775cf94
|
||||
CNIOBoringSSLShims: 902ae35fea0b6be5eefb4fdce906751886cfa46f
|
||||
CNIODarwin: 0489511f8486443af71ff986ccd5abbc680ae713
|
||||
CNIOHTTPParser: f7a6816f7ddbe7dfa57a74cd36dc2db2c53b56e8
|
||||
CNIOLinux: 5921dfefbc4bbe017380b34c510855622147ea41
|
||||
CNIOWindows: f5aa9dfb401b440a7b4c9cd911e53e981a787193
|
||||
gRPC-Swift: 8942047451bf81413077e6b94bf4213e47b181cc
|
||||
Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9
|
||||
LibMobileCoin: f3c44fb94b8647bd5cac3aedf0b1ca24f6cc7201
|
||||
Logging: beeb016c9c80cf77042d62e83495816847ef108b
|
||||
MobileCoin: 339e51b8739b7ea2f0519cc7bdde8c4e05c2b374
|
||||
SwiftLint: e5c7f1fba68eccfc51509d5b2ce1699f5502e0c7
|
||||
SwiftNIO: bb336ceef32850e9671d3fa0e0cc2b9add3b5948
|
||||
SwiftNIOConcurrencyHelpers: ca2594e10749655f42baf5468212be83d2f94fe3
|
||||
SwiftNIOCore: 9deed6620f80c7c82e8e2c2ffb9864495416d892
|
||||
SwiftNIOEmbedded: b7ccf12b402dff35a5d4356990a6253621e4337d
|
||||
SwiftNIOExtras: 70f09aa8eca3ab6baeaf1993da9c855b6e95e97f
|
||||
SwiftNIOFoundationCompat: d3b888766e7c67354a4e4e145d38edf9586efa0c
|
||||
SwiftNIOHPACK: e2fc784ce453bec4c058b21071e89fb7e542ac30
|
||||
SwiftNIOHTTP1: 349a16aae363250cd49f430a9fdb93cff518adfa
|
||||
SwiftNIOHTTP2: a0322f3dcecd949e03df65f4dac106411df0f12c
|
||||
SwiftNIOPosix: e4988a8dcfd5a6319bde219d7a3d0acc5fbe7a89
|
||||
SwiftNIOSSL: 7c2ddcbcbb2a8188468b7fe9c2bc6124df4b3772
|
||||
SwiftNIOTLS: 1b8290ec775238ccc714ed842d929494df95a2c2
|
||||
SwiftNIOTransportServices: 1fbbdb58510af3c53a838a1dbea98f18132dc952
|
||||
SwiftProtobuf: c3c12645230d9b09c72267e0de89468c5543bd86
|
||||
|
||||
PODFILE CHECKSUM: 46e25fe8f13c4beb3ac1e4ab1a9512960701d05d
|
||||
|
||||
COCOAPODS: 1.11.2
|
||||
@ -1,30 +0,0 @@
|
||||
{
|
||||
"configurations" : [
|
||||
{
|
||||
"id" : "7B8CC01D-B029-4322-84A2-F7F086835547",
|
||||
"name" : "Configuration 1",
|
||||
"options" : {
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultOptions" : {
|
||||
|
||||
},
|
||||
"testTargets" : [
|
||||
{
|
||||
"skippedTests" : [
|
||||
"ConsensusConnectionIntTests\/testWrongTrustRootFails()",
|
||||
"FogBlockConnectionIntTests\/testDoSGetBlocks()",
|
||||
"FogBlockConnectionIntTests\/testGetBlockZero()",
|
||||
"MobileCoinClientPublicApiIntTests\/testWrongConsensusTrustRootReturnsError()"
|
||||
],
|
||||
"target" : {
|
||||
"containerPath" : "container:Pods\/Pods.xcodeproj",
|
||||
"identifier" : "45F542481CBFF9E786B4F9148AC9D6F2",
|
||||
"name" : "MobileCoin-Unit-Core-IntegrationTests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"configurations" : [
|
||||
{
|
||||
"id" : "98A4F1B1-FE76-43E0-BD61-A2B0B7F719FC",
|
||||
"name" : "Configuration 1",
|
||||
"options" : {
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultOptions" : {
|
||||
"uiTestingScreenshotsLifetime" : "keepNever",
|
||||
"userAttachmentLifetime" : "keepNever"
|
||||
},
|
||||
"testTargets" : [
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:Pods\/Pods.xcodeproj",
|
||||
"identifier" : "E8A0545E992AC3A0A6B313DAF029D326",
|
||||
"name" : "MobileCoin-UI-Core-PerformanceTests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
{
|
||||
"configurations" : [
|
||||
{
|
||||
"id" : "E10088A3-9143-42A3-8186-B64A2E756B6A",
|
||||
"name" : "Configuration 1",
|
||||
"options" : {
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultOptions" : {
|
||||
|
||||
},
|
||||
"testTargets" : [
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:Pods\/Pods.xcodeproj",
|
||||
"identifier" : "C10663274EBBCA6839AB2898903FF5CB",
|
||||
"name" : "MobileCoin-Unit-Core-Tests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
}
|
||||
4
Gemfile
4
Gemfile
@ -1,4 +0,0 @@
|
||||
source 'https://rubygems.org' do
|
||||
gem 'cocoapods', '~> 1.11'
|
||||
gem 'jazzy'
|
||||
end
|
||||
121
Gemfile.lock
121
Gemfile.lock
@ -1,121 +0,0 @@
|
||||
GEM
|
||||
specs:
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.4)
|
||||
rexml
|
||||
activesupport (6.1.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
atomos (0.1.3)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.11.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.11.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.4.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 1.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.11.2)
|
||||
activesupport (>= 5.0, < 7)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.5.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.1.9)
|
||||
escape (0.0.4)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
ffi (1.15.4)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jazzy (0.14.1)
|
||||
cocoapods (~> 1.5)
|
||||
mustache (~> 1.1)
|
||||
open4 (~> 1.3)
|
||||
redcarpet (~> 3.4)
|
||||
rexml (~> 3.2)
|
||||
rouge (>= 2.0.6, < 4.0)
|
||||
sassc (~> 2.1)
|
||||
sqlite3 (~> 1.3)
|
||||
xcinvoke (~> 0.3.0)
|
||||
json (2.6.1)
|
||||
liferaft (0.0.6)
|
||||
minitest (5.14.4)
|
||||
molinillo (0.8.0)
|
||||
mustache (1.1.1)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
netrc (0.11.0)
|
||||
open4 (1.3.4)
|
||||
public_suffix (4.0.6)
|
||||
redcarpet (3.5.1)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.1)
|
||||
ruby-macho (2.5.1)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sqlite3 (1.4.2)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
xcinvoke (0.3.0)
|
||||
liferaft (~> 0.0.6)
|
||||
xcodeproj (1.21.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
zeitwerk (2.5.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (~> 1.11)!
|
||||
jazzy!
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.24
|
||||
7
Glue/MobileCoinMinimal.m
Normal file
7
Glue/MobileCoinMinimal.m
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
#ifdef DEBUG
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#else
|
||||
static const NSUInteger ddLogLevel = DDLogLevelInfo;
|
||||
#endif
|
||||
34
Glue/MobileCoinMinimal.swift
Normal file
34
Glue/MobileCoinMinimal.swift
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum MobileCoinMinimalError: Error {
|
||||
case invalidReceipt
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public class MobileCoinMinimal {
|
||||
|
||||
public static func txOutPublicKey(forReceiptData serializedData: Data) throws -> Data {
|
||||
guard let proto = try? External_Receipt(serializedData: serializedData) else {
|
||||
logger.warning(
|
||||
"External_Receipt deserialization failed. serializedData: " +
|
||||
"\(redacting: serializedData.base64EncodedString())",
|
||||
logFunction: false)
|
||||
throw MobileCoinMinimalError.invalidReceipt
|
||||
}
|
||||
let txOutPublicKey = proto.publicKey.data
|
||||
return txOutPublicKey
|
||||
}
|
||||
|
||||
public static func isValidMobileCoinPublicAddress(_ serializedData: Data) -> Bool {
|
||||
guard let proto = try? External_PublicAddress(serializedData: serializedData) else {
|
||||
logger.warning("External_PublicAddress deserialization failed. serializedData: " +
|
||||
"\(redacting: serializedData.base64EncodedString())")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
1817
LibMobileCoin/Sources/Generated/Proto/external.pb.swift
Normal file
1817
LibMobileCoin/Sources/Generated/Proto/external.pb.swift
Normal file
File diff suppressed because it is too large
Load Diff
104
Makefile
104
Makefile
@ -1,104 +0,0 @@
|
||||
.PHONY: default
|
||||
default: setup bootstrap build test
|
||||
|
||||
# Commands
|
||||
|
||||
.PHONY: setup
|
||||
setup:
|
||||
bundle install
|
||||
@$(MAKE) --directory=Example setup
|
||||
|
||||
.PHONY: bootstrap
|
||||
bootstrap:
|
||||
@$(MAKE) --directory=Example bootstrap
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
@$(MAKE) --directory=Example build
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@$(MAKE) --directory=Example test
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-docs
|
||||
@$(MAKE) --directory=Example clean
|
||||
|
||||
.PHONY: lint
|
||||
lint: swiftlint
|
||||
|
||||
.PHONY: lint-all
|
||||
lint-all: lint lint-circleci lint-podspec lint-docs
|
||||
|
||||
.PHONY: publish
|
||||
publish: tag-release publish-podspec
|
||||
|
||||
# Release
|
||||
|
||||
.PHONY: tag-release
|
||||
tag-release:
|
||||
VERSION="$$(bundle exec pod ipc spec MobileCoin.podspec | jq -r '.version')" && \
|
||||
git tag "v$$VERSION" && \
|
||||
git push git@github.com:mobilecoinofficial/MobileCoin-Swift.git "refs/tags/v$$VERSION"
|
||||
|
||||
# MobileCoin pod
|
||||
|
||||
.PHONY: lint-podspec
|
||||
lint-podspec:
|
||||
bundle exec pod spec lint MobileCoin.podspec --skip-tests
|
||||
|
||||
.PHONY: publish-podspec
|
||||
publish-podspec:
|
||||
bundle exec pod trunk push MobileCoin.podspec --skip-tests
|
||||
|
||||
# CircleCI
|
||||
|
||||
.PHONY: install-circleci
|
||||
install-circleci:
|
||||
brew install circleci
|
||||
|
||||
.PHONY: lint-circleci
|
||||
lint-circleci:
|
||||
@command -v circleci >/dev/null || $(MAKE) install-circleci
|
||||
circleci config validate
|
||||
|
||||
# Documentation
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
bundle exec jazzy
|
||||
|
||||
.PHONY: clean-docs
|
||||
clean-docs:
|
||||
@[ ! -e docs ] || rm -r docs
|
||||
|
||||
.PHONY: lint-docs
|
||||
lint-docs:
|
||||
@[ -e docs ] || $(MAKE) docs
|
||||
|
||||
@# Check that there are no categories that start with `Other `, since that signifies that a new public
|
||||
@# type was added but was not added to a category in `.jazzy.yaml`
|
||||
@[[ "$$( \
|
||||
name_regex='^Other (?:Classes|Constants|Enumerations|Extensions|Functions|Protocols|Structures|Type Aliases|Type Definitions)$$'; \
|
||||
cat docs/search.json | jq ".[] \
|
||||
| select(has(\"parent_name\") | not) \
|
||||
| select(has(\"name\")) \
|
||||
| select(.name | test(\"$$name_regex\"))" \
|
||||
)" == "" ]] || { echo 'Error: Found one or more public types not categorized in jazzy.'; exit 1; }
|
||||
|
||||
# Swiftlint
|
||||
|
||||
.PHONY: autocorrect
|
||||
autocorrect:
|
||||
@PATH="./Example/Pods/SwiftLint:$$PATH" swiftlint autocorrect
|
||||
|
||||
.PHONY: swiftlint
|
||||
swiftlint:
|
||||
@PATH="./Example/Pods/SwiftLint:$$PATH" swiftlint
|
||||
|
||||
# Maintenance
|
||||
|
||||
.PHONY: upgrade-deps
|
||||
upgrade-deps:
|
||||
bundle update
|
||||
$(MAKE) -C Example upgrade-deps
|
||||
@ -2,7 +2,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
|
||||
|
||||
s.name = "MobileCoin"
|
||||
s.name = "MobileCoinMinimal"
|
||||
s.version = "1.2.0-pre2"
|
||||
s.summary = "A library for communicating with MobileCoin network"
|
||||
|
||||
@ -25,48 +25,26 @@ Pod::Spec.new do |s|
|
||||
|
||||
# ――― Subspecs ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
|
||||
|
||||
s.default_subspec = "Core"
|
||||
|
||||
s.subspec "Core" do |subspec|
|
||||
subspec.source_files = [
|
||||
"Sources/**/*.{h,m,swift}",
|
||||
]
|
||||
|
||||
subspec.dependency "LibMobileCoin", "~> 1.2.0-pre3"
|
||||
|
||||
subspec.dependency "gRPC-Swift"
|
||||
subspec.dependency "Logging", "~> 1.4"
|
||||
subspec.dependency "SwiftNIO"
|
||||
subspec.dependency "SwiftNIOHPACK"
|
||||
subspec.dependency "SwiftNIOHTTP1"
|
||||
subspec.dependency "SwiftProtobuf"
|
||||
|
||||
subspec.test_spec do |test_spec|
|
||||
test_spec.source_files = "Tests/{Unit,Common}/**/*.swift"
|
||||
test_spec.resources = [
|
||||
"Tests/Common/FixtureData/**/*",
|
||||
"Vendor/libmobilecoin-ios-artifacts/Vendor/mobilecoin/test-vectors/vectors/**/*",
|
||||
]
|
||||
end
|
||||
|
||||
subspec.test_spec 'IntegrationTests' do |test_spec|
|
||||
test_spec.source_files = "Tests/{Integration,Common}/**/*.swift"
|
||||
test_spec.resource = "Tests/Common/FixtureData/**/*"
|
||||
end
|
||||
|
||||
subspec.test_spec 'PerformanceTests' do |test_spec|
|
||||
test_spec.source_files = "Tests/{Performance,Common}/**/*.swift"
|
||||
|
||||
test_spec.test_type = :ui
|
||||
test_spec.requires_app_host = true
|
||||
end
|
||||
|
||||
unless ENV["MC_ENABLE_SWIFTLINT_SCRIPT"].nil?
|
||||
subspec.dependency 'SwiftLint'
|
||||
end
|
||||
end
|
||||
s.source_files = [
|
||||
"Sources/**/*.{h,m,swift}",
|
||||
"LibMobileCoin/**/*.{h,m,swift}",
|
||||
"Glue/**/*.{h,m,swift}",
|
||||
]
|
||||
|
||||
s.dependency "Logging", "~> 1.4"
|
||||
s.dependency "SwiftProtobuf"
|
||||
s.dependency "CocoaLumberjack"
|
||||
|
||||
# s.test_spec do |test_spec|
|
||||
s.test_spec 'Tests' do |test_spec|
|
||||
test_spec.source_files = [
|
||||
'Tests/**/*.{h,m,swift}',
|
||||
'TestGlue/**/*.{h,m,swift}',
|
||||
]
|
||||
# test_spec.resources = 'Tests/**/*.{json,encrypted,webp}'
|
||||
test_spec.resources = 'Tests/**/*.*'
|
||||
end
|
||||
|
||||
# ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
|
||||
|
||||
s.swift_version = "5.2"
|
||||
@ -92,23 +70,5 @@ Pod::Spec.new do |s|
|
||||
end
|
||||
|
||||
s.pod_target_xcconfig = pod_target_xcconfig
|
||||
|
||||
unless ENV["MC_ENABLE_SWIFTLINT_SCRIPT"].nil?
|
||||
s.script_phases = [
|
||||
{
|
||||
:name => "Run SwiftLint",
|
||||
:execution_position => :any,
|
||||
:script => <<~'EOS'
|
||||
SWIFTLINT="${PODS_ROOT}/SwiftLint/swiftlint"
|
||||
if which ${SWIFTLINT} >/dev/null; then
|
||||
cd "${PODS_TARGET_SRCROOT}"
|
||||
${SWIFTLINT}
|
||||
else
|
||||
echo "warning: SwiftLint not installed, run \`pod install\`"
|
||||
fi
|
||||
EOS
|
||||
},
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension Account {
|
||||
struct BalanceUpdater {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let account: ReadWriteDispatchLock<Account>
|
||||
private let txOutFetcher: FogView.TxOutFetcher
|
||||
private let viewKeyScanner: FogViewKeyScanner
|
||||
private let fogKeyImageChecker: FogKeyImageChecker
|
||||
|
||||
init(
|
||||
account: ReadWriteDispatchLock<Account>,
|
||||
fogViewService: FogViewService,
|
||||
fogKeyImageService: FogKeyImageService,
|
||||
fogBlockService: FogBlockService,
|
||||
fogQueryScalingStrategy: FogQueryScalingStrategy,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(
|
||||
label: "com.mobilecoin.\(Account.self).\(Self.self)",
|
||||
target: targetQueue)
|
||||
self.account = account
|
||||
self.txOutFetcher = FogView.TxOutFetcher(
|
||||
fogView: account.mapLockWithoutLocking { $0.fogView },
|
||||
accountKey: account.accessWithoutLocking.accountKey,
|
||||
fogViewService: fogViewService,
|
||||
fogQueryScalingStrategy: fogQueryScalingStrategy,
|
||||
targetQueue: targetQueue)
|
||||
self.viewKeyScanner = FogViewKeyScanner(
|
||||
accountKey: account.accessWithoutLocking.accountKey,
|
||||
fogBlockService: fogBlockService)
|
||||
self.fogKeyImageChecker = FogKeyImageChecker(
|
||||
fogKeyImageService: fogKeyImageService,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func updateBalance(completion: @escaping (Result<Balance, ConnectionError>) -> Void) {
|
||||
logger.info("Updating balance...", logFunction: false)
|
||||
checkForNewTxOuts {
|
||||
guard $0.successOr(completion: completion) != nil else {
|
||||
logger.warning(
|
||||
"Failed to update balance: checkForNewTxOuts error: \($0)",
|
||||
logFunction: false)
|
||||
return
|
||||
}
|
||||
|
||||
self.checkForSpentTxOuts {
|
||||
guard $0.successOr(completion: completion) != nil else {
|
||||
logger.warning(
|
||||
"Failed to update balance: checkForSpentTxOuts error: \($0)",
|
||||
logFunction: false)
|
||||
return
|
||||
}
|
||||
|
||||
let balance = self.account.readSync { $0.cachedBalance }
|
||||
|
||||
logger.info(
|
||||
"Balance update successful. balance: \(redacting: balance)",
|
||||
logFunction: false)
|
||||
completion(.success(balance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkForNewTxOuts(completion: @escaping (Result<(), ConnectionError>) -> Void) {
|
||||
checkForNewFogViewTxOuts {
|
||||
guard $0.successOr(completion: completion) != nil else { return }
|
||||
|
||||
self.viewKeyScanUnscannedMissedBlocks(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func checkForNewFogViewTxOuts(completion: @escaping (Result<(), ConnectionError>) -> Void) {
|
||||
txOutFetcher.fetchTxOuts(partialResultsWithWriteLock: { newTxOuts in
|
||||
logger.info(
|
||||
"Found \(redacting: newTxOuts.count) new TxOuts using Fog View",
|
||||
logFunction: false)
|
||||
let account = self.account.accessWithoutLocking
|
||||
account.addTxOuts(newTxOuts)
|
||||
}, completion: completion)
|
||||
}
|
||||
|
||||
func viewKeyScanUnscannedMissedBlocks(
|
||||
completion: @escaping (Result<(), ConnectionError>) -> Void
|
||||
) {
|
||||
let unscannedBlockRanges = account.readSync { $0.unscannedMissedBlocksRanges }
|
||||
guard !unscannedBlockRanges.isEmpty else {
|
||||
logger.debug("0 unscanned missed blocks, skipping.", logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
return
|
||||
}
|
||||
viewKeyScanner.viewKeyScanBlocks(blockRanges: unscannedBlockRanges) {
|
||||
completion($0.map { foundTxOuts in
|
||||
self.account.writeSync {
|
||||
$0.addViewKeyScanResults(
|
||||
scannedBlockRanges: unscannedBlockRanges,
|
||||
foundTxOuts: foundTxOuts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkForSpentTxOuts(completion: @escaping (Result<(), ConnectionError>) -> Void) {
|
||||
let keyImageTrackers = account.mapLock { account in
|
||||
account.allTxOutTrackers.filter { !$0.isSpent }.map { $0.keyImageTracker }
|
||||
}
|
||||
let queries = keyImageTrackers.readSync {
|
||||
$0.map { ($0.keyImage, $0.nextKeyImageQueryBlockIndex) }
|
||||
}
|
||||
fogKeyImageChecker.checkKeyImages(keyImageQueries: queries) {
|
||||
completion($0.map { statuses in
|
||||
keyImageTrackers.writeSync { keyImageTrackers in
|
||||
for (tracker, status) in zip(keyImageTrackers, statuses) {
|
||||
tracker.spentStatus = status
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,231 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Account {
|
||||
struct TransactionEstimator {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let account: ReadWriteDispatchLock<Account>
|
||||
private let feeFetcher: BlockchainFeeFetcher
|
||||
private let txOutSelector: TxOutSelector
|
||||
|
||||
init(
|
||||
account: ReadWriteDispatchLock<Account>,
|
||||
feeFetcher: BlockchainFeeFetcher,
|
||||
txOutSelectionStrategy: TxOutSelectionStrategy,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(
|
||||
label: "com.mobilecoin.\(Account.self).\(Self.self))",
|
||||
target: targetQueue)
|
||||
self.account = account
|
||||
self.feeFetcher = feeFetcher
|
||||
self.txOutSelector = TxOutSelector(txOutSelectionStrategy: txOutSelectionStrategy)
|
||||
}
|
||||
|
||||
func amountTransferable(
|
||||
feeLevel: FeeLevel,
|
||||
completion: @escaping (Result<UInt64, BalanceTransferEstimationFetcherError>) -> Void
|
||||
) {
|
||||
feeFetcher.feeStrategy(for: feeLevel) {
|
||||
completion($0.mapError { .connectionError($0) }
|
||||
.flatMap { feeStrategy in
|
||||
let txOuts = self.account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Calculating amountTransferable. feeLevel: \(feeLevel), " +
|
||||
"unspentTxOutValues: \(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return self.txOutSelector
|
||||
.amountTransferable(feeStrategy: feeStrategy, txOuts: txOuts)
|
||||
.mapError {
|
||||
switch $0 {
|
||||
case .feeExceedsBalance(let reason):
|
||||
return .feeExceedsBalance(reason)
|
||||
case .balanceOverflow(let reason):
|
||||
return .balanceOverflow(reason)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
logger.info(
|
||||
"amountTransferable: \(redacting: $0)",
|
||||
logFunction: false)
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func estimateTotalFee(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel,
|
||||
completion: @escaping (Result<UInt64, TransactionEstimationFetcherError>) -> Void
|
||||
) {
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "estimateTotalFee failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(.invalidInput(errorMessage)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
feeFetcher.feeStrategy(for: feeLevel) {
|
||||
completion($0.mapError { .connectionError($0) }
|
||||
.flatMap { feeStrategy in
|
||||
let txOuts = self.account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Estimating total fee: amount: \(redacting: amount), feeLevel: " +
|
||||
"\(feeLevel), unspentTxOutValues: " +
|
||||
"\(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return self.txOutSelector
|
||||
.estimateTotalFee(
|
||||
toSendAmount: amount,
|
||||
feeStrategy: feeStrategy,
|
||||
txOuts: txOuts)
|
||||
.mapError { _ in
|
||||
TransactionEstimationFetcherError.insufficientBalance()
|
||||
}
|
||||
.map {
|
||||
logger.info(
|
||||
"estimateTotalFee: \(redacting: $0.totalFee), " +
|
||||
"requiresDefrag: \($0.requiresDefrag)",
|
||||
logFunction: false)
|
||||
return $0.totalFee
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func requiresDefragmentation(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel,
|
||||
completion: @escaping (Result<Bool, TransactionEstimationFetcherError>) -> Void
|
||||
) {
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "requiresDefragmentation failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(.invalidInput(errorMessage)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
feeFetcher.feeStrategy(for: feeLevel) {
|
||||
completion($0.mapError { .connectionError($0) }
|
||||
.flatMap { feeStrategy in
|
||||
let txOuts = self.account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Calculation defragmentation required: amount: \(redacting: amount), " +
|
||||
"feeLevel: \(feeLevel), unspentTxOutValues: " +
|
||||
"\(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return self.txOutSelector
|
||||
.estimateTotalFee(
|
||||
toSendAmount: amount,
|
||||
feeStrategy: feeStrategy,
|
||||
txOuts: txOuts)
|
||||
.mapError { _ in
|
||||
TransactionEstimationFetcherError.insufficientBalance()
|
||||
}
|
||||
.map {
|
||||
logger.info(
|
||||
"requiresDefragmentation: \($0.requiresDefrag), totalFee: " +
|
||||
"\(redacting: $0.totalFee)",
|
||||
logFunction: false)
|
||||
return $0.requiresDefrag
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Account.TransactionEstimator {
|
||||
@available(*, deprecated, message: "Use amountTransferable(feeLevel:completion:) instead")
|
||||
func amountTransferable(feeLevel: FeeLevel)
|
||||
-> Result<UInt64, BalanceTransferEstimationError>
|
||||
{
|
||||
let feeStrategy = feeLevel.defaultFeeStrategy
|
||||
let txOuts = account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Calculating amountTransferable. feeLevel: \(feeLevel), unspentTxOutValues: " +
|
||||
"\(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return txOutSelector.amountTransferable(feeStrategy: feeStrategy, txOuts: txOuts)
|
||||
.mapError {
|
||||
switch $0 {
|
||||
case .feeExceedsBalance(let reason):
|
||||
return .feeExceedsBalance(reason)
|
||||
case .balanceOverflow(let reason):
|
||||
return .balanceOverflow(reason)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
logger.info("amountTransferable: \(redacting: $0)", logFunction: false)
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message:
|
||||
"Use estimateTotalFee(toSendAmount:feeLevel:completion:) instead")
|
||||
func estimateTotalFee(toSendAmount amount: UInt64, feeLevel: FeeLevel)
|
||||
-> Result<UInt64, TransactionEstimationError>
|
||||
{
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "estimateTotalFee failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.invalidInput(errorMessage))
|
||||
}
|
||||
|
||||
let feeStrategy = feeLevel.defaultFeeStrategy
|
||||
let txOuts = account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Estimating total fee: amount: \(redacting: amount), feeLevel: \(feeLevel), " +
|
||||
"unspentTxOutValues: \(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return txOutSelector
|
||||
.estimateTotalFee(toSendAmount: amount, feeStrategy: feeStrategy, txOuts: txOuts)
|
||||
.mapError { _ -> TransactionEstimationError in .insufficientBalance() }
|
||||
.map {
|
||||
logger.info(
|
||||
"estimateTotalFee: \(redacting: $0.totalFee), requiresDefrag: " +
|
||||
"\($0.requiresDefrag)",
|
||||
logFunction: false)
|
||||
return $0.totalFee
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message:
|
||||
"Use requiresDefragmentation(toSendAmount:feeLevel:completion:) instead")
|
||||
func requiresDefragmentation(toSendAmount amount: UInt64, feeLevel: FeeLevel)
|
||||
-> Result<Bool, TransactionEstimationError>
|
||||
{
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "requiresDefragmentation failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.invalidInput(errorMessage))
|
||||
}
|
||||
|
||||
let feeStrategy = feeLevel.defaultFeeStrategy
|
||||
let txOuts = account.readSync { $0.unspentTxOuts }
|
||||
logger.info(
|
||||
"Calculation defragmentation required: amount: \(redacting: amount), feeLevel: " +
|
||||
"\(feeLevel), unspentTxOutValues: \(redacting: txOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
return txOutSelector
|
||||
.estimateTotalFee(toSendAmount: amount, feeStrategy: feeStrategy, txOuts: txOuts)
|
||||
.mapError { _ -> TransactionEstimationError in .insufficientBalance() }
|
||||
.map {
|
||||
logger.info(
|
||||
"requiresDefragmentation: \($0.requiresDefrag), totalFee: " +
|
||||
"\(redacting: $0.totalFee)",
|
||||
logFunction: false)
|
||||
return $0.requiresDefrag
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,222 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length function_body_length multiline_arguments
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Account {
|
||||
struct TransactionOperations {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let account: ReadWriteDispatchLock<Account>
|
||||
private let feeFetcher: BlockchainFeeFetcher
|
||||
private let txOutSelector: TxOutSelector
|
||||
private let transactionPreparer: TransactionPreparer
|
||||
|
||||
init(
|
||||
account: ReadWriteDispatchLock<Account>,
|
||||
fogMerkleProofService: FogMerkleProofService,
|
||||
fogResolverManager: FogResolverManager,
|
||||
feeFetcher: BlockchainFeeFetcher,
|
||||
txOutSelectionStrategy: TxOutSelectionStrategy,
|
||||
mixinSelectionStrategy: MixinSelectionStrategy,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(
|
||||
label: "com.mobilecoin.\(Account.self).\(Self.self))",
|
||||
target: targetQueue)
|
||||
self.account = account
|
||||
self.feeFetcher = feeFetcher
|
||||
self.txOutSelector = TxOutSelector(txOutSelectionStrategy: txOutSelectionStrategy)
|
||||
self.transactionPreparer = TransactionPreparer(
|
||||
accountKey: account.accessWithoutLocking.accountKey,
|
||||
fogMerkleProofService: fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func prepareTransaction(
|
||||
to recipient: PublicAddress,
|
||||
amount: UInt64,
|
||||
fee: UInt64,
|
||||
completion: @escaping (
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "prepareTransactionWithFee failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(.invalidInput(errorMessage)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let (unspentTxOuts, ledgerBlockCount) =
|
||||
account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) }
|
||||
logger.info(
|
||||
"Preparing transaction with provided fee... recipient: \(redacting: recipient), " +
|
||||
"amount: \(redacting: amount), fee: \(redacting: fee), unspentTxOutValues: " +
|
||||
"\(redacting: unspentTxOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
switch txOutSelector
|
||||
.selectTransactionInputs(amount: amount, fee: fee, fromTxOuts: unspentTxOuts)
|
||||
.mapError({ error -> TransactionPreparationError in
|
||||
switch error {
|
||||
case .insufficientTxOuts:
|
||||
return .insufficientBalance()
|
||||
case .defragmentationRequired:
|
||||
return .defragmentationRequired()
|
||||
}
|
||||
})
|
||||
{
|
||||
case .success(let txOutsToSpend):
|
||||
logger.info(
|
||||
"Transaction prepared with fee. txOutsToSpend: " +
|
||||
"0x\(redacting: txOutsToSpend.map { $0.publicKey.hexEncodedString() })",
|
||||
logFunction: false)
|
||||
let tombstoneBlockIndex = ledgerBlockCount + 50
|
||||
transactionPreparer.prepareTransaction(
|
||||
inputs: txOutsToSpend,
|
||||
recipient: recipient,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
tombstoneBlockIndex: tombstoneBlockIndex,
|
||||
completion: completion)
|
||||
case .failure(let error):
|
||||
logger.info("prepareTransactionWithFee failure: \(error)", logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareTransaction(
|
||||
to recipient: PublicAddress,
|
||||
amount: UInt64,
|
||||
feeLevel: FeeLevel,
|
||||
completion: @escaping (
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
guard amount > 0 else {
|
||||
let errorMessage = "prepareTransactionWithFeeLevel failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(.invalidInput(errorMessage)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
feeFetcher.feeStrategy(for: feeLevel) {
|
||||
switch $0 {
|
||||
case .success(let feeStrategy):
|
||||
let (unspentTxOuts, ledgerBlockCount) =
|
||||
self.account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) }
|
||||
logger.info(
|
||||
"Preparing transaction with fee level... recipient: " +
|
||||
"\(redacting: recipient), amount: \(redacting: amount), feeLevel: " +
|
||||
"\(feeLevel), unspentTxOutValues: " +
|
||||
"\(redacting: unspentTxOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
switch self.txOutSelector
|
||||
.selectTransactionInputs(
|
||||
amount: amount,
|
||||
feeStrategy: feeStrategy,
|
||||
fromTxOuts: unspentTxOuts)
|
||||
.mapError({ error -> TransactionPreparationError in
|
||||
switch error {
|
||||
case .insufficientTxOuts:
|
||||
return .insufficientBalance()
|
||||
case .defragmentationRequired:
|
||||
return .defragmentationRequired()
|
||||
}
|
||||
})
|
||||
{
|
||||
case .success(let (inputs: inputs, fee: fee)):
|
||||
logger.info(
|
||||
"Transaction prepared with fee level. fee: \(redacting: fee)",
|
||||
logFunction: false)
|
||||
let tombstoneBlockIndex = ledgerBlockCount + 50
|
||||
self.transactionPreparer.prepareTransaction(
|
||||
inputs: inputs,
|
||||
recipient: recipient,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
tombstoneBlockIndex: tombstoneBlockIndex,
|
||||
completion: completion)
|
||||
case .failure(let error):
|
||||
logger.info(
|
||||
"prepareTransactionWithFeeLevel failure: \(error)",
|
||||
logFunction: false)
|
||||
completion(.failure(error))
|
||||
}
|
||||
case .failure(let connectionError):
|
||||
logger.info("failure - error: \(connectionError)")
|
||||
completion(.failure(.connectionError(connectionError)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareDefragmentationStepTransactions(
|
||||
toSendAmount amountToSend: UInt64,
|
||||
feeLevel: FeeLevel,
|
||||
completion: @escaping (Result<[Transaction], DefragTransactionPreparationError>) -> Void
|
||||
) {
|
||||
guard amountToSend > 0 else {
|
||||
let errorMessage =
|
||||
"prepareDefragmentationStepTransactions failure: Cannot spend 0 MOB"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
serialQueue.async {
|
||||
completion(.failure(.invalidInput(errorMessage)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
feeFetcher.feeStrategy(for: feeLevel) {
|
||||
switch $0 {
|
||||
case .success(let feeStrategy):
|
||||
let (unspentTxOuts, ledgerBlockCount) =
|
||||
self.account.readSync { ($0.unspentTxOuts, $0.knowableBlockCount) }
|
||||
logger.info(
|
||||
"Preparing defragmentation step transactions... amountToSend: " +
|
||||
"\(redacting: amountToSend), feeLevel: \(feeLevel), " +
|
||||
"unspentTxOutValues: \(redacting: unspentTxOuts.map { $0.value })",
|
||||
logFunction: false)
|
||||
switch self.txOutSelector.selectInputsForDefragTransactions(
|
||||
toSendAmount: amountToSend,
|
||||
feeStrategy: feeStrategy,
|
||||
fromTxOuts: unspentTxOuts)
|
||||
{
|
||||
case .success(let defragTxInputs):
|
||||
if !defragTxInputs.isEmpty {
|
||||
logger.info(
|
||||
"Preparing \(defragTxInputs.count) defrag transactions",
|
||||
logFunction: false)
|
||||
}
|
||||
let tombstoneBlockIndex = ledgerBlockCount + 50
|
||||
defragTxInputs.mapAsync({ defragInputs, callback in
|
||||
self.transactionPreparer.prepareSelfAddressedTransaction(
|
||||
inputs: defragInputs.inputs,
|
||||
fee: defragInputs.fee,
|
||||
tombstoneBlockIndex: tombstoneBlockIndex,
|
||||
completion: callback)
|
||||
}, serialQueue: self.serialQueue, completion: completion)
|
||||
case .failure(let error):
|
||||
logger.info(
|
||||
"prepareDefragmentationStepTransactions failure: \(error)",
|
||||
logFunction: false)
|
||||
self.serialQueue.async {
|
||||
completion(.failure(.insufficientBalance()))
|
||||
}
|
||||
}
|
||||
case .failure(let connectionError):
|
||||
logger.info("failure - error: \(connectionError)")
|
||||
completion(.failure(.connectionError(connectionError)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class Account {
|
||||
let accountKey: AccountKey
|
||||
|
||||
let fogView = FogView()
|
||||
|
||||
var allTxOutTrackers: [TxOutTracker] = []
|
||||
|
||||
init(accountKey: AccountKeyWithFog) {
|
||||
self.accountKey = accountKey.accountKey
|
||||
}
|
||||
|
||||
var publicAddress: PublicAddress {
|
||||
accountKey.publicAddress
|
||||
}
|
||||
|
||||
var unscannedMissedBlocksRanges: [Range<UInt64>] { fogView.unscannedMissedBlocksRanges }
|
||||
|
||||
private var allTxOutsFoundBlockCount: UInt64 {
|
||||
var allTxOutsFoundBlockCount = fogView.allRngTxOutsFoundBlockCount
|
||||
for unscannedMissedBlocksRange in unscannedMissedBlocksRanges
|
||||
where unscannedMissedBlocksRange.lowerBound < allTxOutsFoundBlockCount
|
||||
{
|
||||
allTxOutsFoundBlockCount = unscannedMissedBlocksRange.lowerBound
|
||||
}
|
||||
return allTxOutsFoundBlockCount
|
||||
}
|
||||
|
||||
/// The number of blocks for which we have complete knowledge of this Account's wallet.
|
||||
var knowableBlockCount: UInt64 {
|
||||
var knowableBlockCount = allTxOutsFoundBlockCount
|
||||
for txOut in allTxOutTrackers {
|
||||
if case .unspent(let knownToBeUnspentBlockCount) = txOut.spentStatus {
|
||||
knowableBlockCount = min(knowableBlockCount, knownToBeUnspentBlockCount)
|
||||
}
|
||||
}
|
||||
return knowableBlockCount
|
||||
}
|
||||
|
||||
var cachedBalance: Balance {
|
||||
let blockCount = knowableBlockCount
|
||||
let txOutValues = allTxOutTrackers
|
||||
.filter { $0.receivedAndUnspent(asOfBlockCount: blockCount) }
|
||||
.map { $0.knownTxOut.value }
|
||||
return Balance(values: txOutValues, blockCount: blockCount)
|
||||
}
|
||||
|
||||
var cachedAccountActivity: AccountActivity {
|
||||
let blockCount = knowableBlockCount
|
||||
let txOuts = allTxOutTrackers.compactMap { OwnedTxOut($0, atBlockCount: blockCount) }
|
||||
return AccountActivity(txOuts: txOuts, blockCount: blockCount)
|
||||
}
|
||||
|
||||
var ownedTxOuts: [KnownTxOut] {
|
||||
ownedTxOutsAndBlockCount.txOuts
|
||||
}
|
||||
|
||||
var ownedTxOutsAndBlockCount: (txOuts: [KnownTxOut], blockCount: UInt64) {
|
||||
let knowableBlockCount = self.knowableBlockCount
|
||||
let txOuts = allTxOutTrackers
|
||||
.filter { $0.received(asOfBlockCount: knowableBlockCount) }
|
||||
.map { $0.knownTxOut }
|
||||
return (txOuts: txOuts, blockCount: knowableBlockCount)
|
||||
}
|
||||
|
||||
var unspentTxOuts: [KnownTxOut] {
|
||||
unspentTxOutsAndBlockCount.txOuts
|
||||
}
|
||||
|
||||
var unspentTxOutsAndBlockCount: (txOuts: [KnownTxOut], blockCount: UInt64) {
|
||||
let knowableBlockCount = self.knowableBlockCount
|
||||
let txOuts = allTxOutTrackers
|
||||
.filter { $0.receivedAndUnspent(asOfBlockCount: knowableBlockCount) }
|
||||
.map { $0.knownTxOut }
|
||||
return (txOuts: txOuts, blockCount: knowableBlockCount)
|
||||
}
|
||||
|
||||
func addTxOuts(_ txOuts: [KnownTxOut]) {
|
||||
allTxOutTrackers.append(contentsOf: txOuts.map { TxOutTracker($0) })
|
||||
}
|
||||
|
||||
func addViewKeyScanResults(scannedBlockRanges: [Range<UInt64>], foundTxOuts: [KnownTxOut]) {
|
||||
addTxOuts(foundTxOuts)
|
||||
fogView.markBlocksAsScanned(blockRanges: scannedBlockRanges)
|
||||
}
|
||||
|
||||
func cachedReceivedStatus(of receipt: Receipt)
|
||||
-> Result<Receipt.ReceivedStatus, InvalidInputError>
|
||||
{
|
||||
ownedTxOut(for: receipt).map {
|
||||
if let ownedTxOut = $0 {
|
||||
return .received(block: ownedTxOut.block)
|
||||
} else {
|
||||
let knownToBeNotReceivedBlockCount = allTxOutsFoundBlockCount
|
||||
guard receipt.txTombstoneBlockIndex > knownToBeNotReceivedBlockCount else {
|
||||
return .tombstoneExceeded
|
||||
}
|
||||
return .notReceived(knownToBeNotReceivedBlockCount: knownToBeNotReceivedBlockCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the `KnownTxOut`'s corresponding to `receipt` and verifies `receipt` is valid.
|
||||
private func ownedTxOut(for receipt: Receipt) -> Result<KnownTxOut?, InvalidInputError> {
|
||||
logger.debug(
|
||||
"Last received TxOut: TxOut pubkey: " +
|
||||
"\(redacting: ownedTxOuts.last?.publicKey.hexEncodedString() ?? "None")",
|
||||
logFunction: false)
|
||||
|
||||
// First check if we've received the TxOut (either from Fog View or from view key scanning).
|
||||
// This has the benefit of providing a guarantee that the TxOut is owned by this account.
|
||||
guard let ownedTxOut = ownedTxOut(for: receipt.txOutPublicKeyTyped) else {
|
||||
return .success(nil)
|
||||
}
|
||||
|
||||
// Make sure the Receipt data matches the TxOut found in the ledger. This verifies that the
|
||||
// public key, commitment, and masked value match.
|
||||
//
|
||||
// Note: This doesn't verify the confirmation number or tombstone block (since neither are
|
||||
// saved to the ledger).
|
||||
guard receipt.matchesTxOut(ownedTxOut) else {
|
||||
let errorMessage =
|
||||
"Receipt data doesn't match the corresponding TxOut found in the ledger. " +
|
||||
"Receipt: \(redacting: receipt.serializedData.base64EncodedString()) - " +
|
||||
"Account TxOut: \(redacting: ownedTxOut)"
|
||||
logger.error(errorMessage, sensitive: true, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
|
||||
// Verify that the confirmation number validates for this account key. This provides a
|
||||
// guarantee that the sender of the Receipt was the creator of the TxOut that we received.
|
||||
guard receipt.validateConfirmationNumber(accountKey: accountKey) else {
|
||||
let errorMessage = "Receipt confirmation number is invalid for this account. " +
|
||||
"Receipt: \(redacting: receipt.serializedData.base64EncodedString())"
|
||||
logger.error(errorMessage, sensitive: true, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
|
||||
return .success(ownedTxOut)
|
||||
}
|
||||
|
||||
private func ownedTxOut(for txOutPublicKey: RistrettoPublic) -> KnownTxOut? {
|
||||
ownedTxOuts.first(where: { $0.publicKey == txOutPublicKey })
|
||||
}
|
||||
}
|
||||
|
||||
extension Account {
|
||||
/// - Returns: `.failure` if `accountKey` doesn't use Fog.
|
||||
static func make(accountKey: AccountKey) -> Result<Account, InvalidInputError> {
|
||||
guard let accountKey = AccountKeyWithFog(accountKey: accountKey) else {
|
||||
let errorMessage = "Accounts without fog URLs are not currently supported."
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
return .success(Account(accountKey: accountKey))
|
||||
}
|
||||
}
|
||||
|
||||
extension Account: CustomRedactingStringConvertible {
|
||||
var redactingDescription: String {
|
||||
publicAddress.redactingDescription
|
||||
}
|
||||
}
|
||||
|
||||
final class TxOutTracker {
|
||||
let knownTxOut: KnownTxOut
|
||||
|
||||
var keyImageTracker: KeyImageSpentTracker
|
||||
|
||||
init(_ knownTxOut: KnownTxOut) {
|
||||
self.knownTxOut = knownTxOut
|
||||
self.keyImageTracker = KeyImageSpentTracker(knownTxOut.keyImage)
|
||||
}
|
||||
|
||||
var spentStatus: KeyImage.SpentStatus {
|
||||
keyImageTracker.spentStatus
|
||||
}
|
||||
|
||||
var isSpent: Bool {
|
||||
keyImageTracker.isSpent
|
||||
}
|
||||
|
||||
func receivedAndUnspent(asOfBlockCount blockCount: UInt64) -> Bool {
|
||||
received(asOfBlockCount: blockCount) && !spent(asOfBlockCount: blockCount)
|
||||
}
|
||||
|
||||
func received(asOfBlockCount blockCount: UInt64) -> Bool {
|
||||
knownTxOut.block.index < blockCount
|
||||
}
|
||||
|
||||
func spent(asOfBlockCount blockCount: UInt64) -> Bool {
|
||||
if case .spent = keyImageTracker.spentStatus.status(atBlockCount: blockCount) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension OwnedTxOut {
|
||||
fileprivate init?(_ txOutTracker: TxOutTracker, atBlockCount blockCount: UInt64) {
|
||||
guard txOutTracker.knownTxOut.block.index < blockCount else {
|
||||
return nil
|
||||
}
|
||||
let receivedBlock = txOutTracker.knownTxOut.block
|
||||
|
||||
let spentBlock: BlockMetadata?
|
||||
if case .spent(let block) = txOutTracker.spentStatus, block.index < blockCount {
|
||||
spentBlock = block
|
||||
} else {
|
||||
spentBlock = nil
|
||||
}
|
||||
|
||||
self.init(txOutTracker.knownTxOut, receivedBlock: receivedBlock, spentBlock: spentBlock)
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Provides a snapshot of account activity at a particular point in the ledger, as indicated by
|
||||
/// `blockCount`.
|
||||
public struct AccountActivity {
|
||||
public let txOuts: Set<OwnedTxOut>
|
||||
|
||||
public let blockCount: UInt64
|
||||
|
||||
init(txOuts: [OwnedTxOut], blockCount: UInt64) {
|
||||
self.txOuts = Set(txOuts)
|
||||
self.blockCount = blockCount
|
||||
}
|
||||
}
|
||||
@ -1,189 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
public struct AccountKey {
|
||||
static func make(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
fogReportUrl: String,
|
||||
fogReportId: String,
|
||||
fogAuthoritySpki: Data,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) -> Result<AccountKey, InvalidInputError> {
|
||||
FogInfo.make(
|
||||
reportUrl: fogReportUrl,
|
||||
reportId: fogReportId,
|
||||
authoritySpki: fogAuthoritySpki
|
||||
).map { fogInfo in
|
||||
AccountKey(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
fogInfo: fogInfo,
|
||||
subaddressIndex: subaddressIndex)
|
||||
}
|
||||
}
|
||||
|
||||
let viewPrivateKey: RistrettoPrivate
|
||||
let spendPrivateKey: RistrettoPrivate
|
||||
let fogInfo: FogInfo?
|
||||
let subaddressIndex: UInt64
|
||||
|
||||
public let publicAddress: PublicAddress
|
||||
|
||||
init(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
fogInfo: FogInfo? = nil,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) {
|
||||
self.viewPrivateKey = viewPrivateKey
|
||||
self.spendPrivateKey = spendPrivateKey
|
||||
self.fogInfo = fogInfo
|
||||
self.subaddressIndex = subaddressIndex
|
||||
self.publicAddress = PublicAddress(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
accountKeyFogInfo: fogInfo,
|
||||
subaddressIndex: subaddressIndex)
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when the input is not deserializable.
|
||||
public init?(serializedData: Data) {
|
||||
guard let proto = try? External_AccountKey(serializedData: serializedData) else {
|
||||
logger.error("External_AccountKey deserialization failed.", logFunction: false)
|
||||
return nil
|
||||
}
|
||||
self.init(proto)
|
||||
}
|
||||
|
||||
public var serializedData: Data {
|
||||
let proto = External_AccountKey(self)
|
||||
return proto.serializedDataInfallible
|
||||
}
|
||||
|
||||
var fogReportUrlString: String? { fogInfo?.reportUrlString }
|
||||
var fogReportUrl: FogUrl? { fogInfo?.reportUrl }
|
||||
var fogReportId: String? { fogInfo?.reportId }
|
||||
var fogAuthoritySpki: Data? { fogInfo?.authoritySpki }
|
||||
|
||||
var subaddressViewPrivateKey: RistrettoPrivate {
|
||||
AccountKeyUtils.subaddressPrivateKeys(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
subaddressIndex: subaddressIndex
|
||||
).subaddressViewPrivateKey
|
||||
}
|
||||
|
||||
var subaddressSpendPrivateKey: RistrettoPrivate {
|
||||
AccountKeyUtils.subaddressPrivateKeys(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
subaddressIndex: subaddressIndex
|
||||
).subaddressSpendPrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountKey: Equatable {}
|
||||
extension AccountKey: Hashable {}
|
||||
|
||||
extension AccountKey {
|
||||
init?(
|
||||
_ proto: External_AccountKey,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) {
|
||||
guard let viewPrivateKey = RistrettoPrivate(proto.viewPrivateKey.data),
|
||||
let spendPrivateKey = RistrettoPrivate(proto.spendPrivateKey.data)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let maybeFogInfo: FogInfo?
|
||||
if !proto.fogReportURL.isEmpty {
|
||||
guard case .success(let fogInfo) = FogInfo.make(
|
||||
reportUrl: proto.fogReportURL,
|
||||
reportId: proto.fogReportID,
|
||||
authoritySpki: proto.fogAuthoritySpki)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
maybeFogInfo = fogInfo
|
||||
} else {
|
||||
maybeFogInfo = nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
fogInfo: maybeFogInfo,
|
||||
subaddressIndex: subaddressIndex)
|
||||
}
|
||||
}
|
||||
|
||||
extension External_AccountKey {
|
||||
init(_ accountKey: AccountKey) {
|
||||
self.init()
|
||||
self.viewPrivateKey = External_RistrettoPrivate(accountKey.viewPrivateKey)
|
||||
self.spendPrivateKey = External_RistrettoPrivate(accountKey.spendPrivateKey)
|
||||
if let fogInfo = accountKey.fogInfo {
|
||||
self.fogReportURL = fogInfo.reportUrlString
|
||||
self.fogReportID = fogInfo.reportId
|
||||
self.fogAuthoritySpki = fogInfo.authoritySpki
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountKey {
|
||||
struct FogInfo {
|
||||
fileprivate static func make(reportUrl: String, reportId: String, authoritySpki: Data)
|
||||
-> Result<FogInfo, InvalidInputError>
|
||||
{
|
||||
FogUrl.make(string: reportUrl).map { reportUrlTyped in
|
||||
FogInfo(
|
||||
reportUrlString: reportUrl,
|
||||
reportUrl: reportUrlTyped,
|
||||
reportId: reportId,
|
||||
authoritySpki: authoritySpki)
|
||||
}
|
||||
}
|
||||
|
||||
let reportUrlString: String
|
||||
let reportUrl: FogUrl
|
||||
let reportId: String
|
||||
let authoritySpki: Data
|
||||
|
||||
private init(
|
||||
reportUrlString: String,
|
||||
reportUrl: FogUrl,
|
||||
reportId: String,
|
||||
authoritySpki: Data
|
||||
) {
|
||||
self.reportUrlString = reportUrlString
|
||||
self.reportUrl = reportUrl
|
||||
self.reportId = reportId
|
||||
self.authoritySpki = authoritySpki
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountKey.FogInfo: Equatable {}
|
||||
extension AccountKey.FogInfo: Hashable {}
|
||||
|
||||
struct AccountKeyWithFog {
|
||||
let accountKey: AccountKey
|
||||
let fogInfo: AccountKey.FogInfo
|
||||
|
||||
init?(accountKey: AccountKey) {
|
||||
guard let fogInfo = accountKey.fogInfo else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.accountKey = accountKey
|
||||
self.fogInfo = fogInfo
|
||||
}
|
||||
}
|
||||
@ -1,232 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable function_parameter_count
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum AccountKeyUtils {
|
||||
static func subaddressPrivateKeys(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
subaddressIndex: UInt64
|
||||
) -> (subaddressViewPrivateKey: RistrettoPrivate, subaddressSpendPrivateKey: RistrettoPrivate) {
|
||||
var subaddressViewPrivateKeyOut = Data32()
|
||||
var subaddressSpendPrivateKeyOut = Data32()
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
spendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
subaddressViewPrivateKeyOut.asMcMutableBuffer { viewPrivateKeyOutPtr in
|
||||
subaddressSpendPrivateKeyOut.asMcMutableBuffer { spendPrivateKeyOutPtr in
|
||||
withMcInfallible {
|
||||
mc_account_key_get_subaddress_private_keys(
|
||||
viewKeyBufferPtr,
|
||||
spendKeyBufferPtr,
|
||||
subaddressIndex,
|
||||
viewPrivateKeyOutPtr,
|
||||
spendPrivateKeyOutPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Safety: It's safe to skip validation because mc_account_key_get_subaddress_private_keys
|
||||
// should always return valid RistrettoPrivate values on success.
|
||||
return (RistrettoPrivate(skippingValidation: subaddressViewPrivateKeyOut),
|
||||
RistrettoPrivate(skippingValidation: subaddressSpendPrivateKeyOut))
|
||||
}
|
||||
|
||||
static func publicAddressPublicKeys(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
subaddressIndex: UInt64
|
||||
) -> (viewPublicKey: RistrettoPublic, spendPublicKey: RistrettoPublic) {
|
||||
var viewPublicKeyOut = Data32()
|
||||
var spendPublicKeyOut = Data32()
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
spendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
viewPublicKeyOut.asMcMutableBuffer { viewPublicKeyOutPtr in
|
||||
spendPublicKeyOut.asMcMutableBuffer { spendPublicKeyOutPtr in
|
||||
withMcInfallible {
|
||||
mc_account_key_get_public_address_public_keys(
|
||||
viewKeyBufferPtr,
|
||||
spendKeyBufferPtr,
|
||||
subaddressIndex,
|
||||
viewPublicKeyOutPtr,
|
||||
spendPublicKeyOutPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Safety: It's safe to skip validation because
|
||||
// mc_account_key_get_public_address_public_keys should always return valid RistrettoPublic
|
||||
// values on success.
|
||||
return (RistrettoPublic(skippingValidation: viewPublicKeyOut),
|
||||
RistrettoPublic(skippingValidation: spendPublicKeyOut))
|
||||
}
|
||||
|
||||
static func fogAuthoritySig(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
reportUrl: String,
|
||||
reportId: String,
|
||||
authoritySpki: Data,
|
||||
subaddressIndex: UInt64
|
||||
) -> Data {
|
||||
McAccountKey.withUnsafePointer(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
reportUrl: reportUrl,
|
||||
reportId: reportId,
|
||||
authoritySpki: authoritySpki
|
||||
) { accountKeyPtr in
|
||||
Data(withFixedLengthMcMutableBufferInfallible: McConstants.SCHNORRKEL_SIGNATURE_LEN)
|
||||
{ bufferPtr in
|
||||
mc_account_key_get_public_address_fog_authority_sig(
|
||||
accountKeyPtr,
|
||||
subaddressIndex,
|
||||
bufferPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension McAccountKey {
|
||||
fileprivate static func withUnsafePointer<T>(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
reportUrl: String,
|
||||
reportId: String,
|
||||
authoritySpki: Data,
|
||||
body: (UnsafePointer<McAccountKey>) throws -> T
|
||||
) rethrows -> T {
|
||||
try McAccountKeyFogInfo.withUnsafePointer(
|
||||
reportUrl: reportUrl,
|
||||
reportId: reportId,
|
||||
authoritySpki: authoritySpki
|
||||
) { fogInfoPtr in
|
||||
try viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
try spendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
var publicAddress = McAccountKey(
|
||||
view_private_key: viewKeyBufferPtr,
|
||||
spend_private_key: spendKeyBufferPtr,
|
||||
fog_info: fogInfoPtr)
|
||||
return try body(&publicAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static func withUnsafePointer<T>(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
body: (UnsafePointer<McAccountKey>) throws -> T
|
||||
) rethrows -> T {
|
||||
try viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
try spendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
var publicAddress = McAccountKey(
|
||||
view_private_key: viewKeyBufferPtr,
|
||||
spend_private_key: spendKeyBufferPtr,
|
||||
fog_info: nil)
|
||||
return try body(&publicAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountKey: CStructWrapper {
|
||||
typealias CStruct = McAccountKey
|
||||
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<McAccountKey>) throws -> R
|
||||
) rethrows -> R {
|
||||
try fogInfo.withUnsafeCStructPointer { fogInfoPtr in
|
||||
try viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
try spendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
var publicAddress = McAccountKey(
|
||||
view_private_key: viewKeyBufferPtr,
|
||||
spend_private_key: spendKeyBufferPtr,
|
||||
fog_info: fogInfoPtr)
|
||||
return try body(&publicAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension McAccountKeyFogInfo {
|
||||
fileprivate static func withUnsafePointer<T>(
|
||||
reportUrl: String,
|
||||
reportId: String,
|
||||
authoritySpki: Data,
|
||||
body: (UnsafePointer<McAccountKeyFogInfo>) throws -> T
|
||||
) rethrows -> T {
|
||||
try reportUrl.withCString { reportUrlPtr in
|
||||
try reportId.withCString { reportIdPtr in
|
||||
try authoritySpki.asMcBuffer { authoritySpkiPtr in
|
||||
var mcFogInfo = McAccountKeyFogInfo(
|
||||
report_url: reportUrlPtr,
|
||||
report_id: reportIdPtr,
|
||||
authority_fingerprint: authoritySpkiPtr)
|
||||
return try body(&mcFogInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountKey.FogInfo: CStructWrapper {
|
||||
typealias CStruct = McAccountKeyFogInfo
|
||||
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<McAccountKeyFogInfo>) throws -> R
|
||||
) rethrows -> R {
|
||||
try McAccountKeyFogInfo.withUnsafePointer(
|
||||
reportUrl: reportUrlString,
|
||||
reportId: reportId,
|
||||
authoritySpki: authoritySpki,
|
||||
body: body)
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress: CStructWrapper {
|
||||
typealias CStruct = McPublicAddress
|
||||
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<McPublicAddress>) throws -> R
|
||||
) rethrows -> R {
|
||||
try viewPublicKey.asMcBuffer { viewKeyBufferPtr in
|
||||
try spendPublicKey.asMcBuffer { spendKeyBufferPtr in
|
||||
try fogInfo.withUnsafeCStructPointer { fogInfoPtr in
|
||||
var publicAddress = McPublicAddress(
|
||||
view_public_key: viewKeyBufferPtr,
|
||||
spend_public_key: spendKeyBufferPtr,
|
||||
fog_info: fogInfoPtr)
|
||||
return try body(&publicAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress.FogInfo: CStructWrapper {
|
||||
typealias CStruct = McPublicAddressFogInfo
|
||||
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<McPublicAddressFogInfo>) throws -> R
|
||||
) rethrows -> R {
|
||||
try reportUrlString.withCString { reportUrlPtr in
|
||||
try reportId.withCString { reportIdPtr in
|
||||
try authoritySig.asMcBuffer { authoritySigPtr in
|
||||
var mcFogInfo = McPublicAddressFogInfo(
|
||||
report_url: reportUrlPtr,
|
||||
report_id: reportIdPtr,
|
||||
authority_sig: authoritySigPtr)
|
||||
return try body(&mcFogInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Balance {
|
||||
public let amountPicoMobLow: UInt64
|
||||
public let amountPicoMobHigh: UInt8
|
||||
let blockCount: UInt64
|
||||
|
||||
init(values: [UInt64], blockCount: UInt64) {
|
||||
var amountLow: UInt64 = 0
|
||||
var amountHigh: UInt8 = 0
|
||||
for value in values {
|
||||
let (partialValue, overflow) = amountLow.addingReportingOverflow(value)
|
||||
amountLow = partialValue
|
||||
if overflow {
|
||||
amountHigh += 1
|
||||
}
|
||||
}
|
||||
self.init(amountLow: amountLow, amountHigh: amountHigh, blockCount: blockCount)
|
||||
}
|
||||
|
||||
init(amountLow: UInt64, amountHigh: UInt8, blockCount: UInt64) {
|
||||
self.amountPicoMobLow = amountLow
|
||||
self.amountPicoMobHigh = amountHigh
|
||||
self.blockCount = blockCount
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when the amount is too large to fit in a `UInt64`.
|
||||
public func amountPicoMob() -> UInt64? {
|
||||
guard amountPicoMobHigh == 0 else {
|
||||
return nil
|
||||
}
|
||||
return amountPicoMobLow
|
||||
}
|
||||
|
||||
/// Convenience accessor for balance value. `mobInt` is the integer part of the value when
|
||||
/// represented in MOB. `picoFrac` is the fractional part of the value when represented in MOB.
|
||||
/// However, rather than reprenting the fractional part as a decimal fraction, it is represented
|
||||
/// in picoMOB, thus allowing both parts to be integer values.
|
||||
///
|
||||
/// The purpose of this representation is to facilitate presenting the balance to the user in
|
||||
/// MOB form.
|
||||
///
|
||||
/// To illustrate, given an amount in the form of XXXXXXXXX.YYYYYYYYYYYY MOB,
|
||||
/// - `mobInt`: XXXXXXXXX (denominated in MOB)
|
||||
/// - `picoFrac`: YYYYYYYYYYYY (denominated in picoMOB)
|
||||
///
|
||||
/// It is necessary to break apart the values into 2 parts because the total max possible
|
||||
/// balance is too large to fit in a single `UInt64`, when denominated in picoMOB, assuming 250
|
||||
/// million MOB in circulation and assuming a base unit of 1 picoMOB as the smallest indivisible
|
||||
/// unit of MOB.
|
||||
public var amountMobParts: (mobInt: UInt32, picoFrac: UInt64) {
|
||||
// amount (picoMOB) = amountLow + amountHigh * 2^64
|
||||
//
|
||||
// amountLowMobDec = amountLow / 10^12
|
||||
// amountHighMobDec = amountHigh * 2^64 / 10^12
|
||||
//
|
||||
// amountMobDec = amountLowMobDec + amountHighMobDec
|
||||
//
|
||||
// amountLowMobInt = floor(amountLow / 10^12)
|
||||
// amountLowPicoFrac = amountLow % 10^12
|
||||
|
||||
// amountHighMobInt = floor((amountHigh * 2^64) / 10^12)
|
||||
// = floor((amountHigh << 52) / 5^12)
|
||||
// amountHighPicoFrac = (amountHigh * 2^64) % 10^12
|
||||
// = ((amountHigh << 52) % 5^12) << 12
|
||||
//
|
||||
// amountPicoFracCarry = floor((amountLowPicoFrac + amountHighPicoFrac) / 10^12)
|
||||
//
|
||||
// amountMobInt = amountLowMobInt + amountHighMobInt + amountPicoFracCarry
|
||||
// amountPicoFrac = (amountLowPicoFrac + amountHighPicoFrac) % 10^12
|
||||
|
||||
let (amountLowMobInt, amountLowPicoFrac) = { () -> (UInt32, UInt64) in
|
||||
// 10^12 = 1_000_000_000_000
|
||||
let mobParts = amountPicoMobLow.quotientAndRemainder(dividingBy: 1_000_000_000_000)
|
||||
return (UInt32(mobParts.quotient), mobParts.remainder)
|
||||
}()
|
||||
|
||||
let (amountHighMobInt, amountHighPicoFrac) = { () -> (UInt32, UInt64) in
|
||||
// Intermediary = base of 5^-12 MOB
|
||||
let amountHighIntermediary = UInt64(amountPicoMobHigh) << 52
|
||||
// 5^12 = 244_140_625
|
||||
let mobParts = amountHighIntermediary.quotientAndRemainder(dividingBy: 244_140_625)
|
||||
return (UInt32(mobParts.quotient), mobParts.remainder << 12)
|
||||
}()
|
||||
|
||||
let amountPicoFracParts = (amountLowPicoFrac + amountHighPicoFrac).quotientAndRemainder(
|
||||
dividingBy: 1_000_000_000_000)
|
||||
|
||||
let amountMobInt = amountLowMobInt + amountHighMobInt + UInt32(amountPicoFracParts.quotient)
|
||||
let amountPicoFrac = amountPicoFracParts.remainder
|
||||
|
||||
return (amountMobInt, amountPicoFrac)
|
||||
}
|
||||
}
|
||||
|
||||
extension Balance: Equatable {}
|
||||
extension Balance: Hashable {}
|
||||
|
||||
extension Balance: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let amountMob = amountMobParts
|
||||
return String(format: "%u.%012llu MOB", amountMob.mobInt, amountMob.picoFrac)
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct OwnedTxOut {
|
||||
let publicKeyTyped: RistrettoPublic
|
||||
/// - Returns: `TxOut` public key
|
||||
public var publicKey: Data { publicKeyTyped.data }
|
||||
|
||||
public let value: UInt64
|
||||
|
||||
let keyImageTyped: KeyImage
|
||||
/// - Returns: `TxOut` key image
|
||||
public var keyImage: Data { keyImageTyped.data }
|
||||
|
||||
public let receivedBlock: BlockMetadata
|
||||
|
||||
public let spentBlock: BlockMetadata?
|
||||
|
||||
init(
|
||||
_ knownTxOut: KnownTxOut,
|
||||
receivedBlock: BlockMetadata,
|
||||
spentBlock: BlockMetadata?
|
||||
) {
|
||||
self.publicKeyTyped = knownTxOut.publicKey
|
||||
self.value = knownTxOut.value
|
||||
self.keyImageTyped = knownTxOut.keyImage
|
||||
self.receivedBlock = receivedBlock
|
||||
self.spentBlock = spentBlock
|
||||
}
|
||||
}
|
||||
|
||||
extension OwnedTxOut: Equatable {}
|
||||
extension OwnedTxOut: Hashable {}
|
||||
@ -1,197 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
public struct PublicAddress {
|
||||
static func make(
|
||||
viewPublicKey: RistrettoPublic,
|
||||
spendPublicKey: RistrettoPublic,
|
||||
fogReportUrl: String,
|
||||
fogReportId: String,
|
||||
fogAuthoritySig: Data
|
||||
) -> Result<PublicAddress, InvalidInputError> {
|
||||
FogInfo.make(
|
||||
reportUrl: fogReportUrl,
|
||||
reportId: fogReportId,
|
||||
authoritySig: fogAuthoritySig)
|
||||
.map { fogInfo in
|
||||
PublicAddress(
|
||||
viewPublicKey: viewPublicKey,
|
||||
spendPublicKey: spendPublicKey,
|
||||
fogInfo: fogInfo)
|
||||
}
|
||||
}
|
||||
|
||||
let viewPublicKeyTyped: RistrettoPublic
|
||||
let spendPublicKeyTyped: RistrettoPublic
|
||||
let fogInfo: FogInfo?
|
||||
|
||||
init(viewPublicKey: RistrettoPublic, spendPublicKey: RistrettoPublic, fogInfo: FogInfo? = nil) {
|
||||
self.viewPublicKeyTyped = viewPublicKey
|
||||
self.spendPublicKeyTyped = spendPublicKey
|
||||
self.fogInfo = fogInfo
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when the input is not deserializable.
|
||||
public init?(serializedData: Data) {
|
||||
guard let proto = try? External_PublicAddress(serializedData: serializedData) else {
|
||||
logger.warning("External_PublicAddress deserialization failed. serializedData: " +
|
||||
"\(redacting: serializedData.base64EncodedString())")
|
||||
return nil
|
||||
}
|
||||
self.init(proto)
|
||||
}
|
||||
|
||||
public var serializedData: Data {
|
||||
let proto = External_PublicAddress(self)
|
||||
return proto.serializedDataInfallible
|
||||
}
|
||||
|
||||
/// Subaddress view public key, `C`, in bytes.
|
||||
public var viewPublicKey: Data { viewPublicKeyTyped.data }
|
||||
|
||||
/// Subaddress spend public key, `D`, in bytes.
|
||||
public var spendPublicKey: Data { spendPublicKeyTyped.data }
|
||||
|
||||
public var fogReportUrlString: String? { fogInfo?.reportUrlString }
|
||||
|
||||
var fogReportUrl: FogUrl? { fogInfo?.reportUrl }
|
||||
var fogReportId: String? { fogInfo?.reportId }
|
||||
var fogAuthoritySig: Data? { fogInfo?.authoritySig }
|
||||
}
|
||||
|
||||
extension PublicAddress: Equatable {}
|
||||
extension PublicAddress: Hashable {}
|
||||
|
||||
extension PublicAddress: CustomRedactingStringConvertible {
|
||||
var redactingDescription: String {
|
||||
"PublicAddress(\(Base58Coder.encode(self)))"
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress {
|
||||
init(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
accountKeyFogInfo: AccountKey.FogInfo? = nil,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) {
|
||||
let (viewPublicKey, spendPublicKey) = AccountKeyUtils.publicAddressPublicKeys(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
subaddressIndex: subaddressIndex)
|
||||
|
||||
let fogInfo: FogInfo?
|
||||
if let accountKeyFogInfo = accountKeyFogInfo {
|
||||
fogInfo = FogInfo(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
accountKeyFogInfo: accountKeyFogInfo,
|
||||
subaddressIndex: subaddressIndex)
|
||||
} else {
|
||||
fogInfo = nil
|
||||
}
|
||||
|
||||
self.init(viewPublicKey: viewPublicKey, spendPublicKey: spendPublicKey, fogInfo: fogInfo)
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress {
|
||||
init?(_ publicAddress: External_PublicAddress) {
|
||||
guard let viewPublicKey = RistrettoPublic(publicAddress.viewPublicKey),
|
||||
let spendPublicKey = RistrettoPublic(publicAddress.spendPublicKey)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let fogInfo: FogInfo?
|
||||
if !publicAddress.fogReportURL.isEmpty {
|
||||
guard case .success(let maybeFogInfo) = FogInfo.make(
|
||||
reportUrl: publicAddress.fogReportURL,
|
||||
reportId: publicAddress.fogReportID,
|
||||
authoritySig: publicAddress.fogAuthoritySig)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
fogInfo = maybeFogInfo
|
||||
} else {
|
||||
fogInfo = nil
|
||||
}
|
||||
|
||||
self.init(viewPublicKey: viewPublicKey, spendPublicKey: spendPublicKey, fogInfo: fogInfo)
|
||||
}
|
||||
}
|
||||
|
||||
extension External_PublicAddress {
|
||||
init(_ publicAddress: PublicAddress) {
|
||||
self.init()
|
||||
self.viewPublicKey = External_CompressedRistretto(publicAddress.viewPublicKey)
|
||||
self.spendPublicKey = External_CompressedRistretto(publicAddress.spendPublicKey)
|
||||
if let fogInfo = publicAddress.fogInfo {
|
||||
self.fogReportURL = fogInfo.reportUrlString
|
||||
self.fogReportID = fogInfo.reportId
|
||||
self.fogAuthoritySig = fogInfo.authoritySig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress {
|
||||
struct FogInfo {
|
||||
fileprivate static func make(reportUrl: String, reportId: String, authoritySig: Data)
|
||||
-> Result<FogInfo, InvalidInputError>
|
||||
{
|
||||
FogUrl.make(string: reportUrl).map { reportUrlTyped in
|
||||
FogInfo(
|
||||
reportUrlString: reportUrl,
|
||||
reportUrl: reportUrlTyped,
|
||||
reportId: reportId,
|
||||
authoritySig: authoritySig)
|
||||
}
|
||||
}
|
||||
|
||||
let reportUrlString: String
|
||||
let reportUrl: FogUrl
|
||||
let reportId: String
|
||||
let authoritySig: Data
|
||||
|
||||
private init(
|
||||
reportUrlString: String,
|
||||
reportUrl: FogUrl,
|
||||
reportId: String,
|
||||
authoritySig: Data
|
||||
) {
|
||||
self.reportUrlString = reportUrlString
|
||||
self.reportUrl = reportUrl
|
||||
self.reportId = reportId
|
||||
self.authoritySig = authoritySig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PublicAddress.FogInfo: Equatable {}
|
||||
extension PublicAddress.FogInfo: Hashable {}
|
||||
|
||||
extension PublicAddress.FogInfo {
|
||||
fileprivate init(
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
spendPrivateKey: RistrettoPrivate,
|
||||
accountKeyFogInfo: AccountKey.FogInfo,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) {
|
||||
let authoritySig = AccountKeyUtils.fogAuthoritySig(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
reportUrl: accountKeyFogInfo.reportUrlString,
|
||||
reportId: accountKeyFogInfo.reportId,
|
||||
authoritySpki: accountKeyFogInfo.authoritySpki,
|
||||
subaddressIndex: subaddressIndex)
|
||||
self.init(
|
||||
reportUrlString: accountKeyFogInfo.reportUrlString,
|
||||
reportUrl: accountKeyFogInfo.reportUrl,
|
||||
reportId: accountKeyFogInfo.reportId,
|
||||
authoritySig: authoritySig)
|
||||
}
|
||||
}
|
||||
@ -1,208 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct InvalidInputError: Error {
|
||||
let reason: String
|
||||
|
||||
init(_ reason: String) {
|
||||
self.reason = reason
|
||||
}
|
||||
}
|
||||
|
||||
extension InvalidInputError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Invalid input: \(reason)"
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConnectionError: Error {
|
||||
case connectionFailure(String)
|
||||
case authorizationFailure(String)
|
||||
case invalidServerResponse(String)
|
||||
case attestationVerificationFailed(String)
|
||||
case outdatedClient(String)
|
||||
case serverRateLimited(String)
|
||||
}
|
||||
|
||||
extension ConnectionError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Connection error: " + {
|
||||
switch self {
|
||||
case .connectionFailure(let reason):
|
||||
return "Connection failure: \(reason)"
|
||||
case .authorizationFailure(let reason):
|
||||
return "Authorization failure: \(reason)"
|
||||
case .invalidServerResponse(let reason):
|
||||
return "Invalid server response: \(reason)"
|
||||
case .attestationVerificationFailed(let reason):
|
||||
return "Attestation verification failed: \(reason)"
|
||||
case .outdatedClient(let reason):
|
||||
return "Outdated client: \(reason)"
|
||||
case .serverRateLimited(let reason):
|
||||
return "Server rate limited: \(reason)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
public enum BalanceTransferEstimationFetcherError: Error {
|
||||
case feeExceedsBalance(String = String())
|
||||
case balanceOverflow(String = String())
|
||||
case connectionError(ConnectionError)
|
||||
}
|
||||
|
||||
extension BalanceTransferEstimationFetcherError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Balance transfer estimation error: " + {
|
||||
switch self {
|
||||
case .feeExceedsBalance(let reason):
|
||||
return "Fee exceeds balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .balanceOverflow(let reason):
|
||||
return "Balance overflow\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .connectionError(let innerError):
|
||||
return "\(innerError)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
public enum TransactionEstimationFetcherError: Error {
|
||||
case invalidInput(String)
|
||||
case insufficientBalance(String = String())
|
||||
case connectionError(ConnectionError)
|
||||
}
|
||||
|
||||
extension TransactionEstimationFetcherError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Transaction estimation error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .insufficientBalance(let reason):
|
||||
return "Insufficient balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .connectionError(let innerError):
|
||||
return "\(innerError)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
public enum TransactionPreparationError: Error {
|
||||
case invalidInput(String)
|
||||
case insufficientBalance(String = String())
|
||||
case defragmentationRequired(String = String())
|
||||
case connectionError(ConnectionError)
|
||||
}
|
||||
|
||||
extension TransactionPreparationError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Transaction preparation error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .insufficientBalance(let reason):
|
||||
return "Insufficient balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .defragmentationRequired(let reason):
|
||||
return "Defragmentation required\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .connectionError(let innerError):
|
||||
return "\(innerError)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
public enum DefragTransactionPreparationError: Error {
|
||||
case invalidInput(String)
|
||||
case insufficientBalance(String = String())
|
||||
case connectionError(ConnectionError)
|
||||
}
|
||||
|
||||
extension DefragTransactionPreparationError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Defragmentation transaction preparation error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .insufficientBalance(let reason):
|
||||
return "Insufficient balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .connectionError(let innerError):
|
||||
return "\(innerError)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
public enum TransactionSubmissionError: Error {
|
||||
case connectionError(ConnectionError)
|
||||
case invalidTransaction(String = String())
|
||||
case feeError(String = String())
|
||||
case tombstoneBlockTooFar(String = String())
|
||||
case missingMemo(String = String())
|
||||
case inputsAlreadySpent(String = String())
|
||||
}
|
||||
|
||||
extension TransactionSubmissionError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Transaction submission error: " + {
|
||||
switch self {
|
||||
case .connectionError(let connectionError):
|
||||
return "\(connectionError)"
|
||||
case .missingMemo(let reason):
|
||||
return "Missing memo error\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .feeError(let reason):
|
||||
return "Fee error\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .invalidTransaction(let reason):
|
||||
return "Invalid transaction\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .tombstoneBlockTooFar(let reason):
|
||||
return "Tombstone block too far\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .inputsAlreadySpent(let reason):
|
||||
return "Inputs already spent\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public enum BalanceTransferEstimationError: Error {
|
||||
case feeExceedsBalance(String = String())
|
||||
case balanceOverflow(String = String())
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
extension BalanceTransferEstimationError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Balance transfer estimation error: " + {
|
||||
switch self {
|
||||
case .feeExceedsBalance(let reason):
|
||||
return "Fee exceeds balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
case .balanceOverflow(let reason):
|
||||
return "Balance overflow\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public enum TransactionEstimationError: Error {
|
||||
case invalidInput(String)
|
||||
case insufficientBalance(String = String())
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
extension TransactionEstimationError: CustomStringConvertible {
|
||||
public var description: String {
|
||||
"Transaction estimation error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .insufficientBalance(let reason):
|
||||
return "Insufficient balance\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum CryptoUtils {
|
||||
static func ristrettoPrivateValidate(_ bytes: Data) -> Bool {
|
||||
bytes.asMcBuffer { bytesPtr in
|
||||
var valid = false
|
||||
withMcInfallible {
|
||||
mc_ristretto_private_validate(bytesPtr, &valid)
|
||||
}
|
||||
return valid
|
||||
}
|
||||
}
|
||||
|
||||
static func ristrettoPublicValidate(_ bytes: Data) -> Bool {
|
||||
bytes.asMcBuffer { bytesPtr in
|
||||
var valid = false
|
||||
withMcInfallible {
|
||||
mc_ristretto_public_validate(bytesPtr, &valid)
|
||||
}
|
||||
return valid
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct RistrettoPrivate {
|
||||
let data32: Data32
|
||||
|
||||
init?(_ data: Data32) {
|
||||
guard CryptoUtils.ristrettoPrivateValidate(data.data) else {
|
||||
return nil
|
||||
}
|
||||
self.data32 = data
|
||||
}
|
||||
|
||||
init(skippingValidation data: Data32) {
|
||||
self.data32 = data
|
||||
}
|
||||
}
|
||||
|
||||
extension RistrettoPrivate: DataConvertibleImpl {
|
||||
typealias Iterator = Data.Iterator
|
||||
|
||||
init?(_ data: Data) {
|
||||
guard let data32 = Data32(data.data) else {
|
||||
return nil
|
||||
}
|
||||
self.init(data32)
|
||||
}
|
||||
|
||||
var data: Data { data32.data }
|
||||
}
|
||||
|
||||
extension RistrettoPrivate {
|
||||
init?(_ ristrettoPrivate: External_RistrettoPrivate) {
|
||||
self.init(ristrettoPrivate.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension External_RistrettoPrivate {
|
||||
init(_ ristrettoPrivate: RistrettoPrivate) {
|
||||
self.init()
|
||||
self.data = ristrettoPrivate.data
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct RistrettoPublic {
|
||||
let data32: Data32
|
||||
|
||||
init?(_ data: Data32) {
|
||||
guard CryptoUtils.ristrettoPublicValidate(data.data) else {
|
||||
return nil
|
||||
}
|
||||
self.data32 = data
|
||||
}
|
||||
|
||||
init(skippingValidation data: Data32) {
|
||||
self.data32 = data
|
||||
}
|
||||
}
|
||||
|
||||
extension RistrettoPublic: DataConvertibleImpl {
|
||||
typealias Iterator = Data.Iterator
|
||||
|
||||
init?(_ data: Data) {
|
||||
guard let data32 = Data32(data.data) else {
|
||||
return nil
|
||||
}
|
||||
self.init(data32)
|
||||
}
|
||||
|
||||
var data: Data { data32.data }
|
||||
}
|
||||
|
||||
extension RistrettoPublic {
|
||||
init?(_ ristrettoPublic: External_CompressedRistretto) {
|
||||
self.init(ristrettoPublic.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension External_CompressedRistretto {
|
||||
init(_ ristrettoPublic: RistrettoPublic) {
|
||||
self.init()
|
||||
self.data = ristrettoPublic.data
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum VersionedCryptoBoxError: Error {
|
||||
case invalidInput(String)
|
||||
case unsupportedVersion(String)
|
||||
}
|
||||
|
||||
extension VersionedCryptoBoxError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Versioned CryptoBox error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .unsupportedVersion(let reason):
|
||||
return "Unsupported version: \(reason)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
enum VersionedCryptoBox {
|
||||
static func encrypt(
|
||||
plaintext: Data,
|
||||
publicKey: RistrettoPublic,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?,
|
||||
rngContext: Any?
|
||||
) -> Result<Data, InvalidInputError> {
|
||||
publicKey.asMcBuffer { viewPublicKeyPtr in
|
||||
plaintext.asMcBuffer { plaintextPtr in
|
||||
withMcRngCallback(rng: rng, rngContext: rngContext) { rngCallbackPtr in
|
||||
Data.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_versioned_crypto_box_encrypt(
|
||||
viewPublicKeyPtr,
|
||||
plaintextPtr,
|
||||
rngCallbackPtr,
|
||||
bufferPtr,
|
||||
&errorPtr)
|
||||
}).mapError {
|
||||
switch $0.errorCode {
|
||||
case .aead:
|
||||
return InvalidInputError("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_versioned_crypto_box_encrypt should not throw
|
||||
// non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func decrypt(
|
||||
ciphertext: Data,
|
||||
privateKey: RistrettoPrivate
|
||||
) -> Result<Data, VersionedCryptoBoxError> {
|
||||
privateKey.asMcBuffer { privateKeyPtr in
|
||||
ciphertext.asMcBuffer { ciphertextPtr in
|
||||
Data.make(withEstimatedLengthMcMutableBuffer: ciphertext.count)
|
||||
{ bufferPtr, errorPtr in
|
||||
mc_versioned_crypto_box_decrypt(
|
||||
privateKeyPtr,
|
||||
ciphertextPtr,
|
||||
bufferPtr,
|
||||
&errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .aead, .invalidInput:
|
||||
return .invalidInput("\(redacting: $0.description)")
|
||||
case .unsupportedCryptoBoxVersion:
|
||||
return .unsupportedVersion("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_versioned_crypto_box_decrypt should not throw non-documented
|
||||
// errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
public enum Base58DecodingResult {
|
||||
case publicAddress(PublicAddress)
|
||||
case paymentRequest(PaymentRequest)
|
||||
case transferPayload(TransferPayload)
|
||||
}
|
||||
|
||||
public enum Base58Coder {
|
||||
public static func encode(_ publicAddress: PublicAddress) -> String {
|
||||
var wrapper = Printable_PrintableWrapper()
|
||||
wrapper.publicAddress = External_PublicAddress(publicAddress)
|
||||
return wrapper.base58EncodedString()
|
||||
}
|
||||
|
||||
public static func encode(_ paymentRequest: PaymentRequest) -> String {
|
||||
var wrapper = Printable_PrintableWrapper()
|
||||
wrapper.paymentRequest = Printable_PaymentRequest(paymentRequest)
|
||||
return wrapper.base58EncodedString()
|
||||
}
|
||||
|
||||
public static func encode(_ transferPayload: TransferPayload) -> String {
|
||||
var wrapper = Printable_PrintableWrapper()
|
||||
wrapper.transferPayload = Printable_TransferPayload(transferPayload)
|
||||
return wrapper.base58EncodedString()
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when the input is not decodable.
|
||||
public static func decode(_ base58String: String) -> Base58DecodingResult? {
|
||||
guard let wrapper = Printable_PrintableWrapper(base58Encoded: base58String) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch wrapper.wrapper {
|
||||
case .publicAddress(let publicAddress):
|
||||
guard let publicAddress = PublicAddress(publicAddress) else {
|
||||
return nil
|
||||
}
|
||||
return .publicAddress(publicAddress)
|
||||
case .paymentRequest(let paymentRequest):
|
||||
guard let paymentRequest = PaymentRequest(paymentRequest) else {
|
||||
return nil
|
||||
}
|
||||
return .paymentRequest(paymentRequest)
|
||||
case .transferPayload(let transferPayload):
|
||||
guard let transferPayload = TransferPayload(transferPayload) else {
|
||||
return nil
|
||||
}
|
||||
return .transferPayload(transferPayload)
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,142 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum MobUri {
|
||||
public enum Payload {
|
||||
case publicAddress(PublicAddress)
|
||||
case paymentRequest(PaymentRequest)
|
||||
case transferPayload(TransferPayload)
|
||||
}
|
||||
|
||||
public static func decode(uri uriString: String) -> Result<Payload, InvalidInputError> {
|
||||
guard let uri = URL(string: uriString) else {
|
||||
logger.info("Could not parse MobURI as URL: \(redacting: uriString)")
|
||||
return .failure(
|
||||
InvalidInputError("Could not parse MobUri as URL: \(redacting: uriString)"))
|
||||
}
|
||||
guard let scheme = uri.scheme else {
|
||||
logger.info("MobUri scheme cannot be empty.")
|
||||
return .failure(InvalidInputError("MobUri scheme cannot be empty."))
|
||||
}
|
||||
guard scheme == McConstants.MOB_URI_SCHEME else {
|
||||
logger.info(
|
||||
"MobUri scheme must be \"\(McConstants.MOB_URI_SCHEME)\". Found: \(scheme)")
|
||||
return .failure(InvalidInputError(
|
||||
"MobUri scheme must be \"\(McConstants.MOB_URI_SCHEME)\". Found: \(scheme)"))
|
||||
}
|
||||
|
||||
return Payload.make(pathComponents: uri.pathComponents)
|
||||
}
|
||||
|
||||
public static func encode(_ publicAddress: PublicAddress) -> String {
|
||||
encode(.publicAddress(publicAddress))
|
||||
}
|
||||
|
||||
public static func encode(_ paymentRequest: PaymentRequest) -> String {
|
||||
encode(.paymentRequest(paymentRequest))
|
||||
}
|
||||
|
||||
public static func encode(_ transferPayload: TransferPayload) -> String {
|
||||
encode(.transferPayload(transferPayload))
|
||||
}
|
||||
|
||||
static func encode(_ payload: Payload) -> String {
|
||||
"\(McConstants.MOB_URI_SCHEME)://\(payload.uriPath)"
|
||||
}
|
||||
}
|
||||
|
||||
extension MobUri.Payload {
|
||||
static func make(pathComponents: [String]) -> Result<MobUri.Payload, InvalidInputError> {
|
||||
// Foundation.URL returns "/" as the first value in pathComponents, so we normalize by
|
||||
// removing it.
|
||||
guard let firstComponent = pathComponents.first else {
|
||||
return .failure(InvalidInputError("MobUri must have a path."))
|
||||
}
|
||||
guard firstComponent == "/" else {
|
||||
return .failure(InvalidInputError("MobUri must have an absolute path."))
|
||||
}
|
||||
let pathComponents = Array(pathComponents.dropFirst())
|
||||
|
||||
guard pathComponents.count >= 2 else {
|
||||
return .failure(InvalidInputError("MobUri must have at least 2 path components."))
|
||||
}
|
||||
let payloadTypeString = pathComponents[0]
|
||||
|
||||
guard let payloadType = PayloadType(payloadTypeString) else {
|
||||
return .failure(InvalidInputError(
|
||||
"MobUri contains unrecognized payload type: \(payloadTypeString)"))
|
||||
}
|
||||
|
||||
switch payloadType {
|
||||
case .b58:
|
||||
let payloadString = pathComponents[1]
|
||||
guard let decodingResult = Base58Coder.decode(payloadString) else {
|
||||
return .failure(InvalidInputError(
|
||||
"MobUri payload base-58 decoding failed. Payload: \(redacting: payloadString)"))
|
||||
}
|
||||
|
||||
return .success(MobUri.Payload(decodingResult))
|
||||
}
|
||||
}
|
||||
|
||||
init(_ base58DecodingResult: Base58DecodingResult) {
|
||||
switch base58DecodingResult {
|
||||
case .publicAddress(let publicAddress):
|
||||
self = .publicAddress(publicAddress)
|
||||
case .paymentRequest(let paymentRequest):
|
||||
self = .paymentRequest(paymentRequest)
|
||||
case .transferPayload(let transferPayload):
|
||||
self = .transferPayload(transferPayload)
|
||||
}
|
||||
}
|
||||
|
||||
var payloadType: PayloadType {
|
||||
switch self {
|
||||
case .publicAddress, .paymentRequest, .transferPayload:
|
||||
return .b58
|
||||
}
|
||||
}
|
||||
|
||||
var payloadString: String {
|
||||
switch self {
|
||||
case .publicAddress(let publicAddress):
|
||||
return Base58Coder.encode(publicAddress)
|
||||
case .paymentRequest(let paymentRequest):
|
||||
return Base58Coder.encode(paymentRequest)
|
||||
case .transferPayload(let transferPayload):
|
||||
return Base58Coder.encode(transferPayload)
|
||||
}
|
||||
}
|
||||
|
||||
var uriPath: String {
|
||||
"/\(payloadType)/\(payloadString)"
|
||||
}
|
||||
}
|
||||
|
||||
extension MobUri.Payload {
|
||||
enum PayloadType {
|
||||
case b58
|
||||
|
||||
init?(_ string: String) {
|
||||
switch string {
|
||||
case "b58":
|
||||
self = .b58
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MobUri.Payload.PayloadType: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .b58:
|
||||
return "b58"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
public struct PaymentRequest {
|
||||
public let publicAddress: PublicAddress
|
||||
public let value: UInt64?
|
||||
public let memo: String?
|
||||
|
||||
/// # Notes:
|
||||
/// * Providing a `value` of `0` is the same as passing `nil`, meaning no value is specified.
|
||||
/// * Providing an empty string for `memo` is the same as passing `nil`, meaning no memo is
|
||||
/// specified.
|
||||
public init(publicAddress: PublicAddress, value: UInt64? = nil, memo: String? = nil) {
|
||||
self.publicAddress = publicAddress
|
||||
|
||||
if let value = value, value != 0 {
|
||||
self.value = value
|
||||
} else {
|
||||
self.value = nil
|
||||
}
|
||||
|
||||
if let memo = memo, !memo.isEmpty {
|
||||
self.memo = memo
|
||||
} else {
|
||||
self.memo = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PaymentRequest: Equatable {}
|
||||
extension PaymentRequest: Hashable {}
|
||||
|
||||
extension PaymentRequest {
|
||||
init?(_ paymentRequest: Printable_PaymentRequest) {
|
||||
guard let publicAddress = PublicAddress(paymentRequest.publicAddress) else {
|
||||
return nil
|
||||
}
|
||||
self.publicAddress = publicAddress
|
||||
self.value = paymentRequest.value != 0 ? paymentRequest.value : nil
|
||||
self.memo = !paymentRequest.memo.isEmpty ? paymentRequest.memo : nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Printable_PaymentRequest {
|
||||
init(_ paymentRequest: PaymentRequest) {
|
||||
self.init()
|
||||
self.publicAddress = External_PublicAddress(paymentRequest.publicAddress)
|
||||
if let value = paymentRequest.value {
|
||||
self.value = value
|
||||
}
|
||||
if let memo = paymentRequest.memo {
|
||||
self.memo = memo
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension Printable_PrintableWrapper {
|
||||
init?(base58Encoded base58String: String) {
|
||||
guard case .success(let decodedData) =
|
||||
Data.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_printable_wrapper_b58_decode(base58String, bufferPtr, &errorPtr)
|
||||
})
|
||||
else {
|
||||
logger.warning("PrintableWrapper base-58 decoding failed.")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let printableWrapper = try? Self(serializedData: decodedData) else {
|
||||
logger.warning("Printable_PrintableWrapper deserialization failed.")
|
||||
return nil
|
||||
}
|
||||
|
||||
self = printableWrapper
|
||||
}
|
||||
|
||||
func base58EncodedString() -> String {
|
||||
let serialized = serializedDataInfallible
|
||||
return serialized.asMcBuffer { bufferPtr in
|
||||
String(mcString: withMcInfallibleReturningOptional {
|
||||
mc_printable_wrapper_b58_encode(bufferPtr)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
public struct TransferPayload {
|
||||
let rootEntropy32: Data32
|
||||
let txOutPublicKey: RistrettoPublic
|
||||
public let memo: String?
|
||||
|
||||
init(rootEntropy: Data32, txOutPublicKey: RistrettoPublic, memo: String? = nil) {
|
||||
self.rootEntropy32 = rootEntropy
|
||||
self.txOutPublicKey = txOutPublicKey
|
||||
self.memo = memo?.isEmpty == false ? memo : nil
|
||||
}
|
||||
|
||||
public var rootEntropy: Data {
|
||||
rootEntropy32.data
|
||||
}
|
||||
}
|
||||
|
||||
extension TransferPayload: Equatable {}
|
||||
extension TransferPayload: Hashable {}
|
||||
|
||||
extension TransferPayload {
|
||||
init?(_ transferPayload: Printable_TransferPayload) {
|
||||
guard let rootEntropy = Data32(transferPayload.rootEntropy),
|
||||
let txOutPublicKey = RistrettoPublic(transferPayload.txOutPublicKey.data)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self.rootEntropy32 = rootEntropy
|
||||
self.txOutPublicKey = txOutPublicKey
|
||||
self.memo = !transferPayload.memo.isEmpty ? transferPayload.memo : nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Printable_TransferPayload {
|
||||
init(_ transferPayload: TransferPayload) {
|
||||
self.init()
|
||||
self.rootEntropy = transferPayload.rootEntropy
|
||||
self.txOutPublicKey = External_CompressedRistretto(transferPayload.txOutPublicKey)
|
||||
if let memo = transferPayload.memo {
|
||||
self.memo = memo
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_arguments multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct FogKeyImageChecker {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let fogKeyImageService: FogKeyImageService
|
||||
|
||||
init(fogKeyImageService: FogKeyImageService, targetQueue: DispatchQueue?) {
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
|
||||
self.fogKeyImageService = fogKeyImageService
|
||||
}
|
||||
|
||||
func checkKeyImage(
|
||||
keyImage: KeyImage,
|
||||
nextKeyImageQueryBlockIndex: UInt64 = 0,
|
||||
completion: @escaping (Result<KeyImage.SpentStatus, ConnectionError>) -> Void
|
||||
) {
|
||||
checkKeyImages(
|
||||
keyImageQueries: [(keyImage, nextKeyImageQueryBlockIndex: nextKeyImageQueryBlockIndex)]
|
||||
) {
|
||||
completion($0.flatMap { statuses in
|
||||
guard let keyImageStatus = statuses.first else {
|
||||
return .failure(.invalidServerResponse(
|
||||
"CheckKeyImage failed to return results: \(statuses)"))
|
||||
}
|
||||
return .success(keyImageStatus)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
keyImageQueries: [KeyImage],
|
||||
maxKeyImagesPerQuery: Int,
|
||||
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
|
||||
) {
|
||||
checkKeyImages(
|
||||
keyImageQueries: keyImageQueries.map { ($0, nextKeyImageQueryBlockIndex: 0) },
|
||||
maxKeyImagesPerQuery: maxKeyImagesPerQuery,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
|
||||
maxKeyImagesPerQuery: Int,
|
||||
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
|
||||
) {
|
||||
let queryArrays = keyImageQueries.chunked(maxLength: maxKeyImagesPerQuery).map { Array($0) }
|
||||
queryArrays.mapAsync({ chunk, callback in
|
||||
checkKeyImages(keyImageQueries: chunk, completion: callback)
|
||||
}, serialQueue: serialQueue, completion: { result in
|
||||
completion(result.map { $0.flatMap { $0 } })
|
||||
})
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
keyImageQueries: [KeyImage],
|
||||
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
|
||||
) {
|
||||
checkKeyImages(
|
||||
keyImageQueries: keyImageQueries.map { ($0, nextKeyImageQueryBlockIndex: 0) },
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
|
||||
completion: @escaping (Result<[KeyImage.SpentStatus], ConnectionError>) -> Void
|
||||
) {
|
||||
var request = FogLedger_CheckKeyImagesRequest()
|
||||
request.queries = keyImageQueries.map {
|
||||
var query = FogLedger_KeyImageQuery()
|
||||
query.keyImage = External_KeyImage($0.0)
|
||||
query.startBlock = $0.nextKeyImageQueryBlockIndex
|
||||
return query
|
||||
}
|
||||
fogKeyImageService.checkKeyImages(request: request) {
|
||||
completion($0.flatMap {
|
||||
Self.parseResponse(keyImageQueries: keyImageQueries, response: $0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseResponse(
|
||||
keyImageQueries: [(KeyImage, nextKeyImageQueryBlockIndex: UInt64)],
|
||||
response: FogLedger_CheckKeyImagesResponse
|
||||
) -> Result<[KeyImage.SpentStatus], ConnectionError> {
|
||||
keyImageQueries.map { query in
|
||||
guard let keyImageResult = response.results.first(
|
||||
where: { KeyImage($0.keyImage) == query.0 }) else
|
||||
{
|
||||
return .success(.unspent(knownToBeUnspentBlockCount: response.numBlocks))
|
||||
}
|
||||
|
||||
switch keyImageResult.keyImageResultCodeEnum {
|
||||
case .spent:
|
||||
let spentAtBlock = BlockMetadata(
|
||||
index: keyImageResult.spentAt,
|
||||
timestampStatus: keyImageResult.timestampStatus)
|
||||
return .success(.spent(block: spentAtBlock))
|
||||
case .notSpent:
|
||||
return .success(.unspent(knownToBeUnspentBlockCount: response.numBlocks))
|
||||
case .keyImageError, .unused, .UNRECOGNIZED:
|
||||
return .failure(.invalidServerResponse("Fog KeyImage result error: " +
|
||||
"\(keyImageResult.keyImageResultCodeEnum), response: \(response)"))
|
||||
}
|
||||
}.collectResult()
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_arguments multiline_function_chains
|
||||
// swiftlint:disable closure_body_length
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum FogMerkleProofFetcherError: Error {
|
||||
case connectionError(ConnectionError)
|
||||
case outOfBounds(blockCount: UInt64, ledgerTxOutCount: UInt64)
|
||||
}
|
||||
|
||||
extension FogMerkleProofFetcherError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Fog Merkle Proof Fetcher error: " + {
|
||||
switch self {
|
||||
case .connectionError(let innerError):
|
||||
return "\(innerError)"
|
||||
case let .outOfBounds(blockCount: blockCount, ledgerTxOutCount: txOutCount):
|
||||
return "Out of bounds: blockCount: \(blockCount), globalTxOutCount: \(txOutCount)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
struct FogMerkleProofFetcher {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let fogMerkleProofService: FogMerkleProofService
|
||||
|
||||
init(fogMerkleProofService: FogMerkleProofService, targetQueue: DispatchQueue?) {
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
|
||||
self.fogMerkleProofService = fogMerkleProofService
|
||||
}
|
||||
|
||||
func getOutputs(
|
||||
globalIndicesArray: [[UInt64]],
|
||||
merkleRootBlock: UInt64,
|
||||
maxNumIndicesPerQuery: Int,
|
||||
completion: @escaping (
|
||||
Result<[[(TxOut, TxOutMembershipProof)]], FogMerkleProofFetcherError>
|
||||
) -> Void
|
||||
) {
|
||||
getOutputs(
|
||||
globalIndices: globalIndicesArray.flatMap { $0 },
|
||||
merkleRootBlock: merkleRootBlock,
|
||||
maxNumIndicesPerQuery: maxNumIndicesPerQuery
|
||||
) {
|
||||
completion($0.flatMap { allResults in
|
||||
globalIndicesArray.map { globalIndices in
|
||||
guard let results = allResults[globalIndices] else {
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"Global txout indices not found in GetOutputs reponse. " +
|
||||
"globalTxOutIndices: \(globalIndices), returned outputs: " +
|
||||
"\(allResults)")))
|
||||
}
|
||||
return .success(results)
|
||||
}.collectResult()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getOutputs(
|
||||
globalIndices: [UInt64],
|
||||
merkleRootBlock: UInt64,
|
||||
maxNumIndicesPerQuery: Int,
|
||||
completion: @escaping (
|
||||
Result<[UInt64: (TxOut, TxOutMembershipProof)], FogMerkleProofFetcherError>
|
||||
) -> Void
|
||||
) {
|
||||
let globalIndicesArrays =
|
||||
globalIndices.chunked(maxLength: maxNumIndicesPerQuery).map { Array($0) }
|
||||
globalIndicesArrays.mapAsync({ chunk, callback in
|
||||
getOutputs(globalIndices: chunk, merkleRootBlock: merkleRootBlock, completion: callback)
|
||||
}, serialQueue: serialQueue, completion: {
|
||||
completion($0.map { arrayOfOutputMaps in
|
||||
arrayOfOutputMaps.reduce(into: [:]) { outputMapAccum, outputMap in
|
||||
outputMapAccum.merge(outputMap, uniquingKeysWith: { key1, _ in key1 })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getOutputs(
|
||||
globalIndices: [UInt64],
|
||||
merkleRootBlock: UInt64,
|
||||
completion: @escaping (
|
||||
Result<[UInt64: (TxOut, TxOutMembershipProof)], FogMerkleProofFetcherError>
|
||||
) -> Void
|
||||
) {
|
||||
var request = FogLedger_GetOutputsRequest()
|
||||
request.indices = globalIndices
|
||||
request.merkleRootBlock = merkleRootBlock
|
||||
fogMerkleProofService.getOutputs(request: request) {
|
||||
completion(
|
||||
$0.mapError { .connectionError($0) }
|
||||
.flatMap { Self.parseResponse(response: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseResponse(response: FogLedger_GetOutputsResponse)
|
||||
-> Result<[UInt64: (TxOut, TxOutMembershipProof)], FogMerkleProofFetcherError>
|
||||
{
|
||||
response.results.map { outputResult in
|
||||
switch outputResult.resultCodeEnum {
|
||||
case .exists:
|
||||
break
|
||||
case .doesNotExist:
|
||||
return .failure(.outOfBounds(
|
||||
blockCount: response.numBlocks,
|
||||
ledgerTxOutCount: response.globalTxoCount))
|
||||
case .outputDatabaseError, .intentionallyUnused, .UNRECOGNIZED:
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"FogMerkleProofService.getOutputs result code error: " +
|
||||
"\(outputResult.resultCodeEnum), response: \(redacting: response)")))
|
||||
}
|
||||
|
||||
let txOut: TxOut
|
||||
switch TxOut.make(outputResult.output) {
|
||||
case .success(let result):
|
||||
txOut = result
|
||||
case .failure(let error):
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"FogMerkleProofService.getOutputs returned invalid TxOut. error: \(error)")))
|
||||
}
|
||||
|
||||
let membershipProof: TxOutMembershipProof
|
||||
switch TxOutMembershipProof.make(outputResult.proof) {
|
||||
case .success(let result):
|
||||
membershipProof = result
|
||||
case .failure(let error):
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"FogMerkleProofService.getOutputs returned invalid membership proof. error: " +
|
||||
"\(error)")))
|
||||
}
|
||||
|
||||
return .success((outputResult.index, (txOut, membershipProof)))
|
||||
}.collectResult().map {
|
||||
Dictionary($0, uniquingKeysWith: { key1, _ in key1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct FogUntrustedTxOutFetcher {
|
||||
private let fogUntrustedTxOutService: FogUntrustedTxOutService
|
||||
|
||||
init(fogUntrustedTxOutService: FogUntrustedTxOutService) {
|
||||
self.fogUntrustedTxOutService = fogUntrustedTxOutService
|
||||
}
|
||||
|
||||
func getTxOut(
|
||||
outputPublicKey: RistrettoPublic,
|
||||
completion: @escaping (
|
||||
Result<(result: FogLedger_TxOutResult, blockCount: UInt64), ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
getTxOuts(outputPublicKeys: [outputPublicKey]) {
|
||||
completion($0.flatMap { results, blockCount in
|
||||
guard let result =
|
||||
results.first(where: { $0.txOutPubkey.data == outputPublicKey.data })
|
||||
else {
|
||||
logger.info("failure - Fog UntrustedTxOut service failed to " +
|
||||
"return the requested TxOut: \(redacting: results)")
|
||||
return .failure(.invalidServerResponse(
|
||||
"Fog UntrustedTxOut service failed to return the requested TxOut. " +
|
||||
"\(results)"))
|
||||
}
|
||||
return .success((result, blockCount: blockCount))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getTxOuts(
|
||||
outputPublicKeys: [RistrettoPublic],
|
||||
completion:
|
||||
@escaping (
|
||||
Result<(results: [FogLedger_TxOutResult], blockCount: UInt64), ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
logger.info(
|
||||
"outputPublicKeys: \(redacting: outputPublicKeys.map { $0.hexEncodedString() })")
|
||||
var request = FogLedger_TxOutRequest()
|
||||
request.txOutPubkeys = outputPublicKeys.map { External_CompressedRistretto($0) }
|
||||
fogUntrustedTxOutService.getTxOuts(request: request) {
|
||||
completion($0.flatMap { response in
|
||||
let resultPairs = response.results.map { ($0.txOutPubkey.data, $0) }
|
||||
let publicKeyToResult =
|
||||
Dictionary(resultPairs, uniquingKeysWith: { key1, _ in key1 })
|
||||
|
||||
let outputPublicKeys = outputPublicKeys.map { $0.data }
|
||||
guard let results = publicKeyToResult[outputPublicKeys] else {
|
||||
logger.info("failure - Fog UntrustedTxOut service failed to " +
|
||||
"return the requested TxOuts: \(redacting: response.results)")
|
||||
return .failure(.invalidServerResponse(
|
||||
"Fog UntrustedTxOut service failed to return the requested TxOuts. " +
|
||||
"\(response)"))
|
||||
}
|
||||
|
||||
return .success((results, blockCount: response.numBlocks))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogViewKeyScanner {
|
||||
private let accountKey: AccountKey
|
||||
private let fogBlockService: FogBlockService
|
||||
|
||||
init(accountKey: AccountKey, fogBlockService: FogBlockService) {
|
||||
self.accountKey = accountKey
|
||||
self.fogBlockService = fogBlockService
|
||||
}
|
||||
|
||||
func viewKeyScanBlocks(
|
||||
blockRanges: [Range<UInt64>],
|
||||
completion: @escaping (Result<[KnownTxOut], ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info(
|
||||
"Fetching block ranges: \(blockRanges.map { "[\($0.lowerBound), \($0.upperBound))" })",
|
||||
logFunction: false)
|
||||
|
||||
fetchBlocksTxOuts(ranges: blockRanges) {
|
||||
completion($0.map { blocksTxOuts in
|
||||
logger.info(
|
||||
"View key scanning blocks: " +
|
||||
"\(blockRanges.map { "[\($0.lowerBound), \($0.upperBound))" }) " +
|
||||
"containing \(blocksTxOuts.count) TxOuts",
|
||||
logFunction: false)
|
||||
|
||||
let foundTxOuts = blocksTxOuts.compactMap {
|
||||
$0.decrypt(accountKey: self.accountKey)
|
||||
}
|
||||
logger.info(
|
||||
"View key scanning missed blocks found \(redacting: foundTxOuts.count) TxOuts",
|
||||
logFunction: false)
|
||||
return foundTxOuts
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fetchBlocksTxOuts(
|
||||
ranges: [Range<UInt64>],
|
||||
completion: @escaping (Result<[LedgerTxOut], ConnectionError>) -> Void
|
||||
) {
|
||||
var request = FogLedger_BlockRequest()
|
||||
request.rangeValues = ranges
|
||||
fogBlockService.getBlocks(request: request) {
|
||||
completion($0.flatMap { response in
|
||||
response.blocks.flatMap { responseBlock -> [Result<LedgerTxOut, ConnectionError>] in
|
||||
let globalIndexStart =
|
||||
responseBlock.globalTxoCount - UInt64(responseBlock.outputs.count)
|
||||
return responseBlock.outputs.enumerated().map { outputIndex, output in
|
||||
guard let partialTxOut = PartialTxOut(output) else {
|
||||
let errorMessage =
|
||||
"Fog Block service returned invalid TxOut: \(output)"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.invalidServerResponse(errorMessage))
|
||||
}
|
||||
|
||||
return .success(LedgerTxOut(
|
||||
partialTxOut,
|
||||
globalIndex: globalIndexStart + UInt64(outputIndex),
|
||||
block: responseBlock.metadata))
|
||||
}
|
||||
}.collectResult()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogReportManager {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
private let serialQueue: DispatchQueue
|
||||
private let serviceProvider: ServiceProvider
|
||||
|
||||
init(serviceProvider: ServiceProvider, targetQueue: DispatchQueue?) {
|
||||
self.inner = .init(Inner(targetQueue: targetQueue), targetQueue: targetQueue)
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
|
||||
self.serviceProvider = serviceProvider
|
||||
}
|
||||
|
||||
func reportResponse(
|
||||
for reportUrl: FogUrl,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportUrl: \(reportUrl.url)")
|
||||
serviceProvider.fogReportService(for: reportUrl) { reportService in
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(reportService: reportService, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reportResponse(
|
||||
for reportUrl: FogUrl,
|
||||
reportParams: [(reportId: String, desiredMinPubkeyExpiry: UInt64)],
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportUrl: \(reportUrl.url), reportParams: \(reportParams)")
|
||||
serviceProvider.fogReportService(for: reportUrl) { reportService in
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(
|
||||
reportService: reportService,
|
||||
reportParams: reportParams,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogReportManager {
|
||||
private struct Inner {
|
||||
private let sharedSerialExclusionQueue: DispatchQueue
|
||||
|
||||
private var networkConfigToServer: [GrpcChannelConfig: FogReportServer] = [:]
|
||||
|
||||
init(targetQueue: DispatchQueue?) {
|
||||
self.sharedSerialExclusionQueue = DispatchQueue(
|
||||
label: "com.mobilecoin.\(FogReportServer.self)",
|
||||
target: targetQueue)
|
||||
}
|
||||
|
||||
mutating func reportServer(for reportUrl: FogUrl) -> FogReportServer {
|
||||
let config = GrpcChannelConfig(url: reportUrl)
|
||||
return networkConfigToServer[config] ?? {
|
||||
let reportServer = FogReportServer(serialExclusionQueue: sharedSerialExclusionQueue)
|
||||
networkConfigToServer[config] = reportServer
|
||||
return reportServer
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable array_init
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogReportServer {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
private let serialConnectionQueue: SerialCallbackQueue
|
||||
|
||||
init(serialExclusionQueue: DispatchQueue) {
|
||||
self.inner = .init(Inner(), serialExclusionQueue: serialExclusionQueue)
|
||||
self.serialConnectionQueue = .init(targetQueue: serialExclusionQueue)
|
||||
}
|
||||
|
||||
func reports(
|
||||
reportService: FogReportService,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
fetchReports(reportService: reportService, completion: completion)
|
||||
}
|
||||
|
||||
func reports(
|
||||
reportService: FogReportService,
|
||||
reportParams: [(reportId: String, desiredMinPubkeyExpiry: UInt64)],
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportParams: \(reportParams)")
|
||||
inner.accessAsync {
|
||||
if let reportResponse =
|
||||
$0.cachedReportResponse(satisfyingReportParams: reportParams.map { $0 })
|
||||
{
|
||||
completion(.success(reportResponse))
|
||||
} else {
|
||||
self.fetchReports(
|
||||
reportService: reportService,
|
||||
reportParams: reportParams,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchReports(
|
||||
reportService: FogReportService,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
serialConnectionQueue.append({ callback in
|
||||
self.doFetchReports(reportService: reportService, completion: callback)
|
||||
}, completion: completion)
|
||||
}
|
||||
|
||||
private func fetchReports(
|
||||
reportService: FogReportService,
|
||||
reportParams: [(reportId: String, desiredMinPubkeyExpiry: UInt64)],
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportParams: \(reportParams)")
|
||||
serialConnectionQueue.append({ callback in
|
||||
// Now that we have the serialConnectionQueue lock, check again if there's a cached
|
||||
// report response that satisfies the reportParams.
|
||||
self.inner.accessAsync {
|
||||
if let reportResponse =
|
||||
$0.cachedReportResponse(satisfyingReportParams: reportParams.map { $0 })
|
||||
{
|
||||
callback(.success(reportResponse))
|
||||
} else {
|
||||
// Otherwise, continue with fetching from the network.
|
||||
self.doFetchReports(reportService: reportService, completion: callback)
|
||||
}
|
||||
}
|
||||
}, completion: completion)
|
||||
}
|
||||
|
||||
private func doFetchReports(
|
||||
reportService: FogReportService,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
reportService.getReports(request: Report_ReportRequest()) {
|
||||
guard let reportResponse = $0.successOr(completion: completion) else { return }
|
||||
|
||||
// Save report response before releasing the serialConnectionQueue
|
||||
// lock. This ensures that, if there's another request waiting, it
|
||||
// will have access to the report response we just fetched.
|
||||
self.cacheReportResponse(reportResponse) {
|
||||
completion(.success(reportResponse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cacheReportResponse(
|
||||
_ reportResponse: Report_ReportResponse,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
$0.cacheReportResponse(reportResponse)
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogReportServer {
|
||||
private struct Inner {
|
||||
private var cachedReportResponse: Report_ReportResponse?
|
||||
|
||||
func cachedReportResponse(
|
||||
satisfyingReportParams reportParams: [(reportId: String, minPubkeyExpiry: UInt64)]
|
||||
) -> Report_ReportResponse? {
|
||||
logger.info("reportParams: \(reportParams)")
|
||||
guard let reportResponse = cachedReportResponse else {
|
||||
return nil
|
||||
}
|
||||
guard reportResponse.isValid(reportParams: reportParams) else {
|
||||
logger.info("report response invalid - reportParams: \(reportParams)")
|
||||
return nil
|
||||
}
|
||||
logger.info("report response valid - reportParams: \(reportParams)")
|
||||
return reportResponse
|
||||
}
|
||||
|
||||
mutating func cacheReportResponse(_ reportResponse: Report_ReportResponse) {
|
||||
cachedReportResponse = reportResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Report_ReportResponse {
|
||||
fileprivate func isValid(reportParams: [(reportId: String, minPubkeyExpiry: UInt64)]) -> Bool {
|
||||
reportParams.allSatisfy { reportId, minPubkeyExpiry in
|
||||
reports.contains { $0.fogReportID == reportId && $0.pubkeyExpiry >= minPubkeyExpiry }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogResolver {
|
||||
private let ptr: OpaquePointer
|
||||
|
||||
convenience init() {
|
||||
self.init(attestation: Attestation())
|
||||
}
|
||||
|
||||
convenience init(
|
||||
attestation: Attestation,
|
||||
reportUrlsAndResponses: [(FogUrl, Report_ReportResponse)]
|
||||
) {
|
||||
self.init(attestation: attestation)
|
||||
for (reportUrl, response) in reportUrlsAndResponses {
|
||||
addReportResponse(reportUrl: reportUrl, reportResponse: response)
|
||||
}
|
||||
}
|
||||
|
||||
private init(attestation: Attestation) {
|
||||
logger.info("attestation: \(attestation)")
|
||||
let verifier = AttestationVerifier(attestation: attestation)
|
||||
// Safety: mc_fog_resolver_create should never return nil.
|
||||
self.ptr = verifier.withUnsafeOpaquePointer { verifierPtr in
|
||||
withMcInfallible {
|
||||
mc_fog_resolver_create(verifierPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_fog_resolver_free(ptr)
|
||||
}
|
||||
|
||||
func withUnsafeOpaquePointer<R>(_ body: (OpaquePointer) throws -> R) rethrows -> R {
|
||||
try body(ptr)
|
||||
}
|
||||
|
||||
private func addReportResponse(reportUrl: FogUrl, reportResponse: Report_ReportResponse) {
|
||||
logger.info("")
|
||||
let serializedReportResponse = reportResponse.serializedDataInfallible
|
||||
serializedReportResponse.asMcBuffer { reportResponsePtr in
|
||||
switch withMcError({ errorPtr in
|
||||
mc_fog_resolver_add_report_response(
|
||||
ptr,
|
||||
reportUrl.url.absoluteString,
|
||||
reportResponsePtr,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .invalidInput:
|
||||
// Safety: mc_fog_resolver_add_report_response shouldn't fail deserialization
|
||||
// since we just serialized it and roundtrip serialization should always
|
||||
// succeed.
|
||||
logger.fatalError("\(error)")
|
||||
default:
|
||||
// Safety: mc_fog_resolver_add_report_response should not throw non-documented
|
||||
// errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_arguments
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogResolverManager {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let reportAttestation: Attestation
|
||||
private let reportManager: FogReportManager
|
||||
|
||||
init(
|
||||
fogReportAttestation: Attestation,
|
||||
serviceProvider: ServiceProvider,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
|
||||
self.reportAttestation = fogReportAttestation
|
||||
self.reportManager =
|
||||
FogReportManager(serviceProvider: serviceProvider, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func fogResolver(
|
||||
addresses: [PublicAddress],
|
||||
completion: @escaping (Result<FogResolver, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("addresses: \(addresses.map { "\(redacting: $0)" })")
|
||||
let reportUrls = Set(addresses.compactMap { $0.fogReportUrl })
|
||||
reportUrls.mapAsync({ reportUrl, callback in
|
||||
reportManager.reportResponse(for: reportUrl) {
|
||||
callback($0.map { response in
|
||||
(reportUrl, response)
|
||||
})
|
||||
}
|
||||
}, serialQueue: serialQueue, completion: {
|
||||
completion($0.map { reportUrlsAndResponses in
|
||||
FogResolver(
|
||||
attestation: self.reportAttestation,
|
||||
reportUrlsAndResponses: reportUrlsAndResponses)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func fogResolver(
|
||||
addresses: [PublicAddress],
|
||||
desiredMinPubkeyExpiry: UInt64,
|
||||
completion: @escaping (Result<FogResolver, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("\(addresses.map { "\(redacting: $0)" }), " +
|
||||
"desiredMinPubkeyExpiry: \(desiredMinPubkeyExpiry)")
|
||||
let fogInfos = addresses.compactMap { $0.fogInfo }
|
||||
|
||||
let reportUrlsToFogInfos = Dictionary(grouping: fogInfos, by: { $0.reportUrl })
|
||||
let reportUrlsToReportParams = reportUrlsToFogInfos.mapValues { fogInfos in
|
||||
fogInfos.map { ($0.reportId, desiredMinPubkeyExpiry) }
|
||||
}
|
||||
reportUrlsToReportParams.mapAsync({ reportUrlToReportParams, callback in
|
||||
let (reportUrl, reportParams) = reportUrlToReportParams
|
||||
reportManager.reportResponse(for: reportUrl, reportParams: reportParams) {
|
||||
callback($0.map { response in
|
||||
(reportUrl, response)
|
||||
})
|
||||
}
|
||||
}, serialQueue: serialQueue, completion: {
|
||||
completion($0.map { reportUrlsAndResponses in
|
||||
FogResolver(
|
||||
attestation: self.reportAttestation,
|
||||
reportUrlsAndResponses: reportUrlsAndResponses)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DefaultFogQueryScalingStrategy: FogQueryScalingStrategy {
|
||||
private static let MIN_SEARCH_KEYS_PER_QUERY = 10
|
||||
private static let MAX_SEARCH_KEYS_PER_QUERY = 200
|
||||
private static let SCALING_MULTIPLIER: Double = 3
|
||||
|
||||
func create() -> AnyInfiniteIterator<PositiveInt> {
|
||||
var next = Self.MIN_SEARCH_KEYS_PER_QUERY
|
||||
return AnyInfiniteIterator {
|
||||
guard let current = PositiveInt(next) else {
|
||||
// Safety: `next` should always be positive if we only ever increase in value.
|
||||
logger.fatalError("PositiveInt.init returned nil. value: \(next)")
|
||||
}
|
||||
next = min(Int(Double(next) * Self.SCALING_MULTIPLIER), Self.MAX_SEARCH_KEYS_PER_QUERY)
|
||||
return current
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol FogQueryScalingStrategy {
|
||||
func create() -> AnyInfiniteIterator<PositiveInt>
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum FogRngError: Error {
|
||||
case invalidKey(String)
|
||||
case unsupportedCryptoBoxVersion(String)
|
||||
}
|
||||
|
||||
extension FogRngError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Fog Kex Rng error: " + {
|
||||
switch self {
|
||||
case .invalidKey(let reason):
|
||||
return "Invalid key: \(reason)"
|
||||
case .unsupportedCryptoBoxVersion(let reason):
|
||||
return "Unsupported CryptoBox version: \(reason)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
final class FogRng {
|
||||
static func make(fogRngKey: FogRngKey, accountKey: AccountKey) -> Result<FogRng, FogRngError> {
|
||||
make(fogRngKey: fogRngKey, subaddressViewPrivateKey: accountKey.subaddressViewPrivateKey)
|
||||
}
|
||||
|
||||
static func make(fogRngKey: FogRngKey, subaddressViewPrivateKey: RistrettoPrivate)
|
||||
-> Result<FogRng, FogRngError>
|
||||
{
|
||||
subaddressViewPrivateKey.asMcBuffer { viewPrivateKeyPtr in
|
||||
fogRngKey.pubkey.asMcBuffer { pubkeyPtr in
|
||||
withMcError { errorPtr in
|
||||
mc_fog_rng_create(viewPrivateKeyPtr, pubkeyPtr, fogRngKey.version, &errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .invalidInput:
|
||||
return .invalidKey("\(redacting: $0.description)")
|
||||
case .unsupportedCryptoBoxVersion:
|
||||
return .unsupportedCryptoBoxVersion("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_fog_rng_create should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}.map { ptr in
|
||||
FogRng(ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// - Returns: `.failure` when the input is not deserializable.
|
||||
static func make(serializedData: Data) -> Result<FogRng, FogRngError> {
|
||||
serializedData.asMcBuffer { dataPtr in
|
||||
withMcError { errorPtr in
|
||||
mc_fog_rng_deserialize_proto(dataPtr, &errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .invalidInput:
|
||||
return .invalidKey("\(redacting: $0.description)")
|
||||
case .unsupportedCryptoBoxVersion:
|
||||
return .unsupportedCryptoBoxVersion("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_fog_rng_deserialize_proto should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}.map { ptr in
|
||||
FogRng(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
private let ptr: OpaquePointer
|
||||
private let outputSize: Int
|
||||
|
||||
private init(_ ptr: OpaquePointer) {
|
||||
self.ptr = ptr
|
||||
self.outputSize = withMcInfallibleReturningOptional {
|
||||
let len = mc_fog_rng_get_output_len(ptr)
|
||||
return len >= 0 ? len : nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_fog_rng_free(ptr)
|
||||
}
|
||||
|
||||
var serializedData: Data {
|
||||
Data(withMcMutableBufferInfallible: { bufferPtr in
|
||||
mc_fog_rng_serialize_proto(ptr, bufferPtr)
|
||||
})
|
||||
}
|
||||
|
||||
func clone() -> FogRng {
|
||||
// Safety: mc_fog_rng_clone should never return nil.
|
||||
FogRng(withMcInfallible { mc_fog_rng_clone(ptr) })
|
||||
}
|
||||
|
||||
var index: UInt64 {
|
||||
withMcInfallibleReturningOptional {
|
||||
let res = mc_fog_rng_index(ptr)
|
||||
return res >= 0 ? UInt64(res) : nil
|
||||
}
|
||||
}
|
||||
|
||||
var output: Data {
|
||||
Data(withFixedLengthMcMutableBufferInfallible: outputSize) { bufferPtr in
|
||||
mc_fog_rng_peek(ptr, bufferPtr)
|
||||
}
|
||||
}
|
||||
|
||||
func outputs(count: Int) -> [Data] {
|
||||
let rngCopy = clone()
|
||||
return (0..<count).map { _ in
|
||||
rngCopy.advance()
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func advance() -> Data {
|
||||
Data(withFixedLengthMcMutableBufferInfallible: outputSize) { bufferPtr in
|
||||
mc_fog_rng_advance(ptr, bufferPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogRng {
|
||||
static func make(fogRngPubkey: KexRng_KexRngPubkey, accountKey: AccountKey)
|
||||
-> Result<FogRng, FogRngError>
|
||||
{
|
||||
make(
|
||||
fogRngKey: FogRngKey(fogRngPubkey),
|
||||
subaddressViewPrivateKey: accountKey.subaddressViewPrivateKey)
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct FogRngKey {
|
||||
let pubkey: Data
|
||||
let version: UInt32
|
||||
|
||||
init(pubkey: Data, version: UInt32) {
|
||||
self.pubkey = pubkey
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
|
||||
extension FogRngKey: Equatable {}
|
||||
extension FogRngKey: Hashable {}
|
||||
|
||||
extension FogRngKey {
|
||||
init(_ pubkey: KexRng_KexRngPubkey) {
|
||||
self.pubkey = pubkey.pubkey
|
||||
self.version = pubkey.version
|
||||
}
|
||||
}
|
||||
|
||||
extension KexRng_KexRngPubkey {
|
||||
init(_ fogRngKey: FogRngKey) {
|
||||
self.init()
|
||||
self.pubkey = fogRngKey.pubkey
|
||||
self.version = fogRngKey.version
|
||||
}
|
||||
}
|
||||
@ -1,349 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogRngSet {
|
||||
private var ingestInvocationIdToRngTrackers: [Int64: RngTracker] = [:]
|
||||
private(set) var rngRecordsKnownBlockCount: UInt64 = 0
|
||||
|
||||
var earliestRngRecordStartBlockIndex: UInt64? {
|
||||
ingestInvocationIdToRngTrackers.values.map { $0.startBlockIndex }.min()
|
||||
}
|
||||
|
||||
var knownBlockCount: UInt64 {
|
||||
ingestInvocationIdToRngTrackers.values.map { $0.knownBlockCount }
|
||||
.reduce(rngRecordsKnownBlockCount, min)
|
||||
}
|
||||
|
||||
func searchAttempt(
|
||||
targetBlockCount: UInt64?,
|
||||
numOutputs: PositiveInt,
|
||||
minOutputsPerSelectedRng: Int
|
||||
) -> FogRngSetSearchAttempt {
|
||||
// Max rngs we can select while maintaining the requested minimum outputs per selected rng.
|
||||
let maxRngs = 0 < minOutputsPerSelectedRng && minOutputsPerSelectedRng <= numOutputs.value
|
||||
? numOutputs.value / minOutputsPerSelectedRng : numOutputs.value
|
||||
|
||||
let selectedRngs =
|
||||
selectRngsForSearch(requestedBlockCount: targetBlockCount, maxRngs: maxRngs)
|
||||
guard !selectedRngs.isEmpty else {
|
||||
logger.info(
|
||||
"No active Fog rngs as of block count: \(rngRecordsKnownBlockCount)",
|
||||
logFunction: false)
|
||||
return FogRngSetSearchAttempt()
|
||||
}
|
||||
|
||||
// Num of outputs to generate per selected rng.
|
||||
let outputsPerRng = numOutputs.value / selectedRngs.count
|
||||
let numRemainderOutputs = numOutputs.value % selectedRngs.count
|
||||
|
||||
let ingestInvocationIdAndRngSearchAttempt =
|
||||
selectedRngs.enumerated().map { i, rngPair -> (Int64, FogRngSearchAttempt) in
|
||||
let (ingestInvocationId, rngTracker) = rngPair
|
||||
let numOutputs = outputsPerRng + (i < numRemainderOutputs ? 1 : 0)
|
||||
let rngSearchAttempt = rngTracker.searchAttempt(numOutputs: numOutputs)
|
||||
return (ingestInvocationId, rngSearchAttempt)
|
||||
}
|
||||
return FogRngSetSearchAttempt(
|
||||
ingestInvocationIdToRngSearchAttempt: Dictionary(
|
||||
uniqueKeysWithValues: ingestInvocationIdAndRngSearchAttempt))
|
||||
}
|
||||
|
||||
private func selectRngsForSearch(requestedBlockCount: UInt64?, maxRngs: Int)
|
||||
-> [Int64: RngTracker]
|
||||
{
|
||||
// Filter for rngs that are still active.
|
||||
var eligibleRngTrackers = ingestInvocationIdToRngTrackers.filter { $0.value.active }
|
||||
|
||||
if let requestedBlockCount = requestedBlockCount {
|
||||
// Filter for rngs that haven't already successfully processed the requested number of
|
||||
// blocks.
|
||||
//
|
||||
// This is how we handle TxOut search pagination. If we repeatedly perform search
|
||||
// attempts with the same `requestedBlockCount` (e.g. when performing a single balance
|
||||
// check), eventually all rngs will have a `knownBlockCount` of at least
|
||||
// `requestedBlockCount`.
|
||||
eligibleRngTrackers = eligibleRngTrackers.filter {
|
||||
$0.value.knownBlockCount < requestedBlockCount
|
||||
}
|
||||
}
|
||||
|
||||
return Dictionary(uniqueKeysWithValues: Array(eligibleRngTrackers.prefix(maxRngs)))
|
||||
}
|
||||
|
||||
func processRngs(queryResponse: FogView_QueryResponse, accountKey: AccountKey)
|
||||
-> Result<(), ConnectionError>
|
||||
{
|
||||
processRngRecords(
|
||||
queryResponse.rngs,
|
||||
highestProcessedBlockCount: queryResponse.highestProcessedBlockCount,
|
||||
accountKey: accountKey
|
||||
).map {
|
||||
processDecommissionedRngs(queryResponse.decommissionedIngestInvocations)
|
||||
}
|
||||
}
|
||||
|
||||
private func processRngRecords(
|
||||
_ rngRecords: [FogView_RngRecord],
|
||||
highestProcessedBlockCount: UInt64,
|
||||
accountKey: AccountKey
|
||||
) -> Result<(), ConnectionError> {
|
||||
for rngRecord in rngRecords
|
||||
where ingestInvocationIdToRngTrackers[rngRecord.ingestInvocationID] == nil
|
||||
{
|
||||
logger.info(
|
||||
"New RngRecord: ingestInvocationID: \(rngRecord.ingestInvocationID), pubkey: " +
|
||||
"\(rngRecord.pubkey.pubkey.hexEncodedString()), version: " +
|
||||
"\(rngRecord.pubkey.version), startBlockIndex: \(rngRecord.startBlock)",
|
||||
logFunction: false)
|
||||
|
||||
switch RngTracker.make(rngRecord: rngRecord, accountKey: accountKey) {
|
||||
case .success(let rngTracker):
|
||||
ingestInvocationIdToRngTrackers[rngRecord.ingestInvocationID] = rngTracker
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case .invalidKey(let reason):
|
||||
let errorMessage = "Fog View returned invalid key rng key: \(reason)"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.invalidServerResponse(errorMessage))
|
||||
case .unsupportedCryptoBoxVersion(let reason):
|
||||
let errorMessage = "Fog View returned unsupported kex rng version: \(reason)"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.outdatedClient(errorMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record that Fog has told us about all rngs that could possibly have been active up to
|
||||
// `highestProcessedBlockCount` (while accounting for the possibility that we already have
|
||||
// more up-to-date information already).
|
||||
if highestProcessedBlockCount > rngRecordsKnownBlockCount {
|
||||
logger.info(
|
||||
"FogRngSet updating rngRecordsKnownBlockCount from \(rngRecordsKnownBlockCount) " +
|
||||
"to \(highestProcessedBlockCount)",
|
||||
logFunction: false)
|
||||
rngRecordsKnownBlockCount = highestProcessedBlockCount
|
||||
}
|
||||
|
||||
return .success(())
|
||||
}
|
||||
|
||||
private func processDecommissionedRngs(
|
||||
_ decommissionedRngs: [FogView_DecommissionedIngestInvocation]
|
||||
) {
|
||||
for decommissionedRng in decommissionedRngs {
|
||||
if let rngTracker =
|
||||
ingestInvocationIdToRngTrackers[decommissionedRng.ingestInvocationID]
|
||||
{
|
||||
logger.info(
|
||||
"Rng decommissioned: ingestInvocationID: " +
|
||||
"\(decommissionedRng.ingestInvocationID), lastIngestedBlockIndex: " +
|
||||
"\(decommissionedRng.lastIngestedBlock)",
|
||||
logFunction: false)
|
||||
rngTracker.decommissioned = true
|
||||
} else {
|
||||
logger.error(
|
||||
"Fog View decommissioned unknown ingestInvocation. ingestInvocationID: " +
|
||||
"\(decommissionedRng.ingestInvocationID), lastIngestedBlock: " +
|
||||
"\(decommissionedRng.lastIngestedBlock), current " +
|
||||
"rngRecordsKnownBlockCount: \(rngRecordsKnownBlockCount)",
|
||||
logFunction: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processTxOutSearchResults(
|
||||
queryResponse: FogView_QueryResponse,
|
||||
rngSetSearchAttempt: FogRngSetSearchAttempt
|
||||
) -> Result<[FogView_TxOutSearchResult], ConnectionError> {
|
||||
processTxOutSearchResults(
|
||||
queryResponse.txOutSearchResults,
|
||||
highestProcessedBlockCount: queryResponse.highestProcessedBlockCount,
|
||||
rngSetSearchAttempt: rngSetSearchAttempt)
|
||||
}
|
||||
|
||||
private func processTxOutSearchResults(
|
||||
_ txOutSearchResults: [FogView_TxOutSearchResult],
|
||||
highestProcessedBlockCount: UInt64,
|
||||
rngSetSearchAttempt: FogRngSetSearchAttempt
|
||||
) -> Result<[FogView_TxOutSearchResult], ConnectionError> {
|
||||
let searchKeyToTxOutResult = Dictionary(
|
||||
txOutSearchResults.map { ($0.searchKey, $0) },
|
||||
uniquingKeysWith: { key1, _ in key1 })
|
||||
|
||||
return rngSetSearchAttempt.ingestInvocationIdToRngSearchAttempt
|
||||
.map { ingestInvocationId, rngSearchAttempt
|
||||
-> Result<[FogView_TxOutSearchResult], ConnectionError> in
|
||||
guard let rngTracker = ingestInvocationIdToRngTrackers[ingestInvocationId] else {
|
||||
// This condition is considered a programming error and mean `searchAttempt` was
|
||||
// created using a different `FogRngSet` instance. We silently fail here, since
|
||||
// we know we're still in a valid, internally-consistent state.
|
||||
logger.assertionFailure("RngTracker not found for rngKey in search attempt. " +
|
||||
"ingestInvocationId: \(ingestInvocationId)")
|
||||
return .success([])
|
||||
}
|
||||
|
||||
// Filter for only the outputs we searched for.
|
||||
let rngSearchKeyToTxOutResult: [Data: FogView_TxOutSearchResult] = Dictionary(
|
||||
rngSearchAttempt.searchKeys.map { $0.bytes }.compactMap { searchKeyBytes in
|
||||
guard let txOutResult = searchKeyToTxOutResult[searchKeyBytes] else {
|
||||
logger.error(
|
||||
"Searched key not in search results. searched key: " +
|
||||
"\(searchKeyBytes.hexEncodedString())",
|
||||
logFunction: false)
|
||||
return nil
|
||||
}
|
||||
return (searchKeyBytes, txOutResult)
|
||||
},
|
||||
uniquingKeysWith: { key1, _ in key1 })
|
||||
|
||||
return rngTracker.processSearchKeyResults(
|
||||
rngSearchKeyToTxOutResult: rngSearchKeyToTxOutResult,
|
||||
highestProcessedBlockCount: highestProcessedBlockCount)
|
||||
}.collectResult().map {
|
||||
$0.flatMap { $0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class RngTracker {
|
||||
let rng: FogRng
|
||||
let startBlockIndex: UInt64
|
||||
|
||||
/// Whether this RNG is still in use by Fog.
|
||||
///
|
||||
/// If an RNG has been decommissioned, then all `TxOut`'s corresponding to the RNG are available
|
||||
/// for immediate retrieval from Fog. This means that once we encounter a search miss we can
|
||||
/// stop considering the RNG when generating search keys for a `TxOut` search.
|
||||
var decommissioned = false
|
||||
|
||||
/// Whether we have found all `TxOut`'s for this RNG.
|
||||
///
|
||||
/// An RNG is active until the RNG has been both decommissioned and we've encountered at least
|
||||
/// one search miss since.
|
||||
var active = true
|
||||
|
||||
/// Number of blocks for which all `TxOut`s for this RNG are known.
|
||||
///
|
||||
/// Represents the number of blocks for which we can guarantee that all `TxOut`'s corresponding
|
||||
/// to this RNG have been found. Put another way, we can guarantee that the next output from
|
||||
/// this RNG has no corresponding `TxOut` within this block range.
|
||||
///
|
||||
/// This starts at either `0` or the RNG's `startBlock`. Each time we do a search, if we
|
||||
/// encounter at least one miss (a.k.a. a `TxOut` is not found for an output from this RNG),
|
||||
/// then we set this value to the `highestProcessedBlockCount` returned in the search response.
|
||||
var knownBlockCount: UInt64
|
||||
|
||||
init(rng: FogRng, startBlockIndex: UInt64) {
|
||||
self.rng = rng
|
||||
self.startBlockIndex = startBlockIndex
|
||||
// We assign a blockCount with the value of a blockIndex because, if X is the block index of
|
||||
// the first block that the rng is active, then X is also the number of blocks that came
|
||||
// before that block, hence our knownBlockCount. E.g. if the startBlockIndex is 1, 1 is also
|
||||
// the number of blocks before block index 1.
|
||||
self.knownBlockCount = startBlockIndex
|
||||
}
|
||||
|
||||
func searchAttempt(numOutputs: Int) -> FogRngSearchAttempt {
|
||||
let outputs = rng.outputs(count: numOutputs)
|
||||
let searchKeys = outputs.map { FogSearchKey($0) }
|
||||
|
||||
// Note: converting directly from blockCount to blockIndex is valid here.
|
||||
return FogRngSearchAttempt(searchKeys: searchKeys, startFromBlockIndex: knownBlockCount)
|
||||
}
|
||||
|
||||
func processSearchKeyResults(
|
||||
rngSearchKeyToTxOutResult: [Data: FogView_TxOutSearchResult],
|
||||
highestProcessedBlockCount: UInt64
|
||||
) -> Result<[FogView_TxOutSearchResult], ConnectionError> {
|
||||
var foundTxOutResults: [FogView_TxOutSearchResult] = []
|
||||
|
||||
searchResultLoop: while true {
|
||||
let output = rng.output
|
||||
|
||||
guard let txOutResult = rngSearchKeyToTxOutResult[output] else {
|
||||
// Either we've found all the outputs we searched for or we've processed txos since
|
||||
// this search attempt was made. Either way, if the next output we need wasn't one
|
||||
// of the ones searched for or wasn't in the search results, then there's nothing
|
||||
// else we can do with this rng.
|
||||
logger.debug(
|
||||
"Next rng output not found in searched keys. rng output: " +
|
||||
"0x\(redacting: output.hexEncodedString())",
|
||||
logFunction: false)
|
||||
break
|
||||
}
|
||||
|
||||
switch txOutResult.resultCodeEnum {
|
||||
case .found:
|
||||
foundTxOutResults.append(txOutResult)
|
||||
rng.advance()
|
||||
case .notFound:
|
||||
// The search key failed to return a `TxOut` during this search attempt.
|
||||
|
||||
if highestProcessedBlockCount > knownBlockCount {
|
||||
// `highestProcessedBlockCount` is the number of blocks that fog guarantees it
|
||||
// finished processing when performing the search, so we store
|
||||
// `highestProcessedBlockCount` as the `knownBlockCount` for this RNG on the
|
||||
// assumption that if we encountered a miss for this RNG, then there are no more
|
||||
// `TxOut`'s that can be found for this RNG in the first
|
||||
// `highestProcessedBlockCount` number of blocks in the ledger.
|
||||
knownBlockCount = highestProcessedBlockCount
|
||||
}
|
||||
|
||||
// Break on the first miss
|
||||
break searchResultLoop
|
||||
case .rateLimited:
|
||||
let errorMessage = "Fog View error response: rateLimited"
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
return .failure(.serverRateLimited(errorMessage))
|
||||
case .badSearchKey, .internalError, .intentionallyUnused, .UNRECOGNIZED:
|
||||
let errorMessage = "Fog view error response: \(txOutResult.resultCodeEnum), " +
|
||||
"txOutResult.searchKey: \(redacting: txOutResult.searchKey)"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(.invalidServerResponse(errorMessage))
|
||||
}
|
||||
}
|
||||
|
||||
return .success(foundTxOutResults)
|
||||
}
|
||||
}
|
||||
|
||||
extension RngTracker {
|
||||
static func make(rngRecord: FogView_RngRecord, accountKey: AccountKey)
|
||||
-> Result<RngTracker, FogRngError>
|
||||
{
|
||||
FogRng.make(fogRngPubkey: rngRecord.pubkey, accountKey: accountKey).map { rng in
|
||||
RngTracker(rng: rng, rngRecord: rngRecord)
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(rng: FogRng, rngRecord: FogView_RngRecord) {
|
||||
self.init(rng: rng, startBlockIndex: rngRecord.startBlock)
|
||||
}
|
||||
}
|
||||
|
||||
struct FogRngSetSearchAttempt {
|
||||
fileprivate let ingestInvocationIdToRngSearchAttempt: [Int64: FogRngSearchAttempt]
|
||||
|
||||
fileprivate init(ingestInvocationIdToRngSearchAttempt: [Int64: FogRngSearchAttempt]? = nil) {
|
||||
self.ingestInvocationIdToRngSearchAttempt = ingestInvocationIdToRngSearchAttempt ?? [:]
|
||||
}
|
||||
|
||||
var searchKeys: [FogSearchKey] {
|
||||
ingestInvocationIdToRngSearchAttempt.values.flatMap { $0.searchKeys }
|
||||
}
|
||||
|
||||
var lowestStartFromBlockIndex: UInt64 {
|
||||
ingestInvocationIdToRngSearchAttempt.values.map { $0.startFromBlockIndex }.min() ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
private struct FogRngSearchAttempt {
|
||||
let searchKeys: [FogSearchKey]
|
||||
let startFromBlockIndex: UInt64
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FogSearchKey {
|
||||
let bytes: Data
|
||||
|
||||
init(_ bytes: Data) {
|
||||
self.bytes = bytes
|
||||
}
|
||||
}
|
||||
|
||||
extension FogSearchKey: Equatable {}
|
||||
extension FogSearchKey: Hashable {}
|
||||
@ -1,123 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
import os
|
||||
|
||||
extension FogView {
|
||||
struct TxOutFetcher {
|
||||
private let serialQueue: DispatchQueue
|
||||
private let fogView: ReadWriteDispatchLock<FogView>
|
||||
private let accountKey: AccountKey
|
||||
private let fogViewService: FogViewService
|
||||
private let fogQueryScalingStrategy: FogQueryScalingStrategy
|
||||
|
||||
init(
|
||||
fogView: ReadWriteDispatchLock<FogView>,
|
||||
accountKey: AccountKey,
|
||||
fogViewService: FogViewService,
|
||||
fogQueryScalingStrategy: FogQueryScalingStrategy,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(
|
||||
label: "com.mobilecoin.\(FogView.self).\(Self.self)",
|
||||
target: targetQueue)
|
||||
self.fogView = fogView
|
||||
self.accountKey = accountKey
|
||||
self.fogViewService = fogViewService
|
||||
self.fogQueryScalingStrategy = fogQueryScalingStrategy
|
||||
}
|
||||
|
||||
private var allRngTxOutsFoundBlockCount: UInt64 {
|
||||
fogView.readSync { $0.allRngTxOutsFoundBlockCount }
|
||||
}
|
||||
|
||||
func fetchTxOuts(
|
||||
partialResultsWithWriteLock: @escaping ([KnownTxOut]) -> Void,
|
||||
completion: @escaping (Result<(), ConnectionError>) -> Void
|
||||
) {
|
||||
performSearchRound(
|
||||
targetBlockCount: nil,
|
||||
queryScaling: nil,
|
||||
partialResultsWithWriteLock: partialResultsWithWriteLock,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
private func performSearchRound(
|
||||
targetBlockCount: UInt64?,
|
||||
queryScaling: AnyInfiniteIterator<PositiveInt>?,
|
||||
partialResultsWithWriteLock: @escaping ([KnownTxOut]) -> Void,
|
||||
completion: @escaping (Result<(), ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("Querying Fog View...", logFunction: false)
|
||||
|
||||
let queryScaling = queryScaling ?? fogQueryScalingStrategy.create()
|
||||
let numOutputs = queryScaling.next()
|
||||
let (requestWrapper, searchAttempt) = fogView.readSync {
|
||||
$0.queryRequest(targetBlockCount: targetBlockCount, numOutputs: numOutputs)
|
||||
}
|
||||
fogViewService.query(requestWrapper: requestWrapper) {
|
||||
let result = $0.flatMap { response in
|
||||
self.fogView.writeSync {
|
||||
$0.processQueryResponse(
|
||||
response,
|
||||
searchAttempt: searchAttempt,
|
||||
accountKey: self.accountKey
|
||||
).map { processResult -> UInt64? in
|
||||
if !searchAttempt.searchKeys.isEmpty {
|
||||
partialResultsWithWriteLock(processResult.newTxOuts)
|
||||
}
|
||||
return processResult.nextRoundTargetBlockCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let nextRoundTargetBlockCount):
|
||||
if let nextRoundTargetBlockCount = nextRoundTargetBlockCount {
|
||||
// Reset query scaling if we didn't search for anything last round.
|
||||
let queryScaling = !searchAttempt.searchKeys.isEmpty ? queryScaling : nil
|
||||
|
||||
// Do another search round
|
||||
self.performSearchRound(
|
||||
targetBlockCount: nextRoundTargetBlockCount,
|
||||
queryScaling: queryScaling,
|
||||
partialResultsWithWriteLock: partialResultsWithWriteLock,
|
||||
completion: completion)
|
||||
} else {
|
||||
// Search complete
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_QueryResponse: CustomRedactingStringConvertible {
|
||||
var redactingDescription: String {
|
||||
let hits = txOutSearchResults.filter { $0.resultCodeEnum == .found }
|
||||
return """
|
||||
FogView_QueryResponse:
|
||||
rng record count: \(rngs.count)
|
||||
TxOutResult count: \(redacting: txOutSearchResults.count)
|
||||
TxOutResult success count: \(redacting: hits.count)
|
||||
highestProcessedBlockCount: \(highestProcessedBlockCount)
|
||||
highestProcessedBlockSignatureTimestamp: \(highestProcessedBlockSignatureTimestamp) \
|
||||
\(Date(timeIntervalSince1970: TimeInterval(highestProcessedBlockSignatureTimestamp)))
|
||||
decommissionedRngs: \(decommissionedIngestInvocations)
|
||||
missedBlockRanges.count: \(missedBlockRanges.count)
|
||||
missedBlockRanges: \(missedBlockRanges)
|
||||
nextStartFromUserEventId: \(nextStartFromUserEventID)
|
||||
lastKnownBlockCount: \(lastKnownBlockCount)
|
||||
lastKnownBlockCumulativeTxoCount: \(lastKnownBlockCumulativeTxoCount)
|
||||
txOutResults result codes: \(redacting: txOutSearchResults.map { $0.resultCode })
|
||||
"""
|
||||
}
|
||||
}
|
||||
@ -1,193 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogView {
|
||||
private let rngSet = FogRngSet()
|
||||
private(set) var unscannedMissedBlocksRanges: [Range<UInt64>] = []
|
||||
|
||||
/// See `FogUserEvent` in the Fog repo for a list of user events.
|
||||
private(set) var nextStartFromUserEventId: Int64 = 0
|
||||
|
||||
var allRngTxOutsFoundBlockCount: UInt64 {
|
||||
rngSet.knownBlockCount
|
||||
}
|
||||
|
||||
var allRngRecordsKnownBlockCount: UInt64 {
|
||||
rngSet.rngRecordsKnownBlockCount
|
||||
}
|
||||
|
||||
func queryRequest(targetBlockCount: UInt64?, numOutputs: PositiveInt)
|
||||
-> (FogViewQueryRequestWrapper, FogSearchAttempt)
|
||||
{
|
||||
let rngSetSearchAttempt = rngSet.searchAttempt(
|
||||
targetBlockCount: targetBlockCount,
|
||||
numOutputs: numOutputs,
|
||||
minOutputsPerSelectedRng: min(2, numOutputs.value))
|
||||
let searchAttempt = FogSearchAttempt(
|
||||
rngSetSearchAttempt: rngSetSearchAttempt,
|
||||
targetBlockCount: targetBlockCount)
|
||||
|
||||
var wrapper = FogViewQueryRequestWrapper()
|
||||
wrapper.requestAad.startFromUserEventID = nextStartFromUserEventId
|
||||
wrapper.requestAad.startFromBlockIndex = rngSetSearchAttempt.lowestStartFromBlockIndex
|
||||
wrapper.request.getTxos = rngSetSearchAttempt.searchKeys.map { $0.bytes }
|
||||
|
||||
logger.info(
|
||||
"Fog view query params: startFromUserEventID: " +
|
||||
"\(wrapper.requestAad.startFromUserEventID), startFromBlockIndex: " +
|
||||
"\(wrapper.requestAad.startFromBlockIndex)",
|
||||
logFunction: false)
|
||||
|
||||
return (wrapper, searchAttempt)
|
||||
}
|
||||
|
||||
func processQueryResponse(
|
||||
_ queryResponse: FogView_QueryResponse,
|
||||
searchAttempt: FogSearchAttempt,
|
||||
accountKey: AccountKey
|
||||
) -> Result<(newTxOuts: [KnownTxOut], nextRoundTargetBlockCount: UInt64?), ConnectionError> {
|
||||
logger.info("Processing Fog View query response...", logFunction: false)
|
||||
|
||||
return rngSet.processRngs(queryResponse: queryResponse, accountKey: accountKey).map {
|
||||
processMissedBlockRanges(queryResponse.missedBlockRanges)
|
||||
|
||||
if queryResponse.nextStartFromUserEventID > nextStartFromUserEventId {
|
||||
// We set this only after we've processed the info in `QueryResponse` that's
|
||||
// considered an event, which currently includes `NewRngRecord`,
|
||||
// `DecommissionIngestInvocation`, and `MissingBlocks`. (See `FogUserEvent` in the
|
||||
// Fog repo for the canonical list.)
|
||||
nextStartFromUserEventId = queryResponse.nextStartFromUserEventID
|
||||
}
|
||||
}.flatMap {
|
||||
rngSet.processTxOutSearchResults(
|
||||
queryResponse: queryResponse,
|
||||
rngSetSearchAttempt: searchAttempt.rngSetSearchAttempt)
|
||||
}.flatMap { searchResults in
|
||||
searchResults.map { searchResult in
|
||||
Self.decryptSearchResult(searchResult, accountKey: accountKey)
|
||||
}.collectResult()
|
||||
}.flatMap { txOutRecords in
|
||||
txOutRecords.map { txOutRecord in
|
||||
LedgerTxOut.make(txOutRecord: txOutRecord, viewKey: accountKey.viewPrivateKey)
|
||||
}.collectResult()
|
||||
}.map { txOuts in
|
||||
let foundTxOuts = Self.ownedTxOuts(validating: txOuts, accountKey: accountKey)
|
||||
|
||||
// After the first call we know the current number of blocks processed by the fog
|
||||
// ingest server, so we'll use that to try to build a complete view of the ledger
|
||||
// for our account up to this number of blocks. We keep using this
|
||||
// `targetBlockCount` because the ledger is always growing and we have to stop and
|
||||
// declare the balance check finished at some point.
|
||||
let targetBlockCount =
|
||||
searchAttempt.targetBlockCount ?? queryResponse.highestProcessedBlockCount
|
||||
|
||||
let performAdditionalSearchRounds = allRngTxOutsFoundBlockCount < targetBlockCount
|
||||
let nextRoundTargetBlockCount = performAdditionalSearchRounds ? targetBlockCount : nil
|
||||
|
||||
return (foundTxOuts, nextRoundTargetBlockCount)
|
||||
}
|
||||
}
|
||||
|
||||
private func processMissedBlockRanges(_ missedBlockRanges: [FogCommon_BlockRange]) {
|
||||
var missedBlocks = missedBlockRanges.map { $0.range }
|
||||
if let earliestRngStartBlockIndex = rngSet.earliestRngRecordStartBlockIndex {
|
||||
missedBlocks = missedBlocks.compactMap { range in
|
||||
// Check that we don't view key scan missed blocks that occur before the first
|
||||
// RngRecord's startBlock. This is a workaround for Fog Ingest needing to mark the
|
||||
// blocks before the first run of Fog Ingest as missed blocks.
|
||||
//
|
||||
// This can be removed when Fog provides a guarantee that it won't report the blocks
|
||||
// before Fog Ingest was run for the first time as missed.
|
||||
guard range.lowerBound >= earliestRngStartBlockIndex else {
|
||||
if range.upperBound <= earliestRngStartBlockIndex {
|
||||
// Entire missed block range is before the earliest RngRecord's startBlock.
|
||||
return nil
|
||||
} else {
|
||||
// Part of missed block range is before the ealiest RngRecord's startBlock,
|
||||
// so we modify the missed blocks range.
|
||||
return earliestRngStartBlockIndex..<range.upperBound
|
||||
}
|
||||
}
|
||||
return range
|
||||
}
|
||||
}
|
||||
|
||||
unscannedMissedBlocksRanges.append(contentsOf: missedBlocks)
|
||||
}
|
||||
|
||||
func markBlocksAsScanned(blockRanges: [Range<UInt64>]) {
|
||||
for range in blockRanges {
|
||||
unscannedMissedBlocksRanges.removeAll(where: { $0 == range })
|
||||
}
|
||||
}
|
||||
|
||||
private static func decryptSearchResult(
|
||||
_ searchResult: FogView_TxOutSearchResult,
|
||||
accountKey: AccountKey
|
||||
) -> Result<FogView_TxOutRecord, ConnectionError> {
|
||||
FogViewUtils.decryptTxOutRecord(
|
||||
ciphertext: searchResult.ciphertext,
|
||||
accountKey: accountKey
|
||||
).mapError { error in
|
||||
switch error {
|
||||
case .invalidInput:
|
||||
let errorMessage = "Could not decrypt TxOut returned from Fog View, ciphertext: " +
|
||||
"\(redacting: searchResult.ciphertext.base64EncodedString()), error: " +
|
||||
"\(error)"
|
||||
logger.error(errorMessage)
|
||||
return .invalidServerResponse(errorMessage)
|
||||
case .unsupportedVersion:
|
||||
let errorMessage = "Could not decrypt TxOut returned from Fog View, ciphertext: " +
|
||||
"\(redacting: searchResult.ciphertext.base64EncodedString()), error: " +
|
||||
"\(error)"
|
||||
logger.error(errorMessage)
|
||||
return .outdatedClient(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Filters out TxOuts that don't belong to this account.
|
||||
private static func ownedTxOuts(
|
||||
validating txOuts: [LedgerTxOut],
|
||||
accountKey: AccountKey
|
||||
) -> [KnownTxOut] {
|
||||
let ownedTxOuts = txOuts.compactMap { txOut -> KnownTxOut? in
|
||||
guard let knownTxOut = txOut.decrypt(accountKey: accountKey) else {
|
||||
logger.warning(
|
||||
"TxOut received from Fog View is not owned by this account. txOut: " +
|
||||
"\(redacting: txOut.targetKey.data.hexEncodedString())",
|
||||
logFunction: false)
|
||||
return nil
|
||||
}
|
||||
return knownTxOut
|
||||
}
|
||||
return ownedTxOuts
|
||||
}
|
||||
}
|
||||
|
||||
struct FogSearchAttempt {
|
||||
fileprivate let rngSetSearchAttempt: FogRngSetSearchAttempt
|
||||
fileprivate let targetBlockCount: UInt64?
|
||||
|
||||
var searchKeys: [FogSearchKey] { rngSetSearchAttempt.searchKeys }
|
||||
}
|
||||
|
||||
extension LedgerTxOut {
|
||||
fileprivate static func make(txOutRecord: FogView_TxOutRecord, viewKey: RistrettoPrivate)
|
||||
-> Result<LedgerTxOut, ConnectionError>
|
||||
{
|
||||
guard let ledgerTxOut = LedgerTxOut(txOutRecord, viewKey: viewKey) else {
|
||||
let errorMessage = "Invalid TxOut returned from Fog View. TxOutRecord: " +
|
||||
"\(redacting: txOutRecord.serializedDataInfallible.base64EncodedString())"
|
||||
logger.error(errorMessage)
|
||||
return .failure(.invalidServerResponse(errorMessage))
|
||||
}
|
||||
return .success(ledgerTxOut)
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum FogViewUtils {
|
||||
static func encryptTxOutRecord(
|
||||
txOutRecord: FogView_TxOutRecord,
|
||||
publicAddress: PublicAddress,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?,
|
||||
rngContext: Any?
|
||||
) -> Result<Data, InvalidInputError> {
|
||||
VersionedCryptoBox.encrypt(
|
||||
plaintext: txOutRecord.serializedDataInfallible,
|
||||
publicKey: publicAddress.viewPublicKeyTyped,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
}
|
||||
|
||||
static func decryptTxOutRecord(
|
||||
ciphertext: Data,
|
||||
accountKey: AccountKey
|
||||
) -> Result<FogView_TxOutRecord, VersionedCryptoBoxError> {
|
||||
VersionedCryptoBox.decrypt(
|
||||
ciphertext: ciphertext,
|
||||
privateKey: accountKey.subaddressViewPrivateKey
|
||||
).flatMap { decrypted in
|
||||
guard let txOutRecord = try? FogView_TxOutRecord(serializedData: decrypted) else {
|
||||
return .failure(.invalidInput("FogView_TxOutRecord deserialization failed. " +
|
||||
"serializedData: \(redacting: decrypted.base64EncodedString())"))
|
||||
}
|
||||
return .success(txOutRecord)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct BlockMetadata {
|
||||
public let index: UInt64
|
||||
|
||||
let timestampStatus: TimestampStatus?
|
||||
public var timestamp: Date? {
|
||||
switch timestampStatus {
|
||||
case .known(timestamp: let timestamp):
|
||||
return timestamp
|
||||
case .none, .unavailable, .temporarilyUnknown:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
init(index: UInt64, timestamp: Date?) {
|
||||
let timestampStatus: TimestampStatus?
|
||||
if let timestamp = timestamp {
|
||||
timestampStatus = .known(timestamp: timestamp)
|
||||
} else {
|
||||
timestampStatus = nil
|
||||
}
|
||||
self.init(index: index, timestampStatus: timestampStatus)
|
||||
}
|
||||
|
||||
init(index: UInt64, timestampStatus: TimestampStatus?) {
|
||||
self.index = index
|
||||
self.timestampStatus = timestampStatus
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockMetadata: Equatable {}
|
||||
extension BlockMetadata: Hashable {}
|
||||
|
||||
extension BlockMetadata {
|
||||
enum TimestampStatus {
|
||||
case known(timestamp: Date)
|
||||
case unavailable
|
||||
case temporarilyUnknown
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockMetadata.TimestampStatus: Equatable {}
|
||||
extension BlockMetadata.TimestampStatus: Hashable {}
|
||||
@ -1,69 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct KeyImage {
|
||||
let data32: Data32
|
||||
|
||||
init(_ data: Data32) {
|
||||
self.data32 = data
|
||||
}
|
||||
|
||||
enum SpentStatus {
|
||||
case spent(block: BlockMetadata)
|
||||
case unspent(knownToBeUnspentBlockCount: UInt64)
|
||||
|
||||
var nextKeyImageQueryBlockIndex: UInt64 {
|
||||
switch self {
|
||||
case .spent:
|
||||
return 0
|
||||
case .unspent(let knownToBeUnspentBlockCount):
|
||||
return knownToBeUnspentBlockCount
|
||||
}
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when `blockCount` exceeds our knowledge about the spent status.
|
||||
func status(atBlockCount blockCount: UInt64) -> SpentStatus? {
|
||||
switch self {
|
||||
case .spent(block: let spentAtBlock):
|
||||
guard spentAtBlock.index < blockCount else {
|
||||
return nil
|
||||
}
|
||||
return .spent(block: spentAtBlock)
|
||||
case .unspent(knownToBeUnspentBlockCount: let knownToBeUnspentBlockCount):
|
||||
guard knownToBeUnspentBlockCount >= blockCount else {
|
||||
return nil
|
||||
}
|
||||
return .unspent(knownToBeUnspentBlockCount: blockCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyImage: DataConvertibleImpl {
|
||||
typealias Iterator = Data.Iterator
|
||||
|
||||
init?(_ data: Data) {
|
||||
guard let data32 = Data32(data.data) else {
|
||||
return nil
|
||||
}
|
||||
self.init(data32)
|
||||
}
|
||||
|
||||
var data: Data { data32.data }
|
||||
}
|
||||
|
||||
extension KeyImage {
|
||||
init?(_ keyImage: External_KeyImage) {
|
||||
self.init(keyImage.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension External_KeyImage {
|
||||
init(_ keyImage: KeyImage) {
|
||||
self.init(keyImage.data32)
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class KeyImageSpentTracker {
|
||||
let keyImage: KeyImage
|
||||
|
||||
var spentStatus: KeyImage.SpentStatus
|
||||
|
||||
init(_ keyImage: KeyImage) {
|
||||
self.keyImage = keyImage
|
||||
self.spentStatus = .unspent(knownToBeUnspentBlockCount: 0)
|
||||
}
|
||||
|
||||
var isSpent: Bool {
|
||||
if case .spent = spentStatus {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var nextKeyImageQueryBlockIndex: UInt64 {
|
||||
spentStatus.nextKeyImageQueryBlockIndex
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct KnownTxOut: TxOutProtocol {
|
||||
private let ledgerTxOut: LedgerTxOut
|
||||
let value: UInt64
|
||||
let keyImage: KeyImage
|
||||
|
||||
init?(_ ledgerTxOut: LedgerTxOut, accountKey: AccountKey) {
|
||||
|
||||
guard let value = ledgerTxOut.value(accountKey: accountKey),
|
||||
let keyImage = ledgerTxOut.keyImage(accountKey: accountKey),
|
||||
let commitment = TxOutUtils.reconstructCommitment(
|
||||
maskedValue: ledgerTxOut.maskedValue,
|
||||
publicKey: ledgerTxOut.publicKey,
|
||||
viewPrivateKey:accountKey.viewPrivateKey)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.commitment = commitment
|
||||
self.ledgerTxOut = ledgerTxOut
|
||||
self.value = value
|
||||
self.keyImage = keyImage
|
||||
}
|
||||
|
||||
var commitment: Data32
|
||||
var maskedValue: UInt64 { ledgerTxOut.maskedValue }
|
||||
var targetKey: RistrettoPublic { ledgerTxOut.targetKey }
|
||||
var publicKey: RistrettoPublic { ledgerTxOut.publicKey }
|
||||
var block: BlockMetadata { ledgerTxOut.block }
|
||||
var globalIndex: UInt64 { ledgerTxOut.globalIndex }
|
||||
}
|
||||
|
||||
extension KnownTxOut: Equatable {}
|
||||
extension KnownTxOut: Hashable {}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct LedgerTxOut: TxOutProtocol {
|
||||
private let txOut: PartialTxOut
|
||||
let globalIndex: UInt64
|
||||
let block: BlockMetadata
|
||||
|
||||
init(_ txOut: PartialTxOut, globalIndex: UInt64, block: BlockMetadata) {
|
||||
self.txOut = txOut
|
||||
self.globalIndex = globalIndex
|
||||
self.block = block
|
||||
}
|
||||
|
||||
var commitment: Data32 { txOut.commitment }
|
||||
var maskedValue: UInt64 { txOut.maskedValue }
|
||||
var targetKey: RistrettoPublic { txOut.targetKey }
|
||||
var publicKey: RistrettoPublic { txOut.publicKey }
|
||||
|
||||
func decrypt(accountKey: AccountKey) -> KnownTxOut? {
|
||||
KnownTxOut(self, accountKey: accountKey)
|
||||
}
|
||||
}
|
||||
|
||||
extension LedgerTxOut: Equatable {}
|
||||
extension LedgerTxOut: Hashable {}
|
||||
|
||||
extension LedgerTxOut {
|
||||
init?(_ txOutRecord: FogView_TxOutRecord, viewKey: RistrettoPrivate) {
|
||||
guard let partialTxOut = PartialTxOut(txOutRecord, viewKey: viewKey) else {
|
||||
return nil
|
||||
}
|
||||
let globalIndex = txOutRecord.txOutGlobalIndex
|
||||
let block = BlockMetadata(
|
||||
index: txOutRecord.blockIndex,
|
||||
timestamp: txOutRecord.timestampDate)
|
||||
self.init(partialTxOut, globalIndex: globalIndex, block: block)
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct PartialTxOut: TxOutProtocol {
|
||||
let commitment: Data32
|
||||
let maskedValue: UInt64
|
||||
let targetKey: RistrettoPublic
|
||||
let publicKey: RistrettoPublic
|
||||
}
|
||||
|
||||
extension PartialTxOut: Equatable {}
|
||||
extension PartialTxOut: Hashable {}
|
||||
|
||||
extension PartialTxOut {
|
||||
init(_ txOut: TxOut) {
|
||||
self.init(
|
||||
commitment: txOut.commitment,
|
||||
maskedValue: txOut.maskedValue,
|
||||
targetKey: txOut.targetKey,
|
||||
publicKey: txOut.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
extension PartialTxOut {
|
||||
init?(_ txOut: External_TxOut) {
|
||||
guard let commitment = Data32(txOut.amount.commitment.data),
|
||||
let targetKey = RistrettoPublic(txOut.targetKey.data),
|
||||
let publicKey = RistrettoPublic(txOut.publicKey.data)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self.init(
|
||||
commitment: commitment,
|
||||
maskedValue: txOut.amount.maskedValue,
|
||||
targetKey: targetKey,
|
||||
publicKey: publicKey)
|
||||
}
|
||||
|
||||
init?(_ txOutRecord: FogView_TxOutRecord, viewKey: RistrettoPrivate) {
|
||||
guard let targetKey = RistrettoPublic(txOutRecord.txOutTargetKeyData),
|
||||
let publicKey = RistrettoPublic(txOutRecord.txOutPublicKeyData),
|
||||
let commitment = TxOutUtils.reconstructCommitment(
|
||||
maskedValue: txOutRecord.txOutAmountMaskedValue,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: viewKey)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
commitment: commitment,
|
||||
maskedValue: txOutRecord.txOutAmountMaskedValue,
|
||||
targetKey: targetKey,
|
||||
publicKey: publicKey)
|
||||
}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct TxOut: TxOutProtocol {
|
||||
fileprivate let proto: External_TxOut
|
||||
|
||||
let commitment: Data32
|
||||
let targetKey: RistrettoPublic
|
||||
let publicKey: RistrettoPublic
|
||||
|
||||
/// - Returns: `nil` when the input is not deserializable.
|
||||
init?(serializedData: Data) {
|
||||
guard let proto = try? External_TxOut(serializedData: serializedData) else {
|
||||
logger.warning(
|
||||
"External_TxOut deserialization failed. serializedData: " +
|
||||
"\(redacting: serializedData.base64EncodedString())",
|
||||
logFunction: false)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch TxOut.make(proto) {
|
||||
case .success(let txOut):
|
||||
self = txOut
|
||||
case .failure(let error):
|
||||
logger.warning(
|
||||
"External_TxOut deserialization failed. serializedData: " +
|
||||
"\(redacting: serializedData.base64EncodedString()), error: \(error)",
|
||||
logFunction: false)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var serializedData: Data {
|
||||
proto.serializedDataInfallible
|
||||
}
|
||||
|
||||
var maskedValue: UInt64 { proto.amount.maskedValue }
|
||||
var encryptedFogHint: Data { proto.eFogHint.data }
|
||||
}
|
||||
|
||||
extension TxOut: Equatable {}
|
||||
extension TxOut: Hashable {}
|
||||
|
||||
extension TxOut {
|
||||
static func make(_ proto: External_TxOut) -> Result<TxOut, InvalidInputError> {
|
||||
guard let commitment = Data32(proto.amount.commitment.data) else {
|
||||
return .failure(
|
||||
InvalidInputError("Failed parsing External_TxOut: invalid commitment format"))
|
||||
}
|
||||
guard let targetKey = RistrettoPublic(proto.targetKey.data) else {
|
||||
return .failure(
|
||||
InvalidInputError("Failed parsing External_TxOut: invalid target key format"))
|
||||
}
|
||||
guard let publicKey = RistrettoPublic(proto.publicKey.data) else {
|
||||
return .failure(
|
||||
InvalidInputError("Failed parsing External_TxOut: invalid public key format"))
|
||||
}
|
||||
return .success(
|
||||
TxOut(proto: proto, commitment: commitment, targetKey: targetKey, publicKey: publicKey))
|
||||
}
|
||||
|
||||
private init(
|
||||
proto: External_TxOut,
|
||||
commitment: Data32,
|
||||
targetKey: RistrettoPublic,
|
||||
publicKey: RistrettoPublic
|
||||
) {
|
||||
self.proto = proto
|
||||
self.commitment = commitment
|
||||
self.targetKey = targetKey
|
||||
self.publicKey = publicKey
|
||||
}
|
||||
}
|
||||
|
||||
extension External_TxOut {
|
||||
init(_ txOut: TxOut) {
|
||||
self = txOut.proto
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct TxOutMembershipProof {
|
||||
let serializedData: Data
|
||||
|
||||
static func make(serializedData: Data) -> Result<TxOutMembershipProof, InvalidInputError> {
|
||||
.success(TxOutMembershipProof(serializedData: serializedData))
|
||||
}
|
||||
|
||||
private init(serializedData: Data) {
|
||||
self.serializedData = serializedData
|
||||
}
|
||||
}
|
||||
|
||||
extension TxOutMembershipProof: Equatable {}
|
||||
extension TxOutMembershipProof: Hashable {}
|
||||
|
||||
extension TxOutMembershipProof {
|
||||
static func make(_ txOutMembershipProof: External_TxOutMembershipProof)
|
||||
-> Result<TxOutMembershipProof, InvalidInputError>
|
||||
{
|
||||
let serializedData = txOutMembershipProof.serializedDataInfallible
|
||||
return TxOutMembershipProof.make(serializedData: serializedData)
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
protocol TxOutProtocol {
|
||||
var commitment: Data32 { get }
|
||||
var maskedValue: UInt64 { get }
|
||||
var targetKey: RistrettoPublic { get }
|
||||
var publicKey: RistrettoPublic { get }
|
||||
}
|
||||
|
||||
extension TxOutProtocol {
|
||||
func matches(accountKey: AccountKey) -> Bool {
|
||||
TxOutUtils.matchesSubaddress(
|
||||
targetKey: targetKey,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: accountKey.viewPrivateKey,
|
||||
subaddressSpendPrivateKey: accountKey.subaddressSpendPrivateKey)
|
||||
}
|
||||
|
||||
func matchesAnySubaddress(accountKey: AccountKey) -> Bool {
|
||||
TxOutUtils.matchesAnySubaddress(
|
||||
maskedValue: maskedValue,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: accountKey.viewPrivateKey)
|
||||
}
|
||||
|
||||
func subaddressSpentPublicKey(viewPrivateKey: RistrettoPrivate) -> RistrettoPublic {
|
||||
TxOutUtils.subaddressSpentPublicKey(
|
||||
targetKey: targetKey,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: viewPrivateKey)
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when `accountKey` cannot unmask value, either because `accountKey` does not
|
||||
/// own `TxOut` or because ` TxOut` values are incongruent.
|
||||
func value(accountKey: AccountKey) -> UInt64? {
|
||||
TxOutUtils.value(
|
||||
maskedValue: maskedValue,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: accountKey.viewPrivateKey)
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when a valid `KeyImage` cannot be constructed, either because `accountKey`
|
||||
/// does not own `TxOut` or because `TxOut` values are incongruent.
|
||||
func keyImage(accountKey: AccountKey) -> KeyImage? {
|
||||
TxOutUtils.keyImage(
|
||||
targetKey: targetKey,
|
||||
publicKey: publicKey,
|
||||
viewPrivateKey: accountKey.viewPrivateKey,
|
||||
subaddressSpendPrivateKey: accountKey.subaddressSpendPrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_TxOutRecord {
|
||||
init(_ txOut: TxOutProtocol) {
|
||||
self.init()
|
||||
self.txOutAmountCommitmentData = txOut.commitment.data
|
||||
self.txOutAmountMaskedValue = txOut.maskedValue
|
||||
self.txOutTargetKeyData = txOut.targetKey.data
|
||||
self.txOutPublicKeyData = txOut.publicKey.data
|
||||
}
|
||||
}
|
||||
@ -1,259 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum TxOutUtils {
|
||||
static func matchesAnySubaddress(
|
||||
maskedValue: UInt64,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate
|
||||
) -> Bool {
|
||||
var mcAmount = McTxOutAmount(masked_value: maskedValue)
|
||||
return publicKey.asMcBuffer { publicKeyPtr in
|
||||
viewPrivateKey.asMcBuffer { viewPrivateKeyPtr in
|
||||
var matches = false
|
||||
// Safety: mc_tx_out_matches_any_subaddress is infallible when preconditions are
|
||||
// upheld.
|
||||
withMcInfallible {
|
||||
mc_tx_out_matches_any_subaddress(
|
||||
&mcAmount,
|
||||
publicKeyPtr,
|
||||
viewPrivateKeyPtr,
|
||||
&matches)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func matchesSubaddress(
|
||||
targetKey: RistrettoPublic,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
subaddressSpendPrivateKey: RistrettoPrivate
|
||||
) -> Bool {
|
||||
targetKey.asMcBuffer { targetKeyBufferPtr in
|
||||
publicKey.asMcBuffer { publicKeyBufferPtr in
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
subaddressSpendPrivateKey.asMcBuffer { spendPrivateKeyBufferPtr in
|
||||
var matches = false
|
||||
// Safety: mc_tx_out_matches_subaddress is infallible when preconditions are
|
||||
// upheld.
|
||||
withMcInfallible {
|
||||
mc_tx_out_matches_subaddress(
|
||||
targetKeyBufferPtr,
|
||||
publicKeyBufferPtr,
|
||||
viewKeyBufferPtr,
|
||||
spendPrivateKeyBufferPtr,
|
||||
&matches)
|
||||
}
|
||||
return matches
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func reconstructCommitment(
|
||||
maskedValue: UInt64,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate
|
||||
) -> Data32? {
|
||||
var mcAmount = McTxOutAmount(masked_value: maskedValue)
|
||||
return publicKey.asMcBuffer { publicKeyBufferPtr in
|
||||
viewPrivateKey.asMcBuffer { viewPrivateKeyPtr in
|
||||
switch Data32.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_tx_out_reconstruct_commitment(
|
||||
&mcAmount,
|
||||
publicKeyBufferPtr,
|
||||
viewPrivateKeyPtr,
|
||||
bufferPtr,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success(let bytes):
|
||||
// Safety: It's safe to skip validation because
|
||||
// mc_tx_out_get_subaddress_spend_public_key should always return a valid
|
||||
// RistrettoPublic on success.
|
||||
return bytes as Data32
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .invalidInput:
|
||||
// Safety: This condition indicates a programming error and can only
|
||||
// happen if arguments to mc_tx_out_get_subaddress_spend_public_key are
|
||||
// supplied incorrectly.
|
||||
// FIXME
|
||||
logger.warning("error: \(redacting: error)")
|
||||
return nil
|
||||
default:
|
||||
// Safety: mc_fog_resolver_add_report_response should not throw
|
||||
// non-documented errors.
|
||||
// FIXME
|
||||
logger.warning("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func subaddressSpentPublicKey(
|
||||
targetKey: RistrettoPublic,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate
|
||||
) -> RistrettoPublic {
|
||||
targetKey.asMcBuffer { targetKeyBufferPtr in
|
||||
publicKey.asMcBuffer { publicKeyBufferPtr in
|
||||
viewPrivateKey.asMcBuffer { viewPrivateKeyPtr in
|
||||
switch Data32.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_tx_out_get_subaddress_spend_public_key(
|
||||
targetKeyBufferPtr,
|
||||
publicKeyBufferPtr,
|
||||
viewPrivateKeyPtr,
|
||||
bufferPtr,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success(let bytes):
|
||||
// Safety: It's safe to skip validation because
|
||||
// mc_tx_out_get_subaddress_spend_public_key should always return a valid
|
||||
// RistrettoPublic on success.
|
||||
return RistrettoPublic(skippingValidation: bytes)
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .invalidInput:
|
||||
// Safety: This condition indicates a programming error and can only
|
||||
// happen if arguments to mc_tx_out_get_subaddress_spend_public_key are
|
||||
// supplied incorrectly.
|
||||
logger.fatalError("error: \(redacting: error)")
|
||||
default:
|
||||
// Safety: mc_fog_resolver_add_report_response should not throw
|
||||
// non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when `viewPrivateKey` cannot unmask value, either because `viewPrivateKey`
|
||||
/// does not own `TxOut` or because `TxOut` values are incongruent.
|
||||
static func value(
|
||||
maskedValue: UInt64,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate
|
||||
) -> UInt64? {
|
||||
var mcAmount = McTxOutAmount(masked_value: maskedValue)
|
||||
return publicKey.asMcBuffer { publicKeyPtr in
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
var valueOut: UInt64 = 0
|
||||
switch withMcError({ errorPtr in
|
||||
mc_tx_out_get_value(
|
||||
&mcAmount,
|
||||
publicKeyPtr,
|
||||
viewKeyBufferPtr,
|
||||
&valueOut,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success:
|
||||
return valueOut
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .transactionCrypto:
|
||||
// Indicates either `commitment`/`maskedValue`/`publicKey` values are
|
||||
// incongruent or `viewPrivateKey` does not own `TxOut`. However, it's
|
||||
// not possible to determine which, only that the provided `commitment`
|
||||
// doesn't match the computed commitment.
|
||||
return nil
|
||||
case .invalidInput:
|
||||
// Safety: This condition indicates a programming error and can only
|
||||
// happen if arguments to mc_tx_out_get_value are supplied incorrectly.
|
||||
logger.fatalError("error: \(redacting: error)")
|
||||
default:
|
||||
// Safety: mc_tx_out_get_value should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// - Returns: `nil` when a valid `KeyImage` cannot be constructed, either because
|
||||
/// `viewPrivateKey`/`subaddressSpendPrivateKey` do not own `TxOut` or because `TxOut`
|
||||
/// values are incongruent.
|
||||
static func keyImage(
|
||||
targetKey: RistrettoPublic,
|
||||
publicKey: RistrettoPublic,
|
||||
viewPrivateKey: RistrettoPrivate,
|
||||
subaddressSpendPrivateKey: RistrettoPrivate
|
||||
) -> KeyImage? {
|
||||
targetKey.asMcBuffer { targetKeyPtr in
|
||||
publicKey.asMcBuffer { publicKeyPtr in
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
subaddressSpendPrivateKey.asMcBuffer { spendKeyBufferPtr in
|
||||
switch Data32.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_tx_out_get_key_image(
|
||||
targetKeyPtr,
|
||||
publicKeyPtr,
|
||||
viewKeyBufferPtr,
|
||||
spendKeyBufferPtr,
|
||||
bufferPtr,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success(let keyImageData):
|
||||
return KeyImage(keyImageData)
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .transactionCrypto:
|
||||
// Indicates either `targetKey`/`publicKey` values are incongruent
|
||||
// or`viewPrivateKey`/`subaddressSpendPrivateKey` does not own
|
||||
// `TxOut`. However, it's not possible to determine which, only that
|
||||
// the provided `targetKey` doesn't match the computed target key
|
||||
// (aka onetime public key).
|
||||
return nil
|
||||
case .invalidInput:
|
||||
// Safety: This condition indicates a programming error and can only
|
||||
// happen if arguments to mc_tx_out_get_key_image are supplied
|
||||
// incorrectly.
|
||||
logger.fatalError("error: \(redacting: error)")
|
||||
default:
|
||||
// Safety: mc_tx_out_get_key_image should not throw non-documented
|
||||
// errors.
|
||||
logger.fatalError(
|
||||
"Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func validateConfirmationNumber(
|
||||
publicKey: RistrettoPublic,
|
||||
confirmationNumber: TxOutConfirmationNumber,
|
||||
viewPrivateKey: RistrettoPrivate
|
||||
) -> Bool {
|
||||
publicKey.asMcBuffer { publicKeyPtr in
|
||||
confirmationNumber.asMcBuffer { confirmationNumberPtr in
|
||||
viewPrivateKey.asMcBuffer { viewKeyBufferPtr in
|
||||
var result = false
|
||||
// Safety: mc_tx_out_validate_confirmation_number is infallible when
|
||||
// preconditions are upheld.
|
||||
withMcInfallible {
|
||||
mc_tx_out_validate_confirmation_number(
|
||||
publicKeyPtr,
|
||||
confirmationNumberPtr,
|
||||
viewKeyBufferPtr,
|
||||
&result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol CStructWrapper {
|
||||
associatedtype CStruct
|
||||
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<CStruct>) throws -> R
|
||||
) rethrows -> R
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: CStructWrapper {
|
||||
func withUnsafeCStructPointer<R>(
|
||||
_ body: (UnsafePointer<Wrapped.CStruct>?) throws -> R
|
||||
) rethrows -> R {
|
||||
if let unwrapped = self {
|
||||
return try unwrapped.withUnsafeCStructPointer { ptr in
|
||||
try body(ptr)
|
||||
}
|
||||
} else {
|
||||
return try body(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable colon multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension Data {
|
||||
static func make(
|
||||
withMcMutableBuffer body:
|
||||
(UnsafeMutablePointer<McMutableBuffer>?, inout UnsafeMutablePointer<McError>?) -> Int
|
||||
) -> Result<Data, LibMobileCoinError> {
|
||||
withMcErrorReturningArrayCount { errorPtr in
|
||||
// Call body() with nil to get number of bytes.
|
||||
body(nil, &errorPtr)
|
||||
}.flatMap { numBytes in
|
||||
// Call body() again with a pointer to the output buffer.
|
||||
var bytes = Data(repeating: 0, count: numBytes)
|
||||
return bytes.asMcMutableBuffer { bufferPtr in
|
||||
withMcErrorReturningArrayCount { errorPtr in
|
||||
body(bufferPtr, &errorPtr)
|
||||
}
|
||||
}.map { numBytesWritten in
|
||||
guard numBytesWritten <= numBytes else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"numBytesWritten (\(numBytesWritten)) must be <= numBytes (\(numBytes))")
|
||||
}
|
||||
|
||||
return bytes.prefix(numBytesWritten)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func make(
|
||||
withFixedLengthMcMutableBuffer numBytes: Int,
|
||||
body: (UnsafeMutablePointer<McMutableBuffer>, inout UnsafeMutablePointer<McError>?) -> Bool
|
||||
) -> Result<Data, LibMobileCoinError> {
|
||||
var bytes = Data(repeating: 0, count: numBytes)
|
||||
return bytes.asMcMutableBuffer { bufferPtr in
|
||||
withMcError { errorPtr in
|
||||
body(bufferPtr, &errorPtr)
|
||||
}
|
||||
}.map { bytes }
|
||||
}
|
||||
|
||||
static func make(
|
||||
withEstimatedLengthMcMutableBuffer numBytes: Int,
|
||||
body: (UnsafeMutablePointer<McMutableBuffer>, inout UnsafeMutablePointer<McError>?) -> Int
|
||||
) -> Result<Data, LibMobileCoinError> {
|
||||
var bytes = Data(repeating: 0, count: numBytes)
|
||||
return bytes.asMcMutableBuffer { bufferPtr in
|
||||
withMcErrorReturningArrayCount { errorPtr in
|
||||
body(bufferPtr, &errorPtr)
|
||||
}
|
||||
}.map { numBytesReturned in
|
||||
guard numBytesReturned <= numBytes else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"Number of bytes returned from LibMobileCoin (\(numBytesReturned)) is " +
|
||||
"greater than estimated (\(numBytes))")
|
||||
}
|
||||
|
||||
return bytes.prefix(numBytesReturned)
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
withFixedLengthMcMutableBufferInfallible numBytes: Int,
|
||||
body: (UnsafeMutablePointer<McMutableBuffer>) -> Bool
|
||||
) {
|
||||
self.init(repeating: 0, count: numBytes)
|
||||
let success = asMcMutableBuffer { bufferPtr in
|
||||
body(bufferPtr)
|
||||
}
|
||||
guard success else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError("Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
}
|
||||
|
||||
init(withMcMutableBufferInfallible body: (UnsafeMutablePointer<McMutableBuffer>?) -> Int) {
|
||||
// Call body() with nil to get number of bytes.
|
||||
let numBytes = body(nil)
|
||||
guard numBytes >= 0 else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError("Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
|
||||
var bytes = Data(repeating: 0, count: numBytes)
|
||||
let numBytesWritten = bytes.asMcMutableBuffer { bufferPtr in
|
||||
body(bufferPtr)
|
||||
}
|
||||
guard numBytesWritten >= 0 else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError("Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
|
||||
guard numBytesWritten <= numBytes else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"numBytesWritten (\(numBytesWritten)) must be <= numBytes (\(numBytes))")
|
||||
}
|
||||
|
||||
self = bytes.prefix(numBytesWritten)
|
||||
}
|
||||
|
||||
init(
|
||||
withEstimatedLengthMcMutableBufferInfallible numBytes: Int,
|
||||
body: (UnsafeMutablePointer<McMutableBuffer>) -> Int
|
||||
) {
|
||||
var bytes = Data(repeating: 0, count: numBytes)
|
||||
let numBytesReturned = bytes.asMcMutableBuffer(body)
|
||||
guard numBytesReturned <= numBytes else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"Number of bytes returned from LibMobileCoin \(numBytesReturned) is greater than " +
|
||||
"estimated \(numBytes)")
|
||||
}
|
||||
|
||||
self = bytes.prefix(numBytesReturned)
|
||||
}
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable colon multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension Data32 {
|
||||
static func make(
|
||||
withMcMutableBuffer body:
|
||||
(UnsafeMutablePointer<McMutableBuffer>, inout UnsafeMutablePointer<McError>?) -> Bool
|
||||
) -> Result<Data32, LibMobileCoinError> {
|
||||
var bytes = Data32()
|
||||
return bytes.asMcMutableBuffer { bufferPtr in
|
||||
withMcError { errorPtr in
|
||||
body(bufferPtr, &errorPtr)
|
||||
}
|
||||
}.map { bytes }
|
||||
}
|
||||
|
||||
static func make(
|
||||
withMcMutableBuffer body:
|
||||
(UnsafeMutablePointer<McMutableBuffer>, inout UnsafeMutablePointer<McError>?) -> Int
|
||||
) -> Result<Data32, LibMobileCoinError> {
|
||||
var bytes = Data32()
|
||||
return bytes.asMcMutableBuffer { bufferPtr in
|
||||
withMcErrorReturningArrayCount { errorPtr in
|
||||
body(bufferPtr, &errorPtr)
|
||||
}
|
||||
}.map { numBytesReturned in
|
||||
guard numBytesReturned == 32 else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"LibMobileCoin function returned unexpected byte count " +
|
||||
"(\(numBytesReturned)). Expected 32.")
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
init(withMcMutableBufferInfallible body: (UnsafeMutablePointer<McMutableBuffer>) -> Bool) {
|
||||
self.init()
|
||||
asMcMutableBuffer { bufferPtr in
|
||||
guard body(bufferPtr) else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError("Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(withMcMutableBufferInfallible body: (UnsafeMutablePointer<McMutableBuffer>) -> Int) {
|
||||
self.init()
|
||||
let numBytesReturned = asMcMutableBuffer { bufferPtr in
|
||||
body(bufferPtr)
|
||||
}
|
||||
guard numBytesReturned > 0 else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError("Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
guard numBytesReturned == 32 else {
|
||||
// This condition indicates a programming error.
|
||||
logger.fatalError(
|
||||
"LibMobileCoin function returned unexpected byte count (\(numBytesReturned)). " +
|
||||
"Expected 32.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
struct LibMobileCoinError: Error {
|
||||
static func make(consuming error: UnsafeMutablePointer<McError>)
|
||||
-> Result<LibMobileCoinError, InvalidInputError>
|
||||
{
|
||||
defer {
|
||||
mc_error_free(error)
|
||||
}
|
||||
guard let libMcError = LibMobileCoinError(error.pointee) else {
|
||||
return .failure(InvalidInputError(
|
||||
"Unknown LibMobileCoin error code: \(error.pointee.error_code), description: " +
|
||||
"\(String(cString: error.pointee.error_description))"))
|
||||
}
|
||||
return .success(libMcError)
|
||||
}
|
||||
|
||||
let errorCode: McErrorCode
|
||||
let description: String
|
||||
|
||||
/// - Returns: `nil` when the error kind is unrecognized.
|
||||
init?(_ error: McError) {
|
||||
self.description = String(cString: error.error_description)
|
||||
|
||||
guard let errorCode = McErrorCode(rawValue: error.error_code) else {
|
||||
return nil
|
||||
}
|
||||
self.errorCode = errorCode
|
||||
}
|
||||
}
|
||||
@ -1,204 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum McConstants {}
|
||||
|
||||
// MARK: - Transaction
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Each input ring must contain this many elements.
|
||||
static let RING_SIZE = 11
|
||||
|
||||
/// Each transaction must contain no more than this many inputs (rings).
|
||||
static let MAX_INPUTS = 16
|
||||
|
||||
/// Each transaction must contain no more than this many outputs.
|
||||
static let MAX_OUTPUTS = 16
|
||||
|
||||
/// Maximum number of blocks in the future a transaction's tombstone block can be set to.
|
||||
static let MAX_TOMBSTONE_BLOCKS: UInt64 = 100
|
||||
|
||||
/// Minimum allowed fee, denominated in picoMOB.
|
||||
static let DEFAULT_MINIMUM_FEE: UInt64 = 10_000_000_000
|
||||
|
||||
/// Transaction hash length, in bytes.
|
||||
static let TX_HASH_LEN = 32
|
||||
|
||||
/// Length of a Transaction's encrypted fog hint field, in bytes.
|
||||
static let ENCRYPTED_FOG_HINT_LEN = 128
|
||||
|
||||
/// Length of a Transaction confirmation number, in bytes.
|
||||
static let CONFIRMATION_NUMBER_LEN = 32
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Block
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Maximum number of transactions that may be included in a Block.
|
||||
static let MAX_TRANSACTIONS_PER_BLOCK = 5000
|
||||
|
||||
}
|
||||
|
||||
// MARK: - TxOut
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Length of a TxOut key image, in bytes.
|
||||
static let KEY_IMAGE_LEN = 32
|
||||
|
||||
}
|
||||
|
||||
// MARK: - MOB
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// The MobileCoin network will contain a fixed supply of 250 million mobilecoins (MOB).
|
||||
static let TOTAL_MOB: UInt64 = 250_000_000
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Account key
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Length of the root entropy used to construct an account key, in bytes.
|
||||
static let ROOT_ENTROPY_LEN = 32
|
||||
|
||||
/// An account's "default address" is its zero^th subaddress.
|
||||
static let DEFAULT_SUBADDRESS_INDEX: UInt64 = 0
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Keys
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Ristretto private key length, in bytes.
|
||||
static let RISTRETTO_PRIVATE_LEN = 32
|
||||
|
||||
/// Ristretto public key length, in bytes.
|
||||
static let RISTRETTO_PUBLIC_LEN = 32
|
||||
|
||||
/// The length of a curve25519 EdDSA `Signature`, in bytes.
|
||||
static let SCHNORRKEL_SIGNATURE_LEN = 64
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Attestation
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// MRENCLAVE length, in bytes.
|
||||
static let MRENCLAVE_LEN = 32
|
||||
|
||||
/// MRSIGNER length, in bytes.
|
||||
static let MRSIGNER_LEN = 32
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Enclave
|
||||
|
||||
extension McConstants {
|
||||
|
||||
static let CONSENSUS_PRODUCT_ID: UInt16 = 1
|
||||
static let FOG_VIEW_PRODUCT_ID: UInt16 = 3
|
||||
static let FOG_LEDGER_PRODUCT_ID: UInt16 = 2
|
||||
static let FOG_REPORT_PRODUCT_ID: UInt16 = 4
|
||||
|
||||
static let CONSENSUS_SECURITY_VERSION: UInt16 = 1
|
||||
static let DEV_CONSENSUS_MRSIGNER_HEX =
|
||||
"7ee5e29d74623fdbc6fbf1454be6f3bb0b86c12366b7b478ad13353e44de8411"
|
||||
static let DEV_CONSENSUS_MRSIGNER = Data([
|
||||
126, 229, 226, 157, 116, 98, 63, 219, 198, 251, 241, 69, 75, 230, 243, 187, 11, 134, 193,
|
||||
35, 102, 183, 180, 120, 173, 19, 53, 62, 68, 222, 132, 17,
|
||||
])
|
||||
static let TESTNET_CONSENSUS_MRSIGNER_HEX =
|
||||
"bf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c"
|
||||
static let TESTNET_CONSENSUS_MRSIGNER = Data([
|
||||
191, 127, 169, 87, 166, 169, 74, 203, 88, 136, 81, 188, 135, 103, 224, 202, 87, 112, 108,
|
||||
121, 244, 252, 42, 166, 188, 185, 147, 1, 44, 60, 56, 108,
|
||||
])
|
||||
|
||||
static let FOG_VIEW_SECURITY_VERSION: UInt16 = 1
|
||||
static let FOG_LEDGER_SECURITY_VERSION: UInt16 = 1
|
||||
static let DEV_FOG_MRSIGNER_HEX =
|
||||
"7ee5e29d74623fdbc6fbf1454be6f3bb0b86c12366b7b478ad13353e44de8411"
|
||||
static let DEV_FOG_MRSIGNER = Data([
|
||||
126, 229, 226, 157, 116, 98, 63, 219, 198, 251, 241, 69, 75, 230, 243, 187, 11, 134, 193,
|
||||
35, 102, 183, 180, 120, 173, 19, 53, 62, 68, 222, 132, 17,
|
||||
])
|
||||
static let TESTNET_FOG_MRSIGNER_HEX =
|
||||
"bf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c"
|
||||
static let TESTNET_FOG_MRSIGNER = Data([
|
||||
191, 127, 169, 87, 166, 169, 74, 203, 88, 136, 81, 188, 135, 103, 224, 202, 87, 112, 108,
|
||||
121, 244, 252, 42, 166, 188, 185, 147, 1, 44, 60, 56, 108,
|
||||
])
|
||||
|
||||
static let FOG_REPORT_SECURITY_VERSION: UInt16 = 1
|
||||
static let DEV_FOG_REPORT_MRSIGNER_HEX =
|
||||
"7ee5e29d74623fdbc6fbf1454be6f3bb0b86c12366b7b478ad13353e44de8411"
|
||||
static let DEV_FOG_REPORT_MRSIGNER = Data([
|
||||
126, 229, 226, 157, 116, 98, 63, 219, 198, 251, 241, 69, 75, 230, 243, 187, 11, 134, 193,
|
||||
35, 102, 183, 180, 120, 173, 19, 53, 62, 68, 222, 132, 17,
|
||||
])
|
||||
static let TESTNET_FOG_REPORT_MRSIGNER_HEX =
|
||||
"bf7fa957a6a94acb588851bc8767e0ca57706c79f4fc2aa6bcb993012c3c386c"
|
||||
static let TESTNET_FOG_REPORT_MRSIGNER = Data([
|
||||
191, 127, 169, 87, 166, 169, 74, 203, 88, 136, 81, 188, 135, 103, 224, 202, 87, 112, 108,
|
||||
121, 244, 252, 42, 166, 188, 185, 147, 1, 44, 60, 56, 108,
|
||||
])
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Url
|
||||
|
||||
extension McConstants {
|
||||
|
||||
static let MOB_URI_SCHEME = "mob"
|
||||
|
||||
/// The part before the '://' of a URL.
|
||||
static let CONSENSUS_SCHEME_SECURE = "mc"
|
||||
static let CONSENSUS_SCHEME_INSECURE = "insecure-mc"
|
||||
|
||||
/// Default port numbers
|
||||
static let CONSENSUS_DEFAULT_SECURE_PORT = 443
|
||||
static let CONSENSUS_DEFAULT_INSECURE_PORT = 3223
|
||||
|
||||
/// The part before the '://' of a URL.
|
||||
static let FOG_SCHEME_SECURE = "fog"
|
||||
static let FOG_SCHEME_INSECURE = "insecure-fog"
|
||||
|
||||
/// Default port numbers
|
||||
static let FOG_DEFAULT_SECURE_PORT = 443
|
||||
static let FOG_DEFAULT_INSECURE_PORT = 3225
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Fog Report
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Fog authority subjectPublicKeyInfo used in development, in hexidecimal.
|
||||
static let DEV_FOG_AUTHORITY_SPKI_HEX = "23e9dfabdaf74c69428ec0dfac15784eedc7466e"
|
||||
/// Fog authority subjectPublicKeyInfo used in development, in bytes.
|
||||
static let DEV_FOG_AUTHORITY_SPKI = Data([
|
||||
35, 233, 223, 171, 218, 247, 76, 105, 66, 142, 192, 223, 172, 21, 120, 78, 237, 199, 70,
|
||||
110,
|
||||
])
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Fog Ledger
|
||||
|
||||
extension McConstants {
|
||||
|
||||
/// Maximum number of Key Images that may be checked in a single request.
|
||||
static let FOG_KEY_IMAGE_MAX_REQUEST_SIZE = 2000
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class McData {
|
||||
let ptr: OpaquePointer
|
||||
|
||||
init(_ ptr: OpaquePointer) {
|
||||
self.ptr = ptr
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_data_free(ptr)
|
||||
}
|
||||
|
||||
var bytes: Data {
|
||||
Data(withMcMutableBufferInfallible: { bufferPtr in
|
||||
mc_data_get_bytes(ptr, bufferPtr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
static func make(withMcDataBytes body: (inout UnsafeMutablePointer<McError>?) -> OpaquePointer?)
|
||||
-> Result<Data, LibMobileCoinError>
|
||||
{
|
||||
withMcError(body).map {
|
||||
let mcData = McData($0)
|
||||
return mcData.bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
func withMcInfallible(_ body: () -> OpaquePointer?) -> OpaquePointer {
|
||||
guard let value = body() else {
|
||||
logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func withMcError(_ body: (inout UnsafeMutablePointer<McError>?) -> OpaquePointer?)
|
||||
-> Result<OpaquePointer, LibMobileCoinError>
|
||||
{
|
||||
var error: UnsafeMutablePointer<McError>?
|
||||
guard let value = body(&error) else {
|
||||
guard let mcError = error else {
|
||||
// Safety: This condition should never occur and indicates a programming error.
|
||||
logger.fatalError("Error: \(#function): block returned failure but out_error == NULL.")
|
||||
}
|
||||
let err: LibMobileCoinError
|
||||
do {
|
||||
err = try LibMobileCoinError.make(consuming: mcError).get()
|
||||
} catch {
|
||||
logger.fatalError("Error: \(#function): \(error)")
|
||||
}
|
||||
guard err.errorCode != .panic else {
|
||||
logger.fatalError("LibMobileCoin function panicked: \(redacting: err.description)")
|
||||
}
|
||||
return .failure(err)
|
||||
}
|
||||
return .success(value)
|
||||
}
|
||||
|
||||
func withMcInfallible(_ body: () -> Bool) {
|
||||
guard body() else {
|
||||
logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func withMcError(_ body: (inout UnsafeMutablePointer<McError>?) -> Bool)
|
||||
-> Result<(), LibMobileCoinError>
|
||||
{
|
||||
var error: UnsafeMutablePointer<McError>?
|
||||
guard body(&error) else {
|
||||
guard let mcError = error else {
|
||||
// Safety: This condition should never occur and indicates a programming error.
|
||||
logger.fatalError("Error: \(#function): block returned failure but out_error == NULL.")
|
||||
}
|
||||
let err: LibMobileCoinError
|
||||
do {
|
||||
err = try LibMobileCoinError.make(consuming: mcError).get()
|
||||
} catch {
|
||||
logger.fatalError("Error: \(#function): \(error)")
|
||||
}
|
||||
guard err.errorCode != .panic else {
|
||||
logger.fatalError("LibMobileCoin function panicked: \(redacting: err.description)")
|
||||
}
|
||||
return .failure(err)
|
||||
}
|
||||
return .success(())
|
||||
}
|
||||
|
||||
func withMcInfallibleReturningOptional<T>(_ body: () -> T?) -> T {
|
||||
guard let value = body() else {
|
||||
logger.fatalError("Error: \(#function): Infallible LibMobileCoin function failed.")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func withMcErrorReturningOptional<T>(_ body: (inout UnsafeMutablePointer<McError>?) -> T?)
|
||||
-> Result<T, LibMobileCoinError>
|
||||
{
|
||||
var error: UnsafeMutablePointer<McError>?
|
||||
guard let value = body(&error) else {
|
||||
guard let mcError = error else {
|
||||
// Safety: This condition should never occur and indicates a programming error.
|
||||
logger.fatalError("Error: \(#function): block returned failure but out_error == NULL.")
|
||||
}
|
||||
let err: LibMobileCoinError
|
||||
do {
|
||||
err = try LibMobileCoinError.make(consuming: mcError).get()
|
||||
} catch {
|
||||
logger.fatalError("Error: \(#function): \(error)")
|
||||
}
|
||||
guard err.errorCode != .panic else {
|
||||
logger.fatalError("LibMobileCoin function panicked: \(redacting: err.description)")
|
||||
}
|
||||
return .failure(err)
|
||||
}
|
||||
return .success(value)
|
||||
}
|
||||
|
||||
func withMcErrorReturningArrayCount(_ body: (inout UnsafeMutablePointer<McError>?) -> Int)
|
||||
-> Result<Int, LibMobileCoinError>
|
||||
{
|
||||
var error: UnsafeMutablePointer<McError>?
|
||||
let value = body(&error)
|
||||
guard value >= 0 else {
|
||||
guard let mcError = error else {
|
||||
// Safety: This condition should never occur and indicates a programming error.
|
||||
logger.fatalError("Error: \(#function): block returned failure but out_error == NULL.")
|
||||
}
|
||||
let err: LibMobileCoinError
|
||||
do {
|
||||
err = try LibMobileCoinError.make(consuming: mcError).get()
|
||||
} catch {
|
||||
logger.fatalError("Error: \(#function): \(error)")
|
||||
}
|
||||
guard err.errorCode != .panic else {
|
||||
logger.fatalError("LibMobileCoin function panicked: \(redacting: err.description)")
|
||||
}
|
||||
return .failure(err)
|
||||
}
|
||||
return .success(value)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
func withMcRngCallback<T>(
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?,
|
||||
rngContext: Any?,
|
||||
_ body: (UnsafeMutablePointer<McRngCallback>?) throws -> T
|
||||
) rethrows -> T {
|
||||
if let rng = rng {
|
||||
var rngContext = rngContext
|
||||
return try withUnsafeMutablePointer(to: &rngContext) { rngContextPtr in
|
||||
var rngCallback = McRngCallback(rng: rng, context: rngContextPtr)
|
||||
return try body(&rngCallback)
|
||||
}
|
||||
} else {
|
||||
return try body(nil)
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension String {
|
||||
init(mcString: UnsafeMutablePointer<CChar>) {
|
||||
defer {
|
||||
mc_string_free(mcString)
|
||||
}
|
||||
self.init(cString: mcString)
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable orphaned_doc_comment
|
||||
|
||||
import LibMobileCoin
|
||||
|
||||
/// This file contains temporary interop code to allow easy source compatibility with upstream
|
||||
/// LibMobileCoin and MobileCoin server code. The code in this file can be removed once upstream
|
||||
/// is deployed to alpha and the older server code no longer needs to be supported.
|
||||
@ -1,183 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
// MARK: - External
|
||||
|
||||
extension External_RistrettoPrivate {
|
||||
init<DataType: DataConvertible>(_ data: DataType) {
|
||||
self.init()
|
||||
self.data = data.data
|
||||
}
|
||||
}
|
||||
|
||||
extension External_CompressedRistretto {
|
||||
init<DataType: DataConvertible>(_ data: DataType) {
|
||||
self.init()
|
||||
self.data = data.data
|
||||
}
|
||||
}
|
||||
|
||||
extension External_KeyImage {
|
||||
init<DataType: DataConvertible>(_ data: DataType) {
|
||||
self.init()
|
||||
self.data = data.data
|
||||
}
|
||||
}
|
||||
|
||||
extension External_Amount {
|
||||
init<CommitmentType: DataConvertible>(commitment: CommitmentType, maskedValue: UInt64) {
|
||||
self.init()
|
||||
self.commitment = External_CompressedRistretto(commitment)
|
||||
self.maskedValue = maskedValue
|
||||
}
|
||||
}
|
||||
|
||||
extension External_EncryptedFogHint {
|
||||
init<DataType: DataConvertible>(_ data: DataType) {
|
||||
self.init()
|
||||
self.data = data.data
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fog Common
|
||||
|
||||
extension FogCommon_BlockRange {
|
||||
init(_ range: Range<UInt64>) {
|
||||
self.init()
|
||||
self.startBlock = range.lowerBound
|
||||
self.endBlock = range.upperBound
|
||||
}
|
||||
|
||||
var range: Range<UInt64> {
|
||||
get { startBlock..<endBlock }
|
||||
set {
|
||||
startBlock = newValue.lowerBound
|
||||
endBlock = newValue.upperBound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fog View
|
||||
|
||||
extension FogView_RngRecord {
|
||||
init(nonce fogRngKey: FogRngKey, startBlock: UInt64) {
|
||||
self.init()
|
||||
self.pubkey = KexRng_KexRngPubkey(fogRngKey)
|
||||
self.startBlock = startBlock
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_TxOutSearchResult {
|
||||
var resultCodeEnum: FogView_TxOutSearchResultCode {
|
||||
get {
|
||||
FogView_TxOutSearchResultCode(rawValue: Int(resultCode))
|
||||
?? .UNRECOGNIZED(Int(resultCode))
|
||||
}
|
||||
set { resultCode = UInt32(newValue.rawValue) }
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_TxOutRecord {
|
||||
var timestampDate: Date? {
|
||||
get { timestamp != UInt64.max ? Date(timeIntervalSince1970: TimeInterval(timestamp)) : nil }
|
||||
set {
|
||||
if let newValue = newValue {
|
||||
timestamp = UInt64(newValue.timeIntervalSince1970)
|
||||
} else {
|
||||
timestamp = UInt64.max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fog Ledger
|
||||
|
||||
extension FogLedger_OutputResult {
|
||||
var resultCodeEnum: FogLedger_OutputResultCode {
|
||||
get {
|
||||
FogLedger_OutputResultCode(rawValue: Int(resultCode)) ?? .UNRECOGNIZED(Int(resultCode))
|
||||
}
|
||||
set { resultCode = UInt32(newValue.rawValue) }
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_KeyImageResult {
|
||||
var timestampDate: Date {
|
||||
get { Date(timeIntervalSince1970: TimeInterval(timestamp)) }
|
||||
set { timestamp = UInt64(newValue.timeIntervalSince1970) }
|
||||
}
|
||||
|
||||
var timestampResultCodeEnum: Watcher_TimestampResultCode {
|
||||
get {
|
||||
Watcher_TimestampResultCode(rawValue: Int(timestampResultCode))
|
||||
?? .UNRECOGNIZED(Int(timestampResultCode))
|
||||
}
|
||||
set { timestampResultCode = UInt32(newValue.rawValue) }
|
||||
}
|
||||
|
||||
var keyImageResultCodeEnum: FogLedger_KeyImageResultCode {
|
||||
get {
|
||||
FogLedger_KeyImageResultCode(rawValue: Int(keyImageResultCode))
|
||||
?? .UNRECOGNIZED(Int(keyImageResultCode))
|
||||
}
|
||||
set { keyImageResultCode = UInt32(newValue.rawValue) }
|
||||
}
|
||||
|
||||
var timestampStatus: BlockMetadata.TimestampStatus? {
|
||||
switch timestampResultCodeEnum {
|
||||
case .timestampFound:
|
||||
return .known(timestamp: timestampDate)
|
||||
case .unavailable:
|
||||
return .unavailable
|
||||
case .watcherBehind, .watcherDatabaseError, .blockIndexOutOfBounds:
|
||||
return .temporarilyUnknown
|
||||
case .unusedField, .UNRECOGNIZED:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_BlockRequest {
|
||||
var rangeValues: [Range<UInt64>] {
|
||||
get { ranges.map { $0.startBlock..<$0.endBlock } }
|
||||
set { ranges = newValue.map { FogCommon_BlockRange($0) } }
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_BlockData {
|
||||
var timestampDate: Date {
|
||||
get { Date(timeIntervalSince1970: TimeInterval(timestamp)) }
|
||||
set { timestamp = UInt64(newValue.timeIntervalSince1970) }
|
||||
}
|
||||
|
||||
var timestampResultCodeEnum: Watcher_TimestampResultCode {
|
||||
get {
|
||||
Watcher_TimestampResultCode(rawValue: Int(timestampResultCode))
|
||||
?? .UNRECOGNIZED(Int(timestampResultCode))
|
||||
}
|
||||
set { timestampResultCode = UInt32(newValue.rawValue) }
|
||||
}
|
||||
|
||||
var timestampStatus: BlockMetadata.TimestampStatus? {
|
||||
switch timestampResultCodeEnum {
|
||||
case .timestampFound:
|
||||
return .known(timestamp: timestampDate)
|
||||
case .unavailable:
|
||||
return .unavailable
|
||||
case .watcherBehind, .watcherDatabaseError, .blockIndexOutOfBounds:
|
||||
return .temporarilyUnknown
|
||||
case .unusedField, .UNRECOGNIZED:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var metadata: BlockMetadata {
|
||||
BlockMetadata(index: index, timestampStatus: timestampStatus)
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
// MARK: - External
|
||||
|
||||
extension External_AccountKey: InfallibleDataSerializable {}
|
||||
|
||||
extension External_PublicAddress: InfallibleDataSerializable {}
|
||||
|
||||
extension External_TxOutMembershipProof: InfallibleDataSerializable {}
|
||||
|
||||
extension External_TxOut: InfallibleDataSerializable {}
|
||||
|
||||
extension External_Tx: InfallibleDataSerializable {}
|
||||
|
||||
extension External_Receipt: InfallibleDataSerializable {}
|
||||
|
||||
// MARK: - Printable
|
||||
|
||||
extension Printable_PrintableWrapper: InfallibleDataSerializable {}
|
||||
|
||||
// MARK: - Attest
|
||||
|
||||
extension Attest_Message: InfallibleDataSerializable {}
|
||||
|
||||
// MARK: - Fog Report
|
||||
|
||||
extension Report_ReportResponse: InfallibleDataSerializable {}
|
||||
|
||||
// MARK: - Fog View
|
||||
|
||||
extension FogView_QueryRequestAAD: InfallibleDataSerializable {}
|
||||
extension FogView_QueryRequest: InfallibleDataSerializable {}
|
||||
extension FogView_QueryResponse: InfallibleDataSerializable {}
|
||||
|
||||
extension FogView_TxOutRecord: InfallibleDataSerializable {}
|
||||
|
||||
// MARK: - Fog Ledger
|
||||
|
||||
extension FogLedger_GetOutputsRequest: InfallibleDataSerializable {}
|
||||
extension FogLedger_GetOutputsResponse: InfallibleDataSerializable {}
|
||||
|
||||
extension FogLedger_CheckKeyImagesRequest: InfallibleDataSerializable {}
|
||||
extension FogLedger_CheckKeyImagesResponse: InfallibleDataSerializable {}
|
||||
@ -1,50 +0,0 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
extension DataConvertible {
|
||||
func asMcBuffer<T>(_ body: (UnsafePointer<McBuffer>) throws -> T) rethrows -> T {
|
||||
try data.withUnsafeBytes {
|
||||
let ptr = $0.bindMemory(to: UInt8.self)
|
||||
guard let bufferPtr = ptr.baseAddress else {
|
||||
// This indicates a programming error. Pointer returned from withUnsafeBytes
|
||||
// shouldn't have a nil baseAddress.
|
||||
logger.fatalError("ptr.baseAddress == nil.")
|
||||
}
|
||||
var buffer = McBuffer(buffer: bufferPtr, len: ptr.count)
|
||||
return try body(&buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MutableData {
|
||||
mutating func asMcMutableBuffer<T>(
|
||||
_ body: (UnsafeMutablePointer<McMutableBuffer>) throws -> T
|
||||
) rethrows -> T {
|
||||
try withUnsafeMutableBytes {
|
||||
let ptr = $0.bindMemory(to: UInt8.self)
|
||||
guard let bufferPtr = ptr.baseAddress else {
|
||||
// This indicates a programming error. Pointer returned from withUnsafeMutableBytes
|
||||
// shouldn't have a nil baseAddress.
|
||||
logger.fatalError("ptr.baseAddress == nil.")
|
||||
}
|
||||
var buffer = McMutableBuffer(buffer: bufferPtr, len: ptr.count)
|
||||
return try body(&buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: DataConvertible {
|
||||
func asOptMcBuffer<T>(_ body: (UnsafePointer<McBuffer>?) throws -> T) rethrows -> T {
|
||||
if let unwrapped = self {
|
||||
return try unwrapped.asMcBuffer(body)
|
||||
} else {
|
||||
return try body(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension AccountKey {
|
||||
public static func make(
|
||||
entropy: Data,
|
||||
fogReportUrl: String,
|
||||
fogReportId: String,
|
||||
fogAuthoritySpki: Data,
|
||||
accountIndex: UInt32 = 0
|
||||
) -> Result<AccountKey, InvalidInputError> {
|
||||
Bip39Utils.mnemonic(fromEntropy: entropy).flatMap { mnemonic in
|
||||
make(
|
||||
mnemonic: mnemonic.phrase,
|
||||
fogReportUrl: fogReportUrl,
|
||||
fogReportId: fogReportId,
|
||||
fogAuthoritySpki: fogAuthoritySpki,
|
||||
accountIndex: accountIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public static func make(
|
||||
mnemonic: String,
|
||||
fogReportUrl: String,
|
||||
fogReportId: String,
|
||||
fogAuthoritySpki: Data,
|
||||
accountIndex: UInt32 = 0
|
||||
) -> Result<AccountKey, InvalidInputError> {
|
||||
Slip10Utils.accountPrivateKeys(fromMnemonic: mnemonic, accountIndex: accountIndex)
|
||||
.flatMap {
|
||||
AccountKey.make(
|
||||
viewPrivateKey: $0.viewPrivateKey,
|
||||
spendPrivateKey: $0.spendPrivateKey,
|
||||
fogReportUrl: fogReportUrl,
|
||||
fogReportId: fogReportId,
|
||||
fogAuthoritySpki: fogAuthoritySpki)
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
mnemonic: Mnemonic,
|
||||
fogInfo: FogInfo? = nil,
|
||||
accountIndex: UInt32 = 0,
|
||||
subaddressIndex: UInt64 = McConstants.DEFAULT_SUBADDRESS_INDEX
|
||||
) {
|
||||
let (viewPrivateKey, spendPrivateKey) =
|
||||
Slip10Utils.accountPrivateKeys(fromMnemonic: mnemonic, accountIndex: accountIndex)
|
||||
self.init(
|
||||
viewPrivateKey: viewPrivateKey,
|
||||
spendPrivateKey: spendPrivateKey,
|
||||
fogInfo: fogInfo,
|
||||
subaddressIndex: subaddressIndex)
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum Bip39Utils {
|
||||
static func mnemonic(fromEntropy entropy: Data32) -> Mnemonic {
|
||||
let mnemonic = entropy.asMcBuffer { entropyPtr in
|
||||
String(mcString:
|
||||
withMcInfallibleReturningOptional { mc_bip39_mnemonic_from_entropy(entropyPtr) })
|
||||
}
|
||||
return Mnemonic(phraseSkippingValidation: mnemonic)
|
||||
}
|
||||
|
||||
/// Entropy must be a multiple of 4 bytes and 16-32 bytes in length.
|
||||
static func mnemonic(fromEntropy entropy: Data) -> Result<Mnemonic, InvalidInputError> {
|
||||
guard entropy.count % 4 == 0 else {
|
||||
return .failure(InvalidInputError("BIP39 error: entropy must be a multiple of 4 bytes"))
|
||||
}
|
||||
guard entropy.count >= 16 && entropy.count <= 32 else {
|
||||
return .failure(InvalidInputError(
|
||||
"BIP39 error: entropy must be between 16 and 32 bytes, inclusive"))
|
||||
}
|
||||
let mnemonic = entropy.asMcBuffer { entropyPtr in
|
||||
String(mcString:
|
||||
withMcInfallibleReturningOptional { mc_bip39_mnemonic_from_entropy(entropyPtr) })
|
||||
}
|
||||
return .success(Mnemonic(phraseSkippingValidation: mnemonic))
|
||||
}
|
||||
|
||||
static func words(matchingPrefix prefix: String) -> [String] {
|
||||
let wordsList =
|
||||
String(mcString: withMcInfallibleReturningOptional { mc_bip39_words_by_prefix(prefix) })
|
||||
return wordsList.split(separator: ",").map { String($0) }
|
||||
}
|
||||
|
||||
static func entropy(fromMnemonic mnemonic: Mnemonic) -> Data {
|
||||
switch Data.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_bip39_entropy_from_mnemonic(mnemonic.phrase, bufferPtr, &errorPtr)
|
||||
}) {
|
||||
case .success(let entropy):
|
||||
return entropy
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .invalidInput:
|
||||
// Safety: mc_bip39_entropy_from_mnemonic should not return invalidInput as long as
|
||||
// `mnemonic` is well-formed.
|
||||
logger.fatalError(
|
||||
"BIP39: error deriving entropy from mnemonic: \(redacting: error.description)")
|
||||
default:
|
||||
// Safety: mc_bip39_entropy_from_mnemonic should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func entropy(fromMnemonic mnemonic: String) -> Result<Data, InvalidInputError> {
|
||||
Data.make(withMcMutableBuffer: { bufferPtr, errorPtr in
|
||||
mc_bip39_entropy_from_mnemonic(mnemonic, bufferPtr, &errorPtr)
|
||||
}).mapError {
|
||||
switch $0.errorCode {
|
||||
case .invalidInput:
|
||||
return InvalidInputError(
|
||||
"BIP39: error deriving entropy from mnemonic: \(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_bip39_entropy_from_mnemonic should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Mnemonic {
|
||||
public static let allWords = Bip39Utils.words(matchingPrefix: "")
|
||||
|
||||
/// Entropy must be a multiple of 4 bytes and 16-32 bytes in length.
|
||||
public static func mnemonic(fromEntropy entropy: Data) -> Result<String, InvalidInputError> {
|
||||
Bip39Utils.mnemonic(fromEntropy: entropy).map { $0.phrase }
|
||||
}
|
||||
|
||||
public static func entropy(fromMnemonic mnemonic: String) -> Result<Data, InvalidInputError> {
|
||||
Bip39Utils.entropy(fromMnemonic: mnemonic)
|
||||
}
|
||||
|
||||
public static func words(matchingPrefix prefix: String) -> [String] {
|
||||
Bip39Utils.words(matchingPrefix: prefix)
|
||||
}
|
||||
|
||||
let phrase: String
|
||||
|
||||
init(phraseSkippingValidation phrase: String) {
|
||||
self.phrase = phrase
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains operator_usage_whitespace
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
/// See https://github.com/satoshilabs/slips/blob/master/slip-0010.md
|
||||
enum Slip10Utils {
|
||||
static func accountPrivateKeys(fromMnemonic mnemonic: Mnemonic, accountIndex: UInt32)
|
||||
-> (viewPrivateKey: RistrettoPrivate, spendPrivateKey: RistrettoPrivate)
|
||||
{
|
||||
var viewPrivateKeyOut = Data32()
|
||||
var spendPrivateKeyOut = Data32()
|
||||
viewPrivateKeyOut.asMcMutableBuffer { viewPrivateKeyOutPtr in
|
||||
spendPrivateKeyOut.asMcMutableBuffer { spendPrivateKeyOutPtr in
|
||||
switch withMcError({ errorPtr in
|
||||
mc_slip10_account_private_keys_from_mnemonic(
|
||||
mnemonic.phrase,
|
||||
accountIndex,
|
||||
viewPrivateKeyOutPtr,
|
||||
spendPrivateKeyOutPtr,
|
||||
&errorPtr)
|
||||
}) {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
switch error.errorCode {
|
||||
case .invalidInput:
|
||||
// Safety: mnemonic is guaranteed to satisfy
|
||||
// mc_slip10_account_private_keys_from_mnemonic preconditions.
|
||||
logger.fatalError(
|
||||
"LibMobileCoin invalidInput error: \(redacting: error.description)")
|
||||
default:
|
||||
// Safety: mc_slip10_account_private_keys_from_mnemonic should not throw
|
||||
// non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Safety: It's safe to skip validation because
|
||||
// mc_slip10_account_private_keys_from_mnemonic should always return valid
|
||||
// RistrettoPrivate values on success.
|
||||
return (RistrettoPrivate(skippingValidation: viewPrivateKeyOut),
|
||||
RistrettoPrivate(skippingValidation: spendPrivateKeyOut))
|
||||
}
|
||||
|
||||
static func accountPrivateKeys(fromMnemonic mnemonic: String, accountIndex: UInt32)
|
||||
-> Result<(viewPrivateKey: RistrettoPrivate, spendPrivateKey: RistrettoPrivate),
|
||||
InvalidInputError>
|
||||
{
|
||||
var viewPrivateKeyOut = Data32()
|
||||
var spendPrivateKeyOut = Data32()
|
||||
return viewPrivateKeyOut.asMcMutableBuffer { viewPrivateKeyOutPtr in
|
||||
spendPrivateKeyOut.asMcMutableBuffer { spendPrivateKeyOutPtr in
|
||||
withMcError { errorPtr in
|
||||
mc_slip10_account_private_keys_from_mnemonic(
|
||||
mnemonic,
|
||||
accountIndex,
|
||||
viewPrivateKeyOutPtr,
|
||||
spendPrivateKeyOutPtr,
|
||||
&errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .invalidInput:
|
||||
return InvalidInputError("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_slip10_account_private_keys_from_mnemonic should not throw
|
||||
// non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.map {
|
||||
// Safety: It's safe to skip validation because
|
||||
// mc_slip10_account_private_keys_from_mnemonic should always return valid
|
||||
// RistrettoPrivate values on success.
|
||||
return (RistrettoPrivate(skippingValidation: viewPrivateKeyOut),
|
||||
RistrettoPrivate(skippingValidation: spendPrivateKeyOut))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,401 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable function_parameter_count multiline_arguments multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import NIOSSL
|
||||
|
||||
public final class MobileCoinClient {
|
||||
/// - Returns: `InvalidInputError` when `accountKey` isn't configured to use Fog.
|
||||
public static func make(accountKey: AccountKey, config: Config)
|
||||
-> Result<MobileCoinClient, InvalidInputError>
|
||||
{
|
||||
guard let accountKey = AccountKeyWithFog(accountKey: accountKey) else {
|
||||
let errorMessage = "Accounts without fog URLs are not currently supported."
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
|
||||
return .success(MobileCoinClient(accountKey: accountKey, config: config))
|
||||
}
|
||||
|
||||
private let accountLock: ReadWriteDispatchLock<Account>
|
||||
private let serialQueue: DispatchQueue
|
||||
private let callbackQueue: DispatchQueue
|
||||
|
||||
private let txOutSelectionStrategy: TxOutSelectionStrategy
|
||||
private let mixinSelectionStrategy: MixinSelectionStrategy
|
||||
private let fogQueryScalingStrategy: FogQueryScalingStrategy
|
||||
|
||||
private let serviceProvider: ServiceProvider
|
||||
private let fogResolverManager: FogResolverManager
|
||||
private let feeFetcher: BlockchainFeeFetcher
|
||||
|
||||
init(accountKey: AccountKeyWithFog, config: Config) {
|
||||
logger.info("""
|
||||
Initializing \(Self.self):
|
||||
\(Self.configDescription(accountKey: accountKey, config: config))
|
||||
""", logFunction: false)
|
||||
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)")
|
||||
self.callbackQueue = config.callbackQueue ?? DispatchQueue.main
|
||||
self.accountLock = .init(Account(accountKey: accountKey))
|
||||
self.txOutSelectionStrategy = config.txOutSelectionStrategy
|
||||
self.mixinSelectionStrategy = config.mixinSelectionStrategy
|
||||
self.fogQueryScalingStrategy = config.fogQueryScalingStrategy
|
||||
|
||||
self.serviceProvider =
|
||||
DefaultServiceProvider(networkConfig: config.networkConfig, targetQueue: serialQueue)
|
||||
self.fogResolverManager = FogResolverManager(
|
||||
fogReportAttestation: config.networkConfig.fogReportAttestation,
|
||||
serviceProvider: serviceProvider,
|
||||
targetQueue: serialQueue)
|
||||
self.feeFetcher = BlockchainFeeFetcher(
|
||||
blockchainService: serviceProvider.blockchainService,
|
||||
minimumFeeCacheTTL: config.minimumFeeCacheTTL,
|
||||
targetQueue: serialQueue)
|
||||
}
|
||||
|
||||
public var balance: Balance {
|
||||
accountLock.readSync { $0.cachedBalance }
|
||||
}
|
||||
|
||||
public var accountActivity: AccountActivity {
|
||||
accountLock.readSync { $0.cachedAccountActivity }
|
||||
}
|
||||
|
||||
public func setTransportProtocol(_ transportProtocol: TransportProtocol) {
|
||||
serviceProvider.setTransportProtocolOption(transportProtocol.option)
|
||||
}
|
||||
|
||||
public func setConsensusBasicAuthorization(username: String, password: String) {
|
||||
let credentials = BasicCredentials(username: username, password: password)
|
||||
serviceProvider.setConsensusAuthorization(credentials: credentials)
|
||||
}
|
||||
|
||||
public func setFogBasicAuthorization(username: String, password: String) {
|
||||
let credentials = BasicCredentials(username: username, password: password)
|
||||
serviceProvider.setFogUserAuthorization(credentials: credentials)
|
||||
}
|
||||
|
||||
public func updateBalance(completion: @escaping (Result<Balance, ConnectionError>) -> Void) {
|
||||
Account.BalanceUpdater(
|
||||
account: accountLock,
|
||||
fogViewService: serviceProvider.fogViewService,
|
||||
fogKeyImageService: serviceProvider.fogKeyImageService,
|
||||
fogBlockService: serviceProvider.fogBlockService,
|
||||
fogQueryScalingStrategy: fogQueryScalingStrategy,
|
||||
targetQueue: serialQueue
|
||||
).updateBalance { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func amountTransferable(
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (Result<UInt64, BalanceTransferEstimationFetcherError>) -> Void
|
||||
) {
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).amountTransferable(feeLevel: feeLevel, completion: completion)
|
||||
}
|
||||
|
||||
public func estimateTotalFee(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (Result<UInt64, TransactionEstimationFetcherError>) -> Void
|
||||
) {
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).estimateTotalFee(toSendAmount: amount, feeLevel: feeLevel, completion: completion)
|
||||
}
|
||||
|
||||
public func requiresDefragmentation(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (Result<Bool, TransactionEstimationFetcherError>) -> Void
|
||||
) {
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).requiresDefragmentation(toSendAmount: amount, feeLevel: feeLevel, completion: completion)
|
||||
}
|
||||
|
||||
public func prepareTransaction(
|
||||
to recipient: PublicAddress,
|
||||
amount: UInt64,
|
||||
fee: UInt64,
|
||||
completion: @escaping (
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, fee: fee) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareTransaction(
|
||||
to recipient: PublicAddress,
|
||||
amount: UInt64,
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, feeLevel: feeLevel) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareDefragmentationStepTransactions(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (Result<[Transaction], DefragTransactionPreparationError>) -> Void
|
||||
) {
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareDefragmentationStepTransactions(toSendAmount: amount, feeLevel: feeLevel)
|
||||
{ result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func submitTransaction(
|
||||
_ transaction: Transaction,
|
||||
completion: @escaping (Result<(), TransactionSubmissionError>) -> Void
|
||||
) {
|
||||
TransactionSubmitter(
|
||||
consensusService: serviceProvider.consensusService,
|
||||
feeFetcher: feeFetcher
|
||||
).submitTransaction(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func status(
|
||||
of transaction: Transaction,
|
||||
completion: @escaping (Result<TransactionStatus, ConnectionError>) -> Void
|
||||
) {
|
||||
TransactionStatusChecker(
|
||||
account: accountLock,
|
||||
fogUntrustedTxOutService: serviceProvider.fogUntrustedTxOutService,
|
||||
fogKeyImageService: serviceProvider.fogKeyImageService,
|
||||
targetQueue: serialQueue
|
||||
).checkStatus(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func status(of receipt: Receipt) -> Result<ReceiptStatus, InvalidInputError> {
|
||||
ReceiptStatusChecker(account: accountLock).status(receipt)
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileCoinClient {
|
||||
private static func configDescription(accountKey: AccountKeyWithFog, config: Config) -> String {
|
||||
let fogInfo = accountKey.fogInfo
|
||||
return """
|
||||
Consensus url: \(config.networkConfig.consensusUrl.url)
|
||||
Fog url: \(config.networkConfig.fogUrl.url)
|
||||
AccountKey PublicAddress: \
|
||||
\(redacting: Base58Coder.encode(accountKey.accountKey.publicAddress))
|
||||
AccountKey Fog Report url: \(fogInfo.reportUrl.url)
|
||||
AccountKey Fog Report id: \(String(reflecting: fogInfo.reportId))
|
||||
AccountKey Fog Report authority sPKI: 0x\(fogInfo.authoritySpki.hexEncodedString())
|
||||
Consensus attestation: \(config.networkConfig.consensus.attestation)
|
||||
Fog View attestation: \(config.networkConfig.fogView.attestation)
|
||||
Fog KeyImage attestation: \(config.networkConfig.fogKeyImage.attestation)
|
||||
Fog MerkleProof attestation: \(config.networkConfig.fogMerkleProof.attestation)
|
||||
Fog Report attestation: \(config.networkConfig.fogReportAttestation)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileCoinClient {
|
||||
@available(*, deprecated, message: "Use amountTransferable(feeLevel:completion:) instead")
|
||||
public func amountTransferable(feeLevel: FeeLevel = .minimum)
|
||||
-> Result<UInt64, BalanceTransferEstimationError>
|
||||
{
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).amountTransferable(feeLevel: feeLevel)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message:
|
||||
"Use estimateTotalFee(toSendAmount:feeLevel:completion:) instead")
|
||||
public func estimateTotalFee(
|
||||
toSendAmount amount: UInt64,
|
||||
feeLevel: FeeLevel = .minimum
|
||||
) -> Result<UInt64, TransactionEstimationError> {
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).estimateTotalFee(toSendAmount: amount, feeLevel: feeLevel)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message:
|
||||
"Use requiresDefragmentation(toSendAmount:feeLevel:completion:) instead")
|
||||
public func requiresDefragmentation(toSendAmount amount: UInt64, feeLevel: FeeLevel = .minimum)
|
||||
-> Result<Bool, TransactionEstimationError>
|
||||
{
|
||||
Account.TransactionEstimator(
|
||||
account: accountLock,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).requiresDefragmentation(toSendAmount: amount, feeLevel: feeLevel)
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileCoinClient {
|
||||
public struct Config {
|
||||
/// - Returns: `InvalidInputError` when `consensusUrl` or `fogUrl` are not well-formed URLs
|
||||
/// with the appropriate schemes.
|
||||
public static func make(
|
||||
consensusUrl: String,
|
||||
consensusAttestation: Attestation,
|
||||
fogUrl: String,
|
||||
fogViewAttestation: Attestation,
|
||||
fogKeyImageAttestation: Attestation,
|
||||
fogMerkleProofAttestation: Attestation,
|
||||
fogReportAttestation: Attestation
|
||||
) -> Result<Config, InvalidInputError> {
|
||||
ConsensusUrl.make(string: consensusUrl).flatMap { consensusUrl in
|
||||
FogUrl.make(string: fogUrl).map { fogUrl in
|
||||
let attestationConfig = NetworkConfig.AttestationConfig(
|
||||
consensus: consensusAttestation,
|
||||
fogView: fogViewAttestation,
|
||||
fogKeyImage: fogKeyImageAttestation,
|
||||
fogMerkleProof: fogMerkleProofAttestation,
|
||||
fogReport: fogReportAttestation)
|
||||
let networkConfig = NetworkConfig(
|
||||
consensusUrl: consensusUrl,
|
||||
fogUrl: fogUrl,
|
||||
attestation: attestationConfig)
|
||||
return Config(networkConfig: networkConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var networkConfig: NetworkConfig
|
||||
|
||||
// default minimum fee cache TTL is 30 minutes
|
||||
public var minimumFeeCacheTTL: TimeInterval = 30 * 60
|
||||
|
||||
public var cacheStorageAdapter: StorageAdapter?
|
||||
|
||||
/// The `DispatchQueue` on which all `MobileCoinClient` completion handlers will be called.
|
||||
/// If `nil`, `DispatchQueue.main` will be used.
|
||||
public var callbackQueue: DispatchQueue?
|
||||
|
||||
var txOutSelectionStrategy: TxOutSelectionStrategy = DefaultTxOutSelectionStrategy()
|
||||
var mixinSelectionStrategy: MixinSelectionStrategy = DefaultMixinSelectionStrategy()
|
||||
var fogQueryScalingStrategy: FogQueryScalingStrategy = DefaultFogQueryScalingStrategy()
|
||||
|
||||
init(networkConfig: NetworkConfig) {
|
||||
self.networkConfig = networkConfig
|
||||
}
|
||||
|
||||
public var transportProtocol: TransportProtocol {
|
||||
get { networkConfig.transportProtocol }
|
||||
set { networkConfig.transportProtocol = newValue }
|
||||
}
|
||||
|
||||
public mutating func setConsensusTrustRoots(_ trustRoots: [Data])
|
||||
-> Result<(), InvalidInputError>
|
||||
{
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
|
||||
networkConfig.consensusTrustRoots = $0
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setFogTrustRoots(_ trustRoots: [Data]) -> Result<(), InvalidInputError>
|
||||
{
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
|
||||
networkConfig.fogTrustRoots = $0
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setConsensusBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.consensusAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
|
||||
public mutating func setFogBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.fogUserAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
|
||||
public var httpRequester: HttpRequester? {
|
||||
get { networkConfig.httpRequester }
|
||||
set { networkConfig.httpRequester = newValue }
|
||||
}
|
||||
|
||||
private static func parseTrustRoots(trustRootsBytes: [Data])
|
||||
-> Result<[NIOSSLCertificate], InvalidInputError>
|
||||
{
|
||||
var trustRoots: [NIOSSLCertificate] = []
|
||||
for trustRootBytes in trustRootsBytes {
|
||||
do {
|
||||
trustRoots.append(
|
||||
try NIOSSLCertificate(bytes: Array(trustRootBytes), format: .der))
|
||||
} catch {
|
||||
let errorMessage = "Error parsing trust root certificate: " +
|
||||
"\(trustRootBytes.base64EncodedString()) - Error: \(error)"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
}
|
||||
return .success(trustRoots)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,274 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
enum AttestAkeError: Error {
|
||||
case invalidInput(String)
|
||||
case attestationVerificationFailed(String)
|
||||
}
|
||||
|
||||
extension AttestAkeError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Attest Ake error: " + {
|
||||
switch self {
|
||||
case .invalidInput(let reason):
|
||||
return "Invalid input: \(reason)"
|
||||
case .attestationVerificationFailed(let reason):
|
||||
return "Attestation verification failed: \(reason)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
enum AeadError: Error {
|
||||
case aead(String)
|
||||
case cipher(String)
|
||||
}
|
||||
|
||||
extension AeadError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Aead error: " + {
|
||||
switch self {
|
||||
case .aead(let reason):
|
||||
return "Aead: \(reason)"
|
||||
case .cipher(let reason):
|
||||
return "Cipher: \(reason)"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
final class AttestAke {
|
||||
private var state: State = .unattested
|
||||
|
||||
func authBeginRequest(
|
||||
responderId: String,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = nil,
|
||||
rngContext: Any? = nil
|
||||
) -> Attest_AuthMessage {
|
||||
var request = Attest_AuthMessage()
|
||||
request.data = authBeginRequestData(
|
||||
responderId: responderId,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
return request
|
||||
}
|
||||
|
||||
func authBeginRequestData(
|
||||
responderId: String,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = nil,
|
||||
rngContext: Any? = nil
|
||||
) -> Data {
|
||||
let ffi = FfiAttestAke()
|
||||
let requestData = ffi.authBeginRequestData(
|
||||
responderId: responderId,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
state = .authPending(ffi)
|
||||
return requestData
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func authEnd(authResponse: Attest_AuthMessage, attestationVerifier: AttestationVerifier)
|
||||
-> Result<Cipher, AttestAkeError>
|
||||
{
|
||||
authEnd(authResponseData: authResponse.data, attestationVerifier: attestationVerifier)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func authEnd(authResponseData: Data, attestationVerifier: AttestationVerifier)
|
||||
-> Result<Cipher, AttestAkeError>
|
||||
{
|
||||
guard case .authPending(let attestAke) = state else {
|
||||
return .failure(.invalidInput("AttestAke.authEnd called without a pending auth."))
|
||||
}
|
||||
|
||||
return attestAke.authEnd(
|
||||
authResponseData: authResponseData,
|
||||
attestationVerifier: attestationVerifier
|
||||
).map {
|
||||
state = .attested(attestAke)
|
||||
return Cipher(attestAke)
|
||||
}
|
||||
}
|
||||
|
||||
var isAttested: Bool {
|
||||
if case .attested = state {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var cipher: Cipher? {
|
||||
if case .attested(let attestAke) = state {
|
||||
return Cipher(attestAke)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func deattest() {
|
||||
state = .unattested
|
||||
}
|
||||
}
|
||||
|
||||
extension AttestAke {
|
||||
struct Cipher {
|
||||
private let ffi: FfiAttestAke
|
||||
|
||||
fileprivate init(_ ffi: FfiAttestAke) {
|
||||
self.ffi = ffi
|
||||
}
|
||||
|
||||
var binding: Data {
|
||||
// Safety: ffi is guaranteed to be attested at this point, so ffi.binding() should
|
||||
// never fail.
|
||||
ffi.binding
|
||||
}
|
||||
|
||||
func encryptMessage(aad: Data, plaintext: Data)
|
||||
-> Result<Attest_Message, AeadError>
|
||||
{
|
||||
var message = Attest_Message()
|
||||
message.aad = aad
|
||||
message.channelID = binding
|
||||
return encrypt(aad: aad, plaintext: plaintext).map {
|
||||
message.data = $0
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
func encrypt(aad: Data, plaintext: Data) -> Result<Data, AeadError> {
|
||||
ffi.encrypt(aad: aad, plaintext: plaintext)
|
||||
}
|
||||
|
||||
func decryptMessage(_ message: Attest_Message) -> Result<Data, AeadError> {
|
||||
decrypt(aad: message.aad, ciphertext: message.data)
|
||||
}
|
||||
|
||||
func decrypt(aad: Data, ciphertext: Data) -> Result<Data, AeadError> {
|
||||
ffi.decrypt(aad: aad, ciphertext: ciphertext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AttestAke {
|
||||
private enum State {
|
||||
case unattested
|
||||
case authPending(FfiAttestAke)
|
||||
case attested(FfiAttestAke)
|
||||
}
|
||||
}
|
||||
|
||||
private final class FfiAttestAke {
|
||||
private let ptr: OpaquePointer
|
||||
|
||||
init() {
|
||||
// Safety: mc_attest_ake_create should never return nil.
|
||||
self.ptr = withMcInfallible(mc_attest_ake_create)
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_attest_ake_free(ptr)
|
||||
}
|
||||
|
||||
var isAttested: Bool {
|
||||
var attested = false
|
||||
withMcInfallible {
|
||||
mc_attest_ake_is_attested(ptr, &attested)
|
||||
}
|
||||
return attested
|
||||
}
|
||||
|
||||
var binding: Data {
|
||||
Data(withMcMutableBufferInfallible: { bufferPtr in
|
||||
mc_attest_ake_get_binding(ptr, bufferPtr)
|
||||
})
|
||||
}
|
||||
|
||||
func authBeginRequestData(
|
||||
responderId: String,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = nil,
|
||||
rngContext: Any? = nil
|
||||
) -> Data {
|
||||
withMcRngCallback(rng: rng, rngContext: rngContext) { rngCallbackPtr in
|
||||
Data(withMcMutableBufferInfallible: { bufferPtr in
|
||||
mc_attest_ake_get_auth_request(ptr, responderId, rngCallbackPtr, bufferPtr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func authEnd(authResponseData: Data, attestationVerifier: AttestationVerifier)
|
||||
-> Result<(), AttestAkeError>
|
||||
{
|
||||
authResponseData.asMcBuffer { bytesPtr in
|
||||
attestationVerifier.withUnsafeOpaquePointer { attestationVerifierPtr in
|
||||
withMcError { errorPtr in
|
||||
mc_attest_ake_process_auth_response(
|
||||
ptr,
|
||||
bytesPtr,
|
||||
attestationVerifierPtr,
|
||||
&errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .invalidInput:
|
||||
return .invalidInput("\(redacting: $0.description)")
|
||||
case .attestationVerificationFailed:
|
||||
return .attestationVerificationFailed("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_attest_ake_process_auth_response should not throw
|
||||
// non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encrypt(aad: Data, plaintext: Data) -> Result<Data, AeadError> {
|
||||
aad.asMcBuffer { aadPtr in
|
||||
plaintext.asMcBuffer { plaintextPtr in
|
||||
Data.make(withMcMutableBuffer: { ciphertextOutPtr, errorPtr in
|
||||
mc_attest_ake_encrypt(ptr, aadPtr, plaintextPtr, ciphertextOutPtr, &errorPtr)
|
||||
}).mapError {
|
||||
switch $0.errorCode {
|
||||
case .aead:
|
||||
return .aead("\(redacting: $0.description)")
|
||||
case .cipher:
|
||||
return .cipher("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_attest_ake_encrypt should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decrypt(aad: Data, ciphertext: Data) -> Result<Data, AeadError> {
|
||||
aad.asMcBuffer { aadPtr in
|
||||
ciphertext.asMcBuffer { ciphertextPtr in
|
||||
Data.make(withEstimatedLengthMcMutableBuffer: ciphertext.count)
|
||||
{ plaintextOutPtr, errorPtr in
|
||||
mc_attest_ake_decrypt(ptr, aadPtr, ciphertextPtr, plaintextOutPtr, &errorPtr)
|
||||
}.mapError {
|
||||
switch $0.errorCode {
|
||||
case .aead:
|
||||
return .aead("\(redacting: $0.description)")
|
||||
case .cipher:
|
||||
return .cipher("\(redacting: $0.description)")
|
||||
default:
|
||||
// Safety: mc_attest_ake_decrypt should not throw non-documented errors.
|
||||
logger.fatalError("Unhandled LibMobileCoin error: \(redacting: $0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,187 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_function_chains
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Attestation {
|
||||
static func make(
|
||||
mrSigner: Data,
|
||||
productId: UInt16,
|
||||
minimumSecurityVersion: UInt16,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) -> Result<Attestation, InvalidInputError> {
|
||||
MrSigner.make(
|
||||
mrSigner: mrSigner,
|
||||
productId: productId,
|
||||
minimumSecurityVersion: minimumSecurityVersion,
|
||||
allowedConfigAdvisories: allowedConfigAdvisories,
|
||||
allowedHardeningAdvisories: allowedHardeningAdvisories
|
||||
).map { mrSigner in
|
||||
Attestation(mrSigners: [mrSigner])
|
||||
}
|
||||
}
|
||||
|
||||
let mrEnclaves: [MrEnclave]
|
||||
let mrSigners: [MrSigner]
|
||||
|
||||
public init(_ mrSigner: MrSigner) {
|
||||
self.init(mrEnclaves: [], mrSigners: [mrSigner])
|
||||
}
|
||||
|
||||
public init(mrEnclaves: [MrEnclave] = [], mrSigners: [MrSigner] = []) {
|
||||
self.mrEnclaves = mrEnclaves
|
||||
self.mrSigners = mrSigners
|
||||
}
|
||||
|
||||
init(
|
||||
mrSigner: Data32,
|
||||
productId: UInt16,
|
||||
minimumSecurityVersion: UInt16,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) {
|
||||
let mrSigner = MrSigner(
|
||||
mrSigner: mrSigner,
|
||||
productId: productId,
|
||||
minimumSecurityVersion: minimumSecurityVersion,
|
||||
allowedConfigAdvisories: allowedConfigAdvisories,
|
||||
allowedHardeningAdvisories: allowedHardeningAdvisories)
|
||||
self.init(mrSigners: [mrSigner])
|
||||
}
|
||||
}
|
||||
|
||||
extension Attestation: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var params: [String] = []
|
||||
if mrEnclaves.count == 1 && mrSigners.isEmpty, let mrEnclave = mrEnclaves.first {
|
||||
params.append("\(mrEnclave)")
|
||||
} else if mrSigners.count == 1 && mrEnclaves.isEmpty, let mrSigner = mrSigners.first {
|
||||
params.append("\(mrSigner)")
|
||||
} else {
|
||||
if !mrEnclaves.isEmpty {
|
||||
params.append("mrEnclaves: \(mrEnclaves)")
|
||||
}
|
||||
if !mrSigners.isEmpty {
|
||||
params.append("mrSigners: \(mrSigners)")
|
||||
}
|
||||
}
|
||||
return "Attestation(\(params.joined(separator: ", ")))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Attestation {
|
||||
public struct MrEnclave {
|
||||
let mrEnclave: Data32
|
||||
let allowedConfigAdvisories: [String]
|
||||
let allowedHardeningAdvisories: [String]
|
||||
|
||||
/// - Returns: `InvalidInputError` when `mrEnclave` is not 32 bytes in length.
|
||||
public static func make(
|
||||
mrEnclave: Data,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) -> Result<MrEnclave, InvalidInputError> {
|
||||
guard let mrEnclave32 = Data32(mrEnclave) else {
|
||||
let errorMessage = "mrEnclave must be 32 bytes in length. mrEnclave: " +
|
||||
"\(mrEnclave.hexEncodedString())"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
return .success(MrEnclave(
|
||||
mrEnclave: mrEnclave32,
|
||||
allowedConfigAdvisories: allowedConfigAdvisories,
|
||||
allowedHardeningAdvisories: allowedHardeningAdvisories))
|
||||
}
|
||||
|
||||
init(
|
||||
mrEnclave: Data32,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) {
|
||||
self.mrEnclave = mrEnclave
|
||||
self.allowedConfigAdvisories = allowedConfigAdvisories
|
||||
self.allowedHardeningAdvisories = allowedHardeningAdvisories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Attestation.MrEnclave: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var params = ["0x\(mrEnclave.hexEncodedString())"]
|
||||
if !allowedConfigAdvisories.isEmpty {
|
||||
params.append("allowedConfigAdvisories: \(allowedConfigAdvisories)")
|
||||
}
|
||||
if !allowedHardeningAdvisories.isEmpty {
|
||||
params.append("allowedHardeningAdvisories: \(allowedHardeningAdvisories)")
|
||||
}
|
||||
return "MrEnclave(\(params.joined(separator: ", ")))"
|
||||
}
|
||||
}
|
||||
|
||||
extension Attestation {
|
||||
public struct MrSigner {
|
||||
let mrSigner: Data32
|
||||
let productId: UInt16
|
||||
let minimumSecurityVersion: UInt16
|
||||
let allowedConfigAdvisories: [String]
|
||||
let allowedHardeningAdvisories: [String]
|
||||
|
||||
/// - Returns: `InvalidInputError` when `mrSigner` is not 32 bytes in length.
|
||||
public static func make(
|
||||
mrSigner: Data,
|
||||
productId: UInt16,
|
||||
minimumSecurityVersion: UInt16,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) -> Result<MrSigner, InvalidInputError> {
|
||||
guard let mrSigner32 = Data32(mrSigner) else {
|
||||
let errorMessage =
|
||||
"mrSigner must be 32 bytes in length. mrSigner: \(mrSigner.hexEncodedString())"
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
return .failure(InvalidInputError(errorMessage))
|
||||
}
|
||||
|
||||
return .success(MrSigner(
|
||||
mrSigner: mrSigner32,
|
||||
productId: productId,
|
||||
minimumSecurityVersion: minimumSecurityVersion,
|
||||
allowedConfigAdvisories: allowedConfigAdvisories,
|
||||
allowedHardeningAdvisories: allowedHardeningAdvisories))
|
||||
}
|
||||
|
||||
init(
|
||||
mrSigner: Data32,
|
||||
productId: UInt16,
|
||||
minimumSecurityVersion: UInt16,
|
||||
allowedConfigAdvisories: [String] = [],
|
||||
allowedHardeningAdvisories: [String] = []
|
||||
) {
|
||||
self.mrSigner = mrSigner
|
||||
self.productId = productId
|
||||
self.minimumSecurityVersion = minimumSecurityVersion
|
||||
self.allowedConfigAdvisories = allowedConfigAdvisories
|
||||
self.allowedHardeningAdvisories = allowedHardeningAdvisories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Attestation.MrSigner: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var params = [
|
||||
"0x\(mrSigner.hexEncodedString())",
|
||||
"productId: \(productId)",
|
||||
"minimumSecurityVersion: \(minimumSecurityVersion)",
|
||||
]
|
||||
if !allowedConfigAdvisories.isEmpty {
|
||||
params.append("allowedConfigAdvisories: \(allowedConfigAdvisories)")
|
||||
}
|
||||
if !allowedHardeningAdvisories.isEmpty {
|
||||
params.append("allowedHardeningAdvisories: \(allowedHardeningAdvisories)")
|
||||
}
|
||||
return "MrSigner(\(params.joined(separator: ", ")))"
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class AttestationVerifier {
|
||||
private let ptr: OpaquePointer
|
||||
|
||||
init(attestation: Attestation) {
|
||||
// Safety: mc_verifier_create should never return nil.
|
||||
self.ptr = withMcInfallible(mc_verifier_create)
|
||||
|
||||
attestation.mrEnclaves.forEach(addMrEnclave)
|
||||
attestation.mrSigners.forEach(addMrSigner)
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_verifier_free(ptr)
|
||||
}
|
||||
|
||||
func withUnsafeOpaquePointer<R>(_ body: (OpaquePointer) throws -> R) rethrows -> R {
|
||||
try body(ptr)
|
||||
}
|
||||
|
||||
private func addMrEnclave(_ mrEnclave: Attestation.MrEnclave) {
|
||||
let ffiMrEnclaveVerifier = MrEnclaveVerifier(mrEnclave: mrEnclave)
|
||||
ffiMrEnclaveVerifier.withUnsafeOpaquePointer { ffiMrEnclaveVerifierPtr in
|
||||
// Safety: mc_verifier_add_mr_enclave should never fail.
|
||||
withMcInfallible { mc_verifier_add_mr_enclave(ptr, ffiMrEnclaveVerifierPtr) }
|
||||
}
|
||||
}
|
||||
|
||||
private func addMrSigner(_ mrSigner: Attestation.MrSigner) {
|
||||
let ffiMrSignerVerifier = MrSignerVerifier(mrSigner: mrSigner)
|
||||
ffiMrSignerVerifier.withUnsafeOpaquePointer { ffiMrSignerVerifierPtr in
|
||||
// Safety: mc_verifier_add_mr_signer should never fail.
|
||||
withMcInfallible { mc_verifier_add_mr_signer(ptr, ffiMrSignerVerifierPtr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MrEnclaveVerifier {
|
||||
private let ptr: OpaquePointer
|
||||
|
||||
init(mrEnclave: Attestation.MrEnclave) {
|
||||
self.ptr = mrEnclave.mrEnclave.asMcBuffer { mrEnclavePtr in
|
||||
// Safety: mc_mr_enclave_verifier_create should never fail.
|
||||
withMcInfallible { mc_mr_enclave_verifier_create(mrEnclavePtr) }
|
||||
}
|
||||
|
||||
mrEnclave.allowedConfigAdvisories.forEach(addConfigAdvisory)
|
||||
mrEnclave.allowedHardeningAdvisories.forEach(addHardeningAdvisory)
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_mr_enclave_verifier_free(ptr)
|
||||
}
|
||||
|
||||
func withUnsafeOpaquePointer<R>(_ body: (OpaquePointer) throws -> R) rethrows -> R {
|
||||
try body(ptr)
|
||||
}
|
||||
|
||||
private func addConfigAdvisory(advisoryId: String) {
|
||||
advisoryId.withCString { advisoryIdPtr in
|
||||
// Safety: mc_mr_enclave_verifier_allow_config_advisory should never fail.
|
||||
withMcInfallible { mc_mr_enclave_verifier_allow_config_advisory(ptr, advisoryIdPtr) }
|
||||
}
|
||||
}
|
||||
|
||||
private func addHardeningAdvisory(advisoryId: String) {
|
||||
advisoryId.withCString { advisoryIdPtr in
|
||||
// Safety: mc_mr_enclave_verifier_allow_hardening_advisory should never fail.
|
||||
withMcInfallible { mc_mr_enclave_verifier_allow_hardening_advisory(ptr, advisoryIdPtr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MrSignerVerifier {
|
||||
private let ptr: OpaquePointer
|
||||
|
||||
init(mrSigner: Attestation.MrSigner) {
|
||||
self.ptr = mrSigner.mrSigner.asMcBuffer { mrSignerPtr in
|
||||
// Safety: mc_mr_signer_verifier_create should never fail.
|
||||
withMcInfallible {
|
||||
mc_mr_signer_verifier_create(
|
||||
mrSignerPtr,
|
||||
mrSigner.productId,
|
||||
mrSigner.minimumSecurityVersion)
|
||||
}
|
||||
}
|
||||
|
||||
mrSigner.allowedConfigAdvisories.forEach(addConfigAdvisory)
|
||||
mrSigner.allowedHardeningAdvisories.forEach(addHardeningAdvisory)
|
||||
}
|
||||
|
||||
deinit {
|
||||
mc_mr_signer_verifier_free(ptr)
|
||||
}
|
||||
|
||||
func withUnsafeOpaquePointer<R>(_ body: (OpaquePointer) throws -> R) rethrows -> R {
|
||||
try body(ptr)
|
||||
}
|
||||
|
||||
private func addConfigAdvisory(advisoryId: String) {
|
||||
advisoryId.withCString { advisoryIdPtr in
|
||||
// Safety: mc_mr_signer_verifier_allow_config_advisory should never fail.
|
||||
withMcInfallible { mc_mr_signer_verifier_allow_config_advisory(ptr, advisoryIdPtr) }
|
||||
}
|
||||
}
|
||||
|
||||
private func addHardeningAdvisory(advisoryId: String) {
|
||||
advisoryId.withCString { advisoryIdPtr in
|
||||
// Safety: mc_mr_signer_verifier_allow_hardening_advisory should never fail.
|
||||
withMcInfallible { mc_mr_signer_verifier_allow_hardening_advisory(ptr, advisoryIdPtr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NIOSSL
|
||||
|
||||
protocol AttestedConnectionConfigProtocol: ConnectionConfigProtocol {
|
||||
var attestation: Attestation { get }
|
||||
}
|
||||
|
||||
struct AttestedConnectionConfig<Url: MobileCoinUrlProtocol>: AttestedConnectionConfigProtocol {
|
||||
let urlTyped: Url
|
||||
let transportProtocolOption: TransportProtocol.Option
|
||||
let attestation: Attestation
|
||||
let trustRoots: [NIOSSLCertificate]?
|
||||
let authorization: BasicCredentials?
|
||||
|
||||
init(
|
||||
url: Url,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
attestation: Attestation,
|
||||
trustRoots: [NIOSSLCertificate]?,
|
||||
authorization: BasicCredentials?
|
||||
) {
|
||||
self.urlTyped = url
|
||||
self.transportProtocolOption = transportProtocolOption
|
||||
self.attestation = attestation
|
||||
self.trustRoots = trustRoots
|
||||
self.authorization = authorization
|
||||
}
|
||||
|
||||
var url: MobileCoinUrlProtocol { urlTyped }
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BasicCredentials {
|
||||
let username: String
|
||||
let password: String
|
||||
|
||||
var authorizationHeaderValue: String {
|
||||
let credentials = "\(username):\(password)"
|
||||
return "Basic \(Data(credentials.utf8).base64EncodedString())"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user