✨ Enhance blockchain
This commit is contained in:
@@ -7,9 +7,28 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
576693F62DC8EDCE0024463C /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 576693F52DC8EDCE0024463C /* CryptoSwift */; };
|
||||
57B6CA1A2CFDFA39009F401F /* SwiftChainCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */; };
|
||||
57BA298C2CF77907009E4448 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 57BA298B2CF77907009E4448 /* CryptoSwift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
57B6CA1B2CFDFA39009F401F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 57BA29782CF778F5009E4448 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 57B6CA0F2CFDFA39009F401F;
|
||||
remoteInfo = SwiftChainCore;
|
||||
};
|
||||
57B6CA392CFDFB1F009F401F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 57BA29782CF778F5009E4448 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 57B6CA0F2CFDFA39009F401F;
|
||||
remoteInfo = SwiftChainCore;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
57BA297E2CF778F5009E4448 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@@ -23,10 +42,35 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftChainCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftChainCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
57BA29802CF778F5009E4448 /* SwiftChain */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SwiftChain; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
57B6CA252CFDFA39009F401F /* Exceptions for "SwiftChainCore" folder in "SwiftChainCore" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
publicHeaders = (
|
||||
SwiftChainCore.h,
|
||||
);
|
||||
target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
57B6CA112CFDFA39009F401F /* SwiftChainCore */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
57B6CA252CFDFA39009F401F /* Exceptions for "SwiftChainCore" folder in "SwiftChainCore" target */,
|
||||
);
|
||||
path = SwiftChainCore;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = SwiftChainCoreTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
57BA29822CF778F5009E4448 /* SwiftChain */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = SwiftChain;
|
||||
@@ -35,6 +79,22 @@
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
57B6CA0D2CFDFA39009F401F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
576693F62DC8EDCE0024463C /* CryptoSwift in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
57B6CA162CFDFA39009F401F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
57B6CA1A2CFDFA39009F401F /* SwiftChainCore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
57BA297D2CF778F5009E4448 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -50,6 +110,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
57BA29822CF778F5009E4448 /* SwiftChain */,
|
||||
57B6CA112CFDFA39009F401F /* SwiftChainCore */,
|
||||
57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */,
|
||||
57BA29812CF778F5009E4448 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -58,13 +120,72 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
57BA29802CF778F5009E4448 /* SwiftChain */,
|
||||
57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */,
|
||||
57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
57B6CA0B2CFDFA39009F401F /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
57B6CA0F2CFDFA39009F401F /* SwiftChainCore */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 57B6CA262CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCore" */;
|
||||
buildPhases = (
|
||||
57B6CA0B2CFDFA39009F401F /* Headers */,
|
||||
57B6CA0C2CFDFA39009F401F /* Sources */,
|
||||
57B6CA0D2CFDFA39009F401F /* Frameworks */,
|
||||
57B6CA0E2CFDFA39009F401F /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
57B6CA112CFDFA39009F401F /* SwiftChainCore */,
|
||||
);
|
||||
name = SwiftChainCore;
|
||||
packageProductDependencies = (
|
||||
576693F52DC8EDCE0024463C /* CryptoSwift */,
|
||||
);
|
||||
productName = SwiftChainCore;
|
||||
productReference = 57B6CA102CFDFA39009F401F /* SwiftChainCore.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
57B6CA182CFDFA39009F401F /* SwiftChainCoreTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 57B6CA272CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCoreTests" */;
|
||||
buildPhases = (
|
||||
57B6CA152CFDFA39009F401F /* Sources */,
|
||||
57B6CA162CFDFA39009F401F /* Frameworks */,
|
||||
57B6CA172CFDFA39009F401F /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
57B6CA1C2CFDFA39009F401F /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
57B6CA1D2CFDFA39009F401F /* SwiftChainCoreTests */,
|
||||
);
|
||||
name = SwiftChainCoreTests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = SwiftChainCoreTests;
|
||||
productReference = 57B6CA192CFDFA39009F401F /* SwiftChainCoreTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
57BA297F2CF778F5009E4448 /* SwiftChain */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 57BA29872CF778F5009E4448 /* Build configuration list for PBXNativeTarget "SwiftChain" */;
|
||||
@@ -76,6 +197,8 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
576693F42DC8EDB60024463C /* PBXTargetDependency */,
|
||||
57B6CA3A2CFDFB1F009F401F /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
57BA29822CF778F5009E4448 /* SwiftChain */,
|
||||
@@ -98,6 +221,12 @@
|
||||
LastSwiftUpdateCheck = 1610;
|
||||
LastUpgradeCheck = 1610;
|
||||
TargetAttributes = {
|
||||
57B6CA0F2CFDFA39009F401F = {
|
||||
CreatedOnToolsVersion = 16.1;
|
||||
};
|
||||
57B6CA182CFDFA39009F401F = {
|
||||
CreatedOnToolsVersion = 16.1;
|
||||
};
|
||||
57BA297F2CF778F5009E4448 = {
|
||||
CreatedOnToolsVersion = 16.1;
|
||||
};
|
||||
@@ -121,11 +250,44 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
57BA297F2CF778F5009E4448 /* SwiftChain */,
|
||||
57B6CA0F2CFDFA39009F401F /* SwiftChainCore */,
|
||||
57B6CA182CFDFA39009F401F /* SwiftChainCoreTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
57B6CA0E2CFDFA39009F401F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
57B6CA172CFDFA39009F401F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
57B6CA0C2CFDFA39009F401F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
57B6CA152CFDFA39009F401F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
57BA297C2CF778F5009E4448 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -135,7 +297,156 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
576693F42DC8EDB60024463C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 576693F32DC8EDB60024463C /* CryptoSwift */;
|
||||
};
|
||||
57B6CA1C2CFDFA39009F401F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */;
|
||||
targetProxy = 57B6CA1B2CFDFA39009F401F /* PBXContainerItemProxy */;
|
||||
};
|
||||
57B6CA3A2CFDFB1F009F401F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 57B6CA0F2CFDFA39009F401F /* SwiftChainCore */;
|
||||
targetProxy = 57B6CA392CFDFB1F009F401F /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
57B6CA212CFDFA39009F401F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCore;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = auto;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
XROS_DEPLOYMENT_TARGET = 2.1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
57B6CA222CFDFA39009F401F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_MODULE_VERIFIER = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
|
||||
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCore;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = auto;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
XROS_DEPLOYMENT_TARGET = 2.1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
57B6CA232CFDFA39009F401F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCoreTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
XROS_DEPLOYMENT_TARGET = 2.1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
57B6CA242CFDFA39009F401F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = WVH3Y23X7X;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.1;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bodinaud.SwiftChainCoreTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
XROS_DEPLOYMENT_TARGET = 2.1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
57BA29852CF778F5009E4448 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -279,6 +590,24 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
57B6CA262CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCore" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
57B6CA212CFDFA39009F401F /* Debug */,
|
||||
57B6CA222CFDFA39009F401F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
57B6CA272CFDFA39009F401F /* Build configuration list for PBXNativeTarget "SwiftChainCoreTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
57B6CA232CFDFA39009F401F /* Debug */,
|
||||
57B6CA242CFDFA39009F401F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
57BA297B2CF778F5009E4448 /* Build configuration list for PBXProject "SwiftChain" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -311,6 +640,15 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
576693F32DC8EDB60024463C /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
576693F52DC8EDCE0024463C /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
57BA298B2CF77907009E4448 /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 57BA298A2CF77907009E4448 /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
||||
"state" : {
|
||||
"revision" : "678d442c6f7828def400a70ae15968aef67ef52d",
|
||||
"version" : "1.8.3"
|
||||
"revision" : "729e01bc9b9dab466ac85f21fb9ee2bc1c61b258",
|
||||
"version" : "1.8.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>SwiftChainCore.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
//
|
||||
// Block.swift
|
||||
// Blockchain
|
||||
//
|
||||
// Created by Victor BODINAUD on 27/02/2020.
|
||||
// Copyright © 2020 Victor BODINAUD. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoSwift
|
||||
|
||||
class Block: Codable {
|
||||
var hash: String
|
||||
var transactions: [Transaction]
|
||||
var previousHash: String
|
||||
var index: Int
|
||||
var nonce: Int
|
||||
var timestamp: Int
|
||||
var difficulty: Int
|
||||
var miner: String? // Adresse qui recevra la récompense
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case hash, transactions, previousHash, index, nonce, timestamp, difficulty, miner
|
||||
}
|
||||
|
||||
init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "",
|
||||
index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) {
|
||||
self.hash = hash
|
||||
self.transactions = transactions
|
||||
self.previousHash = previousHash
|
||||
self.index = index
|
||||
self.nonce = nonce
|
||||
self.timestamp = timestamp
|
||||
self.difficulty = difficulty
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
hash = try container.decode(String.self, forKey: .hash)
|
||||
transactions = try container.decode([Transaction].self, forKey: .transactions)
|
||||
previousHash = try container.decode(String.self, forKey: .previousHash)
|
||||
index = try container.decode(Int.self, forKey: .index)
|
||||
nonce = try container.decode(Int.self, forKey: .nonce)
|
||||
timestamp = try container.decode(Int.self, forKey: .timestamp)
|
||||
difficulty = try container.decode(Int.self, forKey: .difficulty)
|
||||
miner = try container.decodeIfPresent(String.self, forKey: .miner)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(hash, forKey: .hash)
|
||||
try container.encode(transactions, forKey: .transactions)
|
||||
try container.encode(previousHash, forKey: .previousHash)
|
||||
try container.encode(index, forKey: .index)
|
||||
try container.encode(nonce, forKey: .nonce)
|
||||
try container.encode(timestamp, forKey: .timestamp)
|
||||
try container.encode(difficulty, forKey: .difficulty)
|
||||
try container.encodeIfPresent(miner, forKey: .miner)
|
||||
}
|
||||
|
||||
func mineBlock() -> Double {
|
||||
let startTime = Date()
|
||||
let target = String(repeating: "0", count: difficulty)
|
||||
|
||||
print("Mining block \(index) with difficulty \(difficulty)...")
|
||||
|
||||
repeat {
|
||||
nonce += 1
|
||||
hash = generateHash()
|
||||
} while !hash.hasPrefix(target)
|
||||
|
||||
let timeElapsed = Date().timeIntervalSince(startTime)
|
||||
print("Block mined! Nonce: \(nonce), Hash: \(hash)")
|
||||
|
||||
return timeElapsed
|
||||
}
|
||||
|
||||
func isValid() -> Bool {
|
||||
let target = String(repeating: "0", count: difficulty)
|
||||
return hash == generateHash() && hash.hasPrefix(target)
|
||||
}
|
||||
|
||||
/**
|
||||
Génère le hash en incluant toutes les transactions
|
||||
*/
|
||||
func generateHash() -> String {
|
||||
let txData = transactions.map {
|
||||
"\($0.sender)\($0.receiver)\($0.amount)\($0.type)"
|
||||
}.joined()
|
||||
|
||||
return "\(previousHash)\(txData)\(String(timestamp))\(String(index))\(String(nonce))".sha256()
|
||||
}
|
||||
|
||||
/**
|
||||
Calcule le montant total des transactions dans le bloc
|
||||
*/
|
||||
func getTotalAmount() -> Int {
|
||||
return transactions.reduce(0) { $0 + $1.amount }
|
||||
}
|
||||
|
||||
/**
|
||||
Vérifie si le bloc contient une transaction spécifique
|
||||
*/
|
||||
func containsTransaction(_ txId: String) -> Bool {
|
||||
return transactions.contains { tx in
|
||||
"\(tx.sender)\(tx.receiver)\(tx.amount)".sha256() == txId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
//
|
||||
// Blockchain.swift
|
||||
// Blockchain
|
||||
//
|
||||
// Created by Victor BODINAUD on 27/02/2020.
|
||||
// Copyright © 2020 Victor BODINAUD. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Blockchain {
|
||||
var chain = [Block]()
|
||||
let memPool: MemPool
|
||||
let accountManager: AccountManager
|
||||
let minDifficulty = 2
|
||||
let maxDifficulty = 6
|
||||
let targetBlockTime = 10.0 // en secondes
|
||||
|
||||
static let genesisBlock: Block = {
|
||||
let block = Block()
|
||||
block.previousHash = "0000genesis0000"
|
||||
block.index = 0
|
||||
block.timestamp = 1701388800
|
||||
block.difficulty = 4
|
||||
block.nonce = 12345
|
||||
block.hash = "000088c1731bed4996680d2c50ea3d9b573c1507d2d61866c0deff33a7f8cf5e"
|
||||
return block
|
||||
}()
|
||||
|
||||
init() {
|
||||
self.accountManager = AccountManager()
|
||||
self.memPool = MemPool(accountManager: accountManager)
|
||||
}
|
||||
|
||||
/**
|
||||
Initialize the first block of the blockchain.
|
||||
|
||||
- Parameters:
|
||||
- data: The datas of the block
|
||||
*/
|
||||
func createGenesisBlock() {
|
||||
chain.append(Blockchain.genesisBlock)
|
||||
print("Genesis block initialized -- hash: \(Blockchain.genesisBlock.hash)")
|
||||
}
|
||||
|
||||
/**
|
||||
Initialize a new block of the blockchain.
|
||||
|
||||
- Parameters:
|
||||
- data: The datas of the block
|
||||
*/
|
||||
func createBlock(minerAddress: String) -> Block? {
|
||||
guard let lastBlock = chain.last else { return nil }
|
||||
|
||||
// Récupérer les transactions en attente
|
||||
var transactions = memPool.getTransactionsForBlock()
|
||||
|
||||
// Ajouter la récompense de minage
|
||||
let miningReward = Transaction(sender: "SYSTEM", receiver: minerAddress, amount: 50, type: "MINING_REWARD")
|
||||
transactions.append(miningReward)
|
||||
|
||||
let newBlock = Block(
|
||||
transactions: transactions,
|
||||
previousHash: lastBlock.hash,
|
||||
index: chain.count,
|
||||
difficulty: calculateNewDifficulty()
|
||||
)
|
||||
|
||||
newBlock.miner = minerAddress
|
||||
newBlock.timestamp = Int(Date().timeIntervalSince1970)
|
||||
let miningTime = newBlock.mineBlock()
|
||||
|
||||
// Valider et traiter les transactions
|
||||
if accountManager.processBlock(newBlock) {
|
||||
chain.append(newBlock)
|
||||
print("""
|
||||
Block \(newBlock.index) created:
|
||||
Hash: \(newBlock.hash)
|
||||
Previous hash: \(newBlock.previousHash)
|
||||
Transactions: \(newBlock.transactions.count)
|
||||
Mining time: \(String(format: "%.2f", miningTime)) seconds
|
||||
""")
|
||||
return newBlock
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateNewDifficulty() -> Int {
|
||||
guard chain.count >= 2 else { return minDifficulty }
|
||||
|
||||
let lastBlock = chain.last!
|
||||
let previousBlock = chain[chain.count - 2]
|
||||
let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp)
|
||||
|
||||
if timeSpent < targetBlockTime / 2 {
|
||||
return min(lastBlock.difficulty + 1, maxDifficulty)
|
||||
} else if timeSpent > targetBlockTime * 2 {
|
||||
return max(lastBlock.difficulty - 1, minDifficulty)
|
||||
}
|
||||
|
||||
return lastBlock.difficulty
|
||||
}
|
||||
|
||||
func submitTransaction(_ transaction: Transaction) -> Bool {
|
||||
return memPool.addTransaction(transaction)
|
||||
}
|
||||
|
||||
func getBalance(address: String) -> Int {
|
||||
return accountManager.getBalance(address)
|
||||
}
|
||||
|
||||
func chainValidity() -> Bool {
|
||||
for i in 1..<chain.count {
|
||||
let currentBlock = chain[i]
|
||||
let previousBlock = chain[i-1]
|
||||
|
||||
if !currentBlock.isValid() || currentBlock.previousHash != previousBlock.hash {
|
||||
print("Chain invalid at block \(i)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateChain(_ chain: [Block]) -> Bool {
|
||||
// Vérifier que la chaîne commence par notre bloc genesis codé en dur
|
||||
guard let firstBlock = chain.first,
|
||||
firstBlock.hash == Blockchain.genesisBlock.hash else {
|
||||
print("Genesis block mismatch")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier les blocs suivants
|
||||
for i in 1..<chain.count {
|
||||
let block = chain[i]
|
||||
let previousBlock = chain[i-1]
|
||||
|
||||
if block.previousHash != previousBlock.hash {
|
||||
print("Invalid chain at block \(i): incorrect previous hash")
|
||||
return false
|
||||
}
|
||||
|
||||
if !block.isValid() {
|
||||
print("Invalid chain at block \(i): invalid block hash")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func replaceChain(_ newChain: [Block]) -> Bool {
|
||||
// Vérifier que la nouvelle chaîne est plus longue
|
||||
guard newChain.count > chain.count else {
|
||||
print("Received chain is not longer than current chain")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier la validité de la nouvelle chaîne
|
||||
guard validateChain(newChain) else {
|
||||
print("Received chain is invalid")
|
||||
return false
|
||||
}
|
||||
|
||||
// Sauvegarder l'ancienne chaîne au cas où
|
||||
let oldChain = chain
|
||||
|
||||
// Essayer de traiter tous les blocs
|
||||
for block in newChain {
|
||||
if !accountManager.processBlock(block) {
|
||||
print("Failed to process transactions in block \(block.index)")
|
||||
chain = oldChain
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
chain = newChain
|
||||
print("Chain replaced successfully")
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Victor BODINAUD on 31/03/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Transaction: Codable {
|
||||
let sender: String
|
||||
let receiver: String
|
||||
let amount: Int
|
||||
let type: String
|
||||
var signature: Data?
|
||||
var senderPublicKey: Data?
|
||||
|
||||
init(sender: String, receiver: String, amount: Int, type: String) {
|
||||
self.sender = sender
|
||||
self.receiver = receiver
|
||||
self.amount = amount
|
||||
self.type = type
|
||||
}
|
||||
|
||||
// Pour encoder/décoder les Data optionnels
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case sender, receiver, amount, type, signature, senderPublicKey
|
||||
}
|
||||
|
||||
// Données à signer
|
||||
func messageToSign() -> Data {
|
||||
return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)!
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
sender = try container.decode(String.self, forKey: .sender)
|
||||
receiver = try container.decode(String.self, forKey: .receiver)
|
||||
amount = try container.decode(Int.self, forKey: .amount)
|
||||
type = try container.decode(String.self, forKey: .type)
|
||||
signature = try container.decodeIfPresent(Data.self, forKey: .signature)
|
||||
senderPublicKey = try container.decodeIfPresent(Data.self, forKey: .senderPublicKey)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(sender, forKey: .sender)
|
||||
try container.encode(receiver, forKey: .receiver)
|
||||
try container.encode(amount, forKey: .amount)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encodeIfPresent(signature, forKey: .signature)
|
||||
try container.encodeIfPresent(senderPublicKey, forKey: .senderPublicKey)
|
||||
}
|
||||
|
||||
// Vérifier la validité de la signature
|
||||
func isSignatureValid() -> Bool {
|
||||
guard let signature = signature,
|
||||
let publicKeyData = senderPublicKey else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Pas besoin de vérifier la signature pour les récompenses de minage
|
||||
if type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
|
||||
return Wallet.verifySignature(
|
||||
for: self,
|
||||
signature: signature,
|
||||
publicKeyData: publicKeyData
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftChainCore
|
||||
|
||||
let node = Node()
|
||||
var command: String?
|
||||
@@ -15,6 +16,8 @@ var wallets: [String: Wallet] = [:]
|
||||
|
||||
print("""
|
||||
Blockchain CLI - Commandes disponibles:
|
||||
- createwallet : Créer un nouveau wallet
|
||||
- listwallet : Liste des wallets
|
||||
- mine : Miner un nouveau bloc
|
||||
- send : Envoyer des tokens
|
||||
- balance : Voir le solde d'une adresse
|
||||
@@ -23,6 +26,7 @@ Blockchain CLI - Commandes disponibles:
|
||||
- setminer : Changer l'adresse du mineur
|
||||
- connect <host> : Se connecter à un pair
|
||||
- peers : Liste des pairs connectés
|
||||
- mempool : État du mempool
|
||||
- exit : Quitter
|
||||
""")
|
||||
|
||||
@@ -41,7 +45,7 @@ repeat {
|
||||
node.listPeers()
|
||||
|
||||
case "mine":
|
||||
if let block = node.mineBlock(minerAddress: currentMinerAddress) {
|
||||
if let _ = node.mineBlock(minerAddress: currentMinerAddress) {
|
||||
print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)")
|
||||
print("Nouveau solde: \(node.getBalance(currentMinerAddress))")
|
||||
} else {
|
||||
@@ -81,27 +85,41 @@ repeat {
|
||||
case "exit":
|
||||
print("Au revoir!")
|
||||
|
||||
// Ajoutons une nouvelle commande pour créer un wallet
|
||||
case "createwallet":
|
||||
let wallet = Wallet()
|
||||
wallets[wallet.address] = wallet
|
||||
print("Nouveau wallet créé!")
|
||||
print("Adresse: \(wallet.address)")
|
||||
|
||||
case "listwallet":
|
||||
print("\nWallets disponibles:")
|
||||
for (address, _) in wallets {
|
||||
print("- \(address) (Solde: \(node.getBalance(address)))")
|
||||
}
|
||||
|
||||
case "send":
|
||||
print("Votre adresse (wallet):")
|
||||
guard let senderAddress = readLine(),
|
||||
let wallet = wallets[senderAddress] else {
|
||||
let wallet = wallets[senderAddress]
|
||||
else {
|
||||
print("Wallet non trouvé")
|
||||
break
|
||||
}
|
||||
|
||||
print("Adresse du destinataire:")
|
||||
guard let receiverAddress = readLine() else { break }
|
||||
guard let receiverAddress = readLine() else {
|
||||
print("Adresse du destinataire invalide")
|
||||
break
|
||||
}
|
||||
|
||||
print("Montant:")
|
||||
guard let amountStr = readLine(),
|
||||
let amount = Int(amountStr) else { break }
|
||||
let amount = Int(amountStr),
|
||||
amount > 0
|
||||
else {
|
||||
print("Montant invalide")
|
||||
break
|
||||
}
|
||||
|
||||
let transaction = Transaction(
|
||||
sender: senderAddress,
|
||||
@@ -110,16 +128,21 @@ repeat {
|
||||
type: "TRANSFER"
|
||||
)
|
||||
|
||||
// Signer la transaction
|
||||
transaction.senderPublicKey = wallet.getPublicKeyData()
|
||||
transaction.signature = wallet.signTransaction(transaction)
|
||||
|
||||
if node.submitTransaction(transaction) {
|
||||
print("Transaction propagée au réseau!")
|
||||
if let signature = wallet.signTransaction(transaction) {
|
||||
transaction.signature = signature
|
||||
if node.submitTransaction(transaction) {
|
||||
print("Transaction signée et propagée au réseau!")
|
||||
} else {
|
||||
print("Erreur lors de l'envoi de la transaction")
|
||||
}
|
||||
} else {
|
||||
print("Erreur lors de l'envoi de la transaction")
|
||||
print("Erreur lors de la signature de la transaction")
|
||||
}
|
||||
|
||||
case "mempool":
|
||||
node.printMemPoolStatus()
|
||||
|
||||
default:
|
||||
print("Commande inconnue")
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
//
|
||||
// Created by Victor BODINAUD on 31/03/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Account {
|
||||
public class Account {
|
||||
var id: String
|
||||
var balance: Int
|
||||
|
||||
|
||||
init(id: String, balance: Int) {
|
||||
self.id = id
|
||||
self.balance = balance
|
||||
@@ -5,10 +5,8 @@
|
||||
// Created by Victor on 27/11/2024.
|
||||
//
|
||||
|
||||
class AccountManager {
|
||||
public class AccountManager {
|
||||
private var accounts: [String: Account] = [:]
|
||||
|
||||
// Mining reward amount
|
||||
private let miningReward = 50
|
||||
|
||||
func getAccount(_ address: String) -> Account {
|
||||
@@ -21,7 +19,6 @@ class AccountManager {
|
||||
}
|
||||
|
||||
func canProcessTransaction(_ transaction: Transaction) -> Bool {
|
||||
// Mining rewards are always valid
|
||||
if transaction.type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
@@ -55,13 +52,10 @@ class AccountManager {
|
||||
}
|
||||
|
||||
func processBlock(_ block: Block) -> Bool {
|
||||
// Create temporary state to validate all transactions
|
||||
let tempAccounts = accounts
|
||||
|
||||
// Try to process all transactions
|
||||
for transaction in block.transactions {
|
||||
if !processTransaction(transaction) {
|
||||
// Restore previous state if any transaction fails
|
||||
accounts = tempAccounts
|
||||
return false
|
||||
}
|
||||
116
SwiftChainCore/Models/Block.swift
Normal file
116
SwiftChainCore/Models/Block.swift
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Block.swift
|
||||
// Blockchain
|
||||
//
|
||||
// Created by Victor BODINAUD on 27/02/2020.
|
||||
// Copyright © 2020 Victor BODINAUD. All rights reserved.
|
||||
//
|
||||
|
||||
internal import CryptoSwift
|
||||
import Foundation
|
||||
|
||||
public class Block: Codable {
|
||||
var hash: String
|
||||
var transactions: [Transaction]
|
||||
var previousHash: String
|
||||
var index: Int
|
||||
var nonce: Int
|
||||
var timestamp: Int
|
||||
var difficulty: Int
|
||||
var miner: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case hash, transactions, previousHash, index, nonce, timestamp, difficulty, miner
|
||||
}
|
||||
|
||||
init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "",
|
||||
index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4)
|
||||
{
|
||||
self.hash = hash
|
||||
self.transactions = transactions
|
||||
self.previousHash = previousHash
|
||||
self.index = index
|
||||
self.nonce = nonce
|
||||
self.timestamp = timestamp
|
||||
self.difficulty = difficulty
|
||||
}
|
||||
|
||||
func mineBlock() -> Double {
|
||||
let startTime = Date()
|
||||
let target = String(repeating: "0", count: difficulty)
|
||||
|
||||
print("Mining block \(index) with difficulty \(difficulty)...")
|
||||
|
||||
repeat {
|
||||
nonce += 1
|
||||
hash = generateHash()
|
||||
} while !hash.hasPrefix(target)
|
||||
|
||||
let timeElapsed = Date().timeIntervalSince(startTime)
|
||||
print("Block mined! Nonce: \(nonce), Hash: \(hash)")
|
||||
|
||||
return timeElapsed
|
||||
}
|
||||
|
||||
func isValid() -> Bool {
|
||||
// Vérification de base du hash
|
||||
let target = String(repeating: "0", count: difficulty)
|
||||
if hash != generateHash() || !hash.hasPrefix(target) {
|
||||
print("Block \(index): Invalid hash")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier l'index
|
||||
if index < 0 {
|
||||
print("Block: Invalid index")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier le timestamp
|
||||
let currentTime = Int(Date().timeIntervalSince1970)
|
||||
if timestamp > currentTime + 7200 || timestamp < 1701388800 { // 2h dans le futur max
|
||||
print("Block: Invalid timestamp")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier les transactions
|
||||
for transaction in transactions {
|
||||
if !transaction.isValid() {
|
||||
print("Block: Invalid transaction found")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier qu'il n'y a qu'une seule récompense de minage
|
||||
if transaction.type == "MINING_REWARD" {
|
||||
if transactions.filter({ $0.type == "MINING_REWARD" }).count > 1 {
|
||||
print("Block: Multiple mining rewards found")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func generateHash() -> String {
|
||||
let txData = transactions.map {
|
||||
"\($0.sender)\($0.receiver)\($0.amount)\($0.type)"
|
||||
}.joined()
|
||||
|
||||
return "\(previousHash)\(txData)\(String(timestamp))\(String(index))\(String(nonce))".sha256()
|
||||
}
|
||||
}
|
||||
|
||||
// Structure pour suivre la progression du mining
|
||||
struct MiningProgress {
|
||||
let hashesChecked: Int
|
||||
let hashRate: Double
|
||||
let elapsedTime: TimeInterval
|
||||
let estimatedTimeRemaining: TimeInterval?
|
||||
}
|
||||
|
||||
// Erreurs possibles pendant le mining
|
||||
enum MiningError: Error {
|
||||
case cancelled
|
||||
case failed(String)
|
||||
}
|
||||
127
SwiftChainCore/Models/Blockchain.swift
Normal file
127
SwiftChainCore/Models/Blockchain.swift
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Blockchain.swift
|
||||
// Blockchain
|
||||
//
|
||||
// Created by Victor BODINAUD on 27/02/2020.
|
||||
// Copyright © 2020 Victor BODINAUD. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class Blockchain {
|
||||
var chain = [Block]()
|
||||
let minDifficulty = 2
|
||||
let maxDifficulty = 6
|
||||
let targetBlockTime = 10.0 // en secondes
|
||||
|
||||
static let genesisBlock: Block = {
|
||||
let block = Block()
|
||||
block.previousHash = "0000genesis0000"
|
||||
block.index = 0
|
||||
block.timestamp = 1701388800
|
||||
block.difficulty = 4
|
||||
block.nonce = 12345
|
||||
block.hash = "000088c1731bed4996680d2c50ea3d9b573c1507d2d61866c0deff33a7f8cf5e"
|
||||
return block
|
||||
}()
|
||||
|
||||
init() {
|
||||
chain.append(Blockchain.genesisBlock)
|
||||
print("Genesis block initialized -- hash: \(Blockchain.genesisBlock.hash)")
|
||||
}
|
||||
|
||||
func calculateNewDifficulty() -> Int {
|
||||
guard chain.count >= 2 else { return minDifficulty }
|
||||
|
||||
let lastBlock = chain.last!
|
||||
let previousBlock = chain[chain.count - 2]
|
||||
let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp)
|
||||
|
||||
if timeSpent < targetBlockTime / 2 {
|
||||
return min(lastBlock.difficulty + 1, maxDifficulty)
|
||||
} else if timeSpent > targetBlockTime * 2 {
|
||||
return max(lastBlock.difficulty - 1, minDifficulty)
|
||||
}
|
||||
|
||||
return lastBlock.difficulty
|
||||
}
|
||||
|
||||
func validateChain(_ chain: [Block]) -> Bool {
|
||||
guard let firstBlock = chain.first,
|
||||
firstBlock.hash == Blockchain.genesisBlock.hash
|
||||
else {
|
||||
print("Genesis block mismatch")
|
||||
return false
|
||||
}
|
||||
|
||||
for i in 1..<chain.count {
|
||||
let block = chain[i]
|
||||
let previousBlock = chain[i - 1]
|
||||
|
||||
if block.previousHash != previousBlock.hash {
|
||||
print("Invalid chain at block \(i): incorrect previous hash")
|
||||
return false
|
||||
}
|
||||
|
||||
if !block.isValid() {
|
||||
print("Invalid chain at block \(i): invalid block hash")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func validateChainTotally(_ chain: [Block]) -> Bool {
|
||||
// Vérifier le genesis block
|
||||
guard let firstBlock = chain.first,
|
||||
firstBlock.hash == Blockchain.genesisBlock.hash
|
||||
else {
|
||||
print("Chain: Invalid genesis block")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier la séquence des blocs
|
||||
for i in 1..<chain.count {
|
||||
let block = chain[i]
|
||||
let previousBlock = chain[i - 1]
|
||||
|
||||
// Vérifier le chaînage
|
||||
if block.previousHash != previousBlock.hash {
|
||||
print("Chain: Invalid block sequence at height \(i)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier l'index
|
||||
if block.index != i {
|
||||
print("Chain: Invalid block index at height \(i)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier la chronologie
|
||||
if block.timestamp <= previousBlock.timestamp {
|
||||
print("Chain: Invalid timestamp at height \(i)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier la difficulté
|
||||
let expectedDifficulty = calculateExpectedDifficulty(at: i, chain: chain)
|
||||
if block.difficulty != expectedDifficulty {
|
||||
print("Chain: Invalid difficulty at height \(i)")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier le bloc lui-même
|
||||
if !block.isValid() {
|
||||
print("Chain: Invalid block at height \(i)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func calculateExpectedDifficulty(at index: Int, chain: [Block]) -> Int {
|
||||
return chain[index].difficulty
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Victor on 27/11/2024.
|
||||
//
|
||||
|
||||
class MemPool {
|
||||
public class MemPool {
|
||||
private var pendingTransactions: [Transaction] = []
|
||||
private let maxTransactionsPerBlock: Int = 10
|
||||
private let accountManager: AccountManager
|
||||
@@ -108,9 +108,9 @@ class MemPool {
|
||||
for block in chain {
|
||||
if block.transactions.contains(where: { chainTx in
|
||||
chainTx.sender == pendingTx.sender &&
|
||||
chainTx.receiver == pendingTx.receiver &&
|
||||
chainTx.amount == pendingTx.amount &&
|
||||
chainTx.type == pendingTx.type
|
||||
chainTx.receiver == pendingTx.receiver &&
|
||||
chainTx.amount == pendingTx.amount &&
|
||||
chainTx.type == pendingTx.type
|
||||
}) {
|
||||
isInChain = true
|
||||
break
|
||||
@@ -8,21 +8,22 @@
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
class Node {
|
||||
public class Node {
|
||||
// Network properties
|
||||
private var peers: [NWConnection] = []
|
||||
private let port: NWEndpoint.Port = 8333
|
||||
private var listener: NWListener?
|
||||
|
||||
// Le node possède et gère la blockchain
|
||||
private let blockchain = Blockchain()
|
||||
private let memPool: MemPool
|
||||
private let accountManager = AccountManager()
|
||||
|
||||
// Queue pour gérer les connexions de manière asynchrone
|
||||
private let queue = DispatchQueue(label: "com.blockchain.node")
|
||||
|
||||
init() {
|
||||
self.memPool = MemPool(accountManager: accountManager)
|
||||
// Blockchain components
|
||||
private let blockchain: Blockchain
|
||||
private let accountManager: AccountManager
|
||||
private var pendingTransactions: [Transaction] = []
|
||||
private let maxTransactionsPerBlock: Int = 10
|
||||
|
||||
public init() {
|
||||
self.accountManager = AccountManager()
|
||||
self.blockchain = Blockchain()
|
||||
setupListener()
|
||||
}
|
||||
|
||||
@@ -65,13 +66,14 @@ class Node {
|
||||
}
|
||||
}
|
||||
|
||||
func mineBlock(minerAddress: String) -> Block? {
|
||||
guard let lastBlock = blockchain.chain.last else { return nil }
|
||||
public func mineBlock(minerAddress: String) -> Block? {
|
||||
guard let lastBlock = blockchain.chain.last else {
|
||||
print("No existing blockchain")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Récupérer les transactions du mempool
|
||||
var transactions = memPool.getTransactionsForBlock()
|
||||
|
||||
// Créer la récompense de minage
|
||||
// Utiliser la méthode locale au lieu de memPool
|
||||
var transactions = getTransactionsForBlock()
|
||||
let miningReward = Transaction(
|
||||
sender: "SYSTEM",
|
||||
receiver: minerAddress,
|
||||
@@ -80,7 +82,6 @@ class Node {
|
||||
)
|
||||
transactions.append(miningReward)
|
||||
|
||||
// Créer et miner le bloc
|
||||
let newBlock = Block(
|
||||
transactions: transactions,
|
||||
previousHash: lastBlock.hash,
|
||||
@@ -92,46 +93,49 @@ class Node {
|
||||
newBlock.timestamp = Int(Date().timeIntervalSince1970)
|
||||
let miningTime = newBlock.mineBlock()
|
||||
|
||||
// Valider et traiter les transactions
|
||||
if accountManager.processBlock(newBlock) {
|
||||
blockchain.chain.append(newBlock)
|
||||
broadcastBlock(newBlock)
|
||||
|
||||
// Nettoyer le mempool après avoir ajouté le bloc
|
||||
cleanupMempool(chain: blockchain.chain)
|
||||
|
||||
print("""
|
||||
Block \(newBlock.index) created:
|
||||
Hash: \(newBlock.hash)
|
||||
Previous hash: \(newBlock.previousHash)
|
||||
Transactions: \(newBlock.transactions.count)
|
||||
Mining time: \(String(format: "%.2f", miningTime)) seconds
|
||||
""")
|
||||
Block \(newBlock.index) created:
|
||||
Hash: \(newBlock.hash)
|
||||
Previous hash: \(newBlock.previousHash)
|
||||
Transactions: \(newBlock.transactions.count)
|
||||
Mining time: \(String(format: "%.2f", miningTime)) seconds
|
||||
""")
|
||||
return newBlock
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func submitTransaction(_ transaction: Transaction) -> Bool {
|
||||
if memPool.addTransaction(transaction) {
|
||||
// Public interface
|
||||
public func submitTransaction(_ transaction: Transaction) -> Bool {
|
||||
if addTransaction(transaction) {
|
||||
broadcastTransaction(transaction)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getBalance(_ address: String) -> Int {
|
||||
public func getBalance(_ address: String) -> Int {
|
||||
return accountManager.getBalance(address)
|
||||
}
|
||||
|
||||
func getPendingTransactions() -> [Transaction] {
|
||||
return memPool.getAllPendingTransactions()
|
||||
public func getPendingTransactions() -> [Transaction] {
|
||||
return pendingTransactions
|
||||
}
|
||||
|
||||
func isChainValid() -> Bool {
|
||||
public func isChainValid() -> Bool {
|
||||
return blockchain.validateChain(blockchain.chain)
|
||||
}
|
||||
|
||||
// Connexion à un pair
|
||||
func connectToPeer(host: String) {
|
||||
public func connectToPeer(host: String) {
|
||||
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: port)
|
||||
let connection = NWConnection(to: endpoint, using: .tcp)
|
||||
|
||||
@@ -156,7 +160,7 @@ class Node {
|
||||
}
|
||||
|
||||
// Méthode pour lister les pairs connectés
|
||||
func listPeers() {
|
||||
public func listPeers() {
|
||||
if peers.isEmpty {
|
||||
print("Aucun pair connecté")
|
||||
return
|
||||
@@ -202,20 +206,12 @@ class Node {
|
||||
print("Receive error: \(error)")
|
||||
}
|
||||
|
||||
if !isComplete && error == nil {
|
||||
if !isComplete, error == nil {
|
||||
self?.startReceiving(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traitement d'une transaction reçue
|
||||
private func handleReceivedTransaction(_ transaction: Transaction) {
|
||||
if blockchain.submitTransaction(transaction) {
|
||||
// Propager aux autres pairs si la transaction est valide
|
||||
broadcastTransaction(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleReceivedData(_ data: Data, from connection: NWConnection) {
|
||||
do {
|
||||
print("Received data of size: \(data.count) bytes")
|
||||
@@ -226,7 +222,7 @@ class Node {
|
||||
case .transaction:
|
||||
let transaction = try JSONDecoder().decode(Transaction.self, from: message.data)
|
||||
print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)")
|
||||
if blockchain.submitTransaction(transaction) {
|
||||
if addTransaction(transaction) { // Utiliser la méthode locale
|
||||
print("Transaction added to mempool successfully")
|
||||
} else {
|
||||
print("Failed to add transaction to mempool")
|
||||
@@ -299,15 +295,31 @@ class Node {
|
||||
private func handleReceivedBlockchain(_ receivedChain: [Block]) {
|
||||
print("Received blockchain with \(receivedChain.count) blocks")
|
||||
|
||||
// Vérifier si la chaîne reçue est plus longue et valide
|
||||
if receivedChain.count > blockchain.chain.count {
|
||||
print("Received chain is longer, validating...")
|
||||
if blockchain.replaceChain(receivedChain) {
|
||||
print("Blockchain updated successfully")
|
||||
// Nettoyer le mempool des transactions qui sont maintenant dans la chaîne
|
||||
blockchain.memPool.cleanupMempool(chain: receivedChain)
|
||||
if blockchain.validateChain(receivedChain) {
|
||||
// Sauvegarder l'état actuel
|
||||
let oldChain = blockchain.chain
|
||||
|
||||
// Essayer d'appliquer la nouvelle chaîne
|
||||
blockchain.chain = receivedChain
|
||||
|
||||
// Vérifier que toutes les transactions sont valides
|
||||
var isValid = true
|
||||
for block in receivedChain where !accountManager.processBlock(block) {
|
||||
isValid = false
|
||||
break
|
||||
}
|
||||
|
||||
if isValid {
|
||||
print("Blockchain updated successfully")
|
||||
cleanupMempool(chain: receivedChain)
|
||||
} else {
|
||||
blockchain.chain = oldChain
|
||||
print("Failed to process transactions in received chain")
|
||||
}
|
||||
} else {
|
||||
print("Failed to update blockchain")
|
||||
print("Received chain is invalid")
|
||||
}
|
||||
} else {
|
||||
print("Current chain is longer or equal, keeping it")
|
||||
@@ -334,11 +346,14 @@ class Node {
|
||||
}
|
||||
|
||||
// Traiter les transactions du bloc
|
||||
if !blockchain.accountManager.processBlock(block) {
|
||||
if !accountManager.processBlock(block) {
|
||||
print("Failed to process block transactions")
|
||||
return
|
||||
}
|
||||
|
||||
// Nettoyer le mempool des transactions incluses dans le bloc
|
||||
cleanupMempool(chain: [block])
|
||||
|
||||
// Ajouter le bloc à la chaîne
|
||||
blockchain.chain.append(block)
|
||||
print("New block added to chain")
|
||||
@@ -369,3 +384,117 @@ class Node {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mempool methods
|
||||
|
||||
extension Node {
|
||||
// MemPool functionality
|
||||
private func addTransaction(_ transaction: Transaction) -> Bool {
|
||||
if validateTransaction(transaction) {
|
||||
pendingTransactions.append(transaction)
|
||||
print("Transaction added to mempool successfully")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func getTransactionsForBlock() -> [Transaction] {
|
||||
var validTransactions: [Transaction] = []
|
||||
var remainingTransactions: [Transaction] = []
|
||||
|
||||
for transaction in pendingTransactions {
|
||||
if validTransactions.count >= maxTransactionsPerBlock {
|
||||
break
|
||||
}
|
||||
|
||||
if validateTransaction(transaction) {
|
||||
validTransactions.append(transaction)
|
||||
} else {
|
||||
remainingTransactions.append(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
// Update pending transactions
|
||||
pendingTransactions = remainingTransactions + pendingTransactions.dropFirst(validTransactions.count)
|
||||
|
||||
return validTransactions
|
||||
}
|
||||
|
||||
private func cleanupMempool(chain: [Block]) {
|
||||
var remainingTransactions: [Transaction] = []
|
||||
|
||||
for pendingTx in pendingTransactions {
|
||||
// On vérifie si la transaction est dans un des blocs
|
||||
var isInChain = false
|
||||
|
||||
for block in chain {
|
||||
if block.transactions.contains(where: { chainTx in
|
||||
chainTx.sender == pendingTx.sender &&
|
||||
chainTx.receiver == pendingTx.receiver &&
|
||||
chainTx.amount == pendingTx.amount &&
|
||||
chainTx.type == pendingTx.type
|
||||
}) {
|
||||
isInChain = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isInChain {
|
||||
remainingTransactions.append(pendingTx)
|
||||
}
|
||||
}
|
||||
|
||||
print("Mempool cleanup: removed \(pendingTransactions.count - remainingTransactions.count) transactions")
|
||||
pendingTransactions = remainingTransactions
|
||||
}
|
||||
|
||||
var hasPendingTransactions: Bool {
|
||||
return !pendingTransactions.isEmpty
|
||||
}
|
||||
|
||||
public func printMemPoolStatus() {
|
||||
print("""
|
||||
MemPool Status:
|
||||
- Pending transactions: \(pendingTransactions.count)
|
||||
- Maximum transactions per block: \(maxTransactionsPerBlock)
|
||||
""")
|
||||
}
|
||||
|
||||
private func checkDoubleSpending(_ transaction: Transaction) -> Bool {
|
||||
let address = transaction.sender
|
||||
let pendingAmount = pendingTransactions
|
||||
.filter { $0.sender == address }
|
||||
.reduce(0) { $0 + $1.amount }
|
||||
|
||||
let currentBalance = accountManager.getBalance(address)
|
||||
|
||||
if currentBalance < (pendingAmount + transaction.amount) {
|
||||
print("MemPool: Transaction refusée - tentative de double dépense détectée")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func validateTransaction(_ transaction: Transaction) -> Bool {
|
||||
if !transaction.isValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
if transaction.type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
|
||||
if !checkDoubleSpending(transaction) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !accountManager.canProcessTransaction(transaction) {
|
||||
print("MemPool: Transaction refusée - solde insuffisant")
|
||||
return false
|
||||
}
|
||||
|
||||
print("MemPool: Transaction validée avec succès")
|
||||
return true
|
||||
}
|
||||
}
|
||||
96
SwiftChainCore/Models/Transaction.swift
Normal file
96
SwiftChainCore/Models/Transaction.swift
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Victor BODINAUD on 31/03/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class Transaction: Codable {
|
||||
public let sender: String
|
||||
public let receiver: String
|
||||
public let amount: Int
|
||||
public let type: String
|
||||
public var signature: Data?
|
||||
public var senderPublicKey: Data?
|
||||
|
||||
public init(sender: String, receiver: String, amount: Int, type: String) {
|
||||
self.sender = sender
|
||||
self.receiver = receiver
|
||||
self.amount = amount
|
||||
self.type = type
|
||||
}
|
||||
|
||||
// Pour encoder/décoder les Data optionnels
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case sender, receiver, amount, type, signature, senderPublicKey
|
||||
}
|
||||
|
||||
func messageToSign() -> Data {
|
||||
return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)!
|
||||
}
|
||||
|
||||
func isSignatureValid() -> Bool {
|
||||
guard let signature = signature,
|
||||
let publicKeyData = senderPublicKey
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
if type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
|
||||
return Wallet.verifySignature(
|
||||
for: self,
|
||||
signature: signature,
|
||||
publicKeyData: publicKeyData
|
||||
)
|
||||
}
|
||||
|
||||
func isValid() -> Bool {
|
||||
// Vérifications de base
|
||||
if amount <= 0 {
|
||||
print("Transaction: Amount must be positive")
|
||||
return false
|
||||
}
|
||||
|
||||
if sender.isEmpty || receiver.isEmpty {
|
||||
print("Transaction: Invalid addresses")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérification du type
|
||||
if !["TRANSFER", "MINING_REWARD"].contains(type) {
|
||||
print("Transaction: Invalid type")
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifications spécifiques selon le type
|
||||
if type == "MINING_REWARD" {
|
||||
if sender != "SYSTEM" {
|
||||
print("Transaction: Mining reward must come from SYSTEM")
|
||||
return false
|
||||
}
|
||||
if amount != 50 { // La récompense doit être exactement 50
|
||||
print("Transaction: Invalid mining reward amount")
|
||||
return false
|
||||
}
|
||||
return true // Pas besoin de vérifier la signature pour les récompenses
|
||||
}
|
||||
|
||||
// Pour les transactions normales
|
||||
if !isSignatureValid() {
|
||||
print("Transaction: Invalid signature")
|
||||
return false
|
||||
}
|
||||
|
||||
if sender == receiver {
|
||||
print("Transaction: Sender cannot be receiver")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@
|
||||
// Created by Victor on 27/11/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
|
||||
class Wallet {
|
||||
public class Wallet {
|
||||
private let privateKey: Curve25519.Signing.PrivateKey
|
||||
let publicKey: Curve25519.Signing.PublicKey
|
||||
let address: String
|
||||
public let address: String
|
||||
|
||||
init() {
|
||||
public init() {
|
||||
// Générer une nouvelle paire de clés
|
||||
privateKey = Curve25519.Signing.PrivateKey()
|
||||
publicKey = privateKey.publicKey
|
||||
@@ -26,7 +26,7 @@ class Wallet {
|
||||
}
|
||||
|
||||
// Signer une transaction
|
||||
func signTransaction(_ transaction: Transaction) -> Data? {
|
||||
public func signTransaction(_ transaction: Transaction) -> Data? {
|
||||
let messageData = transaction.messageToSign()
|
||||
return try? privateKey.signature(for: messageData)
|
||||
}
|
||||
@@ -37,11 +37,11 @@ class Wallet {
|
||||
return false
|
||||
}
|
||||
|
||||
return (publicKey.isValidSignature(signature, for: transaction.messageToSign()))
|
||||
return publicKey.isValidSignature(signature, for: transaction.messageToSign())
|
||||
}
|
||||
|
||||
// Obtenir la clé publique en format Data
|
||||
func getPublicKeyData() -> Data {
|
||||
public func getPublicKeyData() -> Data {
|
||||
return publicKey.rawRepresentation
|
||||
}
|
||||
}
|
||||
13
SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md
Normal file
13
SwiftChainCore/SwiftChainCore.docc/SwiftChainCore.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# ``SwiftChainCore``
|
||||
|
||||
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
|
||||
|
||||
## Overview
|
||||
|
||||
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
|
||||
|
||||
## Topics
|
||||
|
||||
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
|
||||
|
||||
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
|
||||
18
SwiftChainCore/SwiftChainCore.h
Normal file
18
SwiftChainCore/SwiftChainCore.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// SwiftChainCore.h
|
||||
// SwiftChainCore
|
||||
//
|
||||
// Created by Victor on 02/12/2024.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for SwiftChainCore.
|
||||
FOUNDATION_EXPORT double SwiftChainCoreVersionNumber;
|
||||
|
||||
//! Project version string for SwiftChainCore.
|
||||
FOUNDATION_EXPORT const unsigned char SwiftChainCoreVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <SwiftChainCore/PublicHeader.h>
|
||||
|
||||
|
||||
36
SwiftChainCoreTests/SwiftChainCoreTests.swift
Normal file
36
SwiftChainCoreTests/SwiftChainCoreTests.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// SwiftChainCoreTests.swift
|
||||
// SwiftChainCoreTests
|
||||
//
|
||||
// Created by Victor on 02/12/2024.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftChainCore
|
||||
|
||||
final class SwiftChainCoreTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
// Any test you write for XCTest can be annotated as throws and async.
|
||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user