From 481e1cffa7ff7734ac0eeff72e9f8b9bbdd5031a Mon Sep 17 00:00:00 2001 From: Wolf McNally Date: Wed, 24 Apr 2019 00:43:27 -0700 Subject: [PATCH] First commit. --- .gitignore | 26 + .travis.yml | 14 + AirgappedSigning.podspec | 28 + Docs/AirgappedSigningExamples.md | 148 +++++ Docs/AirgappedSigningSchema.json | 326 ++++++++++ .../project.pbxproj | 610 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../AirgappedSigning-Example.xcscheme | 115 ++++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + Example/AirgappedSigning/AppDelegate.swift | 34 + .../Base.lproj/LaunchScreen.xib | 35 + .../Base.lproj/Main.storyboard | 30 + .../AppIcon.appiconset/Contents.json | 53 ++ Example/AirgappedSigning/Info.plist | 39 ++ Example/AirgappedSigning/ViewController.swift | 28 + Example/Podfile | 13 + Example/Podfile.lock | 81 +++ Example/Tests/Info.plist | 24 + Example/Tests/Tests.swift | 97 +++ LICENSE | 19 + Package.swift | 16 + README.md | 31 + Sources/AirgappedSigning/.gitkeep | 0 Sources/AirgappedSigning/Account.swift | 35 + .../AirgappedSigningError.swift | 37 ++ Sources/AirgappedSigning/Checked.swift | 68 ++ Sources/AirgappedSigning/Document.swift | 42 ++ Sources/AirgappedSigning/Header.swift | 37 ++ Sources/AirgappedSigning/KnownReceiver.swift | 33 + Sources/AirgappedSigning/MultiPart.swift | 34 + Sources/AirgappedSigning/RecoveryWords.swift | 37 ++ Sources/AirgappedSigning/Transaction.swift | 98 +++ 33 files changed, 2213 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AirgappedSigning.podspec create mode 100644 Docs/AirgappedSigningExamples.md create mode 100644 Docs/AirgappedSigningSchema.json create mode 100644 Example/AirgappedSigning.xcodeproj/project.pbxproj create mode 100644 Example/AirgappedSigning.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Example/AirgappedSigning.xcodeproj/xcshareddata/xcschemes/AirgappedSigning-Example.xcscheme create mode 100644 Example/AirgappedSigning.xcworkspace/contents.xcworkspacedata create mode 100644 Example/AirgappedSigning.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Example/AirgappedSigning/AppDelegate.swift create mode 100644 Example/AirgappedSigning/Base.lproj/LaunchScreen.xib create mode 100644 Example/AirgappedSigning/Base.lproj/Main.storyboard create mode 100644 Example/AirgappedSigning/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Example/AirgappedSigning/Info.plist create mode 100644 Example/AirgappedSigning/ViewController.swift create mode 100644 Example/Podfile create mode 100644 Example/Podfile.lock create mode 100644 Example/Tests/Info.plist create mode 100644 Example/Tests/Tests.swift create mode 100644 LICENSE create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/AirgappedSigning/.gitkeep create mode 100644 Sources/AirgappedSigning/Account.swift create mode 100644 Sources/AirgappedSigning/AirgappedSigningError.swift create mode 100644 Sources/AirgappedSigning/Checked.swift create mode 100644 Sources/AirgappedSigning/Document.swift create mode 100644 Sources/AirgappedSigning/Header.swift create mode 100644 Sources/AirgappedSigning/KnownReceiver.swift create mode 100644 Sources/AirgappedSigning/MultiPart.swift create mode 100644 Sources/AirgappedSigning/RecoveryWords.swift create mode 100644 Sources/AirgappedSigning/Transaction.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9caadcd --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +Carthage/Build +Pods/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0badb5d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +# references: +# * https://www.objc.io/issues/6-build-tools/travis-ci/ +# * https://github.com/supermarin/xcpretty#usage + +osx_image: xcode7.3 +language: objective-c +# cache: cocoapods +# podfile: Example/Podfile +# before_install: +# - gem install cocoapods # Since Travis is not always on latest version +# - pod install --project-directory=Example +script: +- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/AirgappedSigning.xcworkspace -scheme AirgappedSigning-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty +- pod lib lint diff --git a/AirgappedSigning.podspec b/AirgappedSigning.podspec new file mode 100644 index 0000000..ac7d5e9 --- /dev/null +++ b/AirgappedSigning.podspec @@ -0,0 +1,28 @@ +Pod::Spec.new do |s| + s.name = 'AirgappedSigning' + s.version = '0.1.0' + s.summary = 'A protocol for transmission of messages between hot and cold cryptographic wallets.' + + # s.description = <<-DESC + # TODO: Add long description of the pod here. + # DESC + + s.homepage = 'https://github.com/wolfmcnally/AirgappedSigning' + s.license = { :type => 'Apache', :file => 'LICENSE' } + s.author = { 'Wolf McNally' => 'wolf@wolfmcnally.com' } + s.source = { :git => 'https://github.com/blockchainCommons/AirgappedSigning.git', :tag => s.version.to_s } + + s.source_files = 'Sources/AirgappedSigning/**/*' + + s.swift_version = '5.0' + + s.ios.deployment_target = '11.0' + # s.macos.deployment_target = '10.13' + # s.tvos.deployment_target = '11.0' + + s.module_name = 'AirgappedSigning' + + s.dependency 'Bitcoin' + s.dependency 'WolfCore' + s.dependency 'NonEmpty' +end diff --git a/Docs/AirgappedSigningExamples.md b/Docs/AirgappedSigningExamples.md new file mode 100644 index 0000000..0e794f4 --- /dev/null +++ b/Docs/AirgappedSigningExamples.md @@ -0,0 +1,148 @@ +# Airgapped Signing Protocol Examples + +### Examples + +Note that while the examples in this section should validate against the JSON schema, these examples are not test vectors and should not be expected to validate. Please see the JSON schema below for further explanatory comments. + +#### MultiPart + +Used when a document is too large to fit into a packet (like a QR code) and must be broken into multiple parts. The concatenated `data` fields must decode to another document in this specification. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "multiPart": { + "uid": "449C40FE-E207-4AC9-B552-51B007B68D50", + "part": 0, + "count": 1, + "data": "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4K" + } +} +``` + +#### RecoveryWords + +Used to back up the seed from which all other keys are derived. The passphrase (if any) is never transmitted in this structure-- it is always entered directly by the user. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "recoveryWords": { + "name": "Rainy Day", + "format": "BIP39", + "words": ["panda", "diary", "marriage", "suffer", "basic", "glare", "surge", "auto", "scissors", "describe", "sell", "unique"] + } +} +``` + +#### KnownReceiver + +Used to send the address of a known receiver to the cold wallet to be used when confirming the contents of transactions. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "knownReceiver": { + "name": "Example", + "asset": "BTC", + "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" + } +} +``` + +#### Account + +Used to send a public key, asset, and associated account path to the hot wallet to be used in watching the block chain and/or creating new transactions. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "account": { + "name": "Example Account", + "asset": "BTC", + "hdPublicKey": "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz", + "index": 1 + } +} +``` + +#### Transaction Signing Request + +"Transaction" as used here is rougly equivalent to a Partially Signed Bitcoin Transaction (PSBT). It is designed so multiple parties can provide inputs, outputs, and signatures to be combined later. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "transaction": { + "uid": "819AACCF-984A-43EF-AC1D-D4CD37C9A7DC", + "asset": "BTC", + "inputs": [ + { + "uid": "A6B0DF6D-E670-4233-8EAC-626C0A9AAC9A", + "txHash": "ba34fa2ed2c42e7ba66887a96d22e14dec1244a3c47b271cd69a679afbaab868", + "inputIndex": 1, + "sender": "18f8tN4PLdCXF2JHy5PjRerqXg994yGme3", + "derivation": { + "accountIndex": 0, + "addressIndex": 12 + }, + "amount": 59943910 + } + ], + "outputs": [ + { + "uid": "79296757-6C80-49BF-996F-87FCB46A566C", + "receiver": "1N5YvoDTEbkKsE1SiPEKbih6BM1HAD3dV9", + "amount": 9240 + }, + { + "uid": "69F19CB5-A9B1-4C80-B5CA-A304E37C04C7", + "receiver": "19MUKz42a951wmXtJBa5fdvRvGvAGh6NDG", + "derivation": { + "accountIndex": 0, + "addressIndex": 4 + }, + "amount": 59855564 + } + ] + } +} +``` + +#### Transaction Signing Response + +This example is a reply from the cold wallet that signs the input provided in the previous example. + +```json +{ + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "transaction": { + "uid": "819AACCF-984A-43EF-AC1D-D4CD37C9A7DC", + "inputSignatures": [ + { + "uid": "A6B0DF6D-E670-4233-8EAC-626C0A9AAC9A", + "ecPublicKey": "AzfAJTIa8S38RRcVnZvZgWTdL/n715cbKdCqJx1yxATK", + "ecSignature": "MEQCIBBhpg0KAmDqJu/0v/L8a9kCWGMNh3QVLYpq/tfzEQdmAiAMLrZKT5H2maQXPvEm6iYTNIcpKk6B+8Lg4z+xaLVCbAE=" + } + ] + } +} +``` diff --git a/Docs/AirgappedSigningSchema.json b/Docs/AirgappedSigningSchema.json new file mode 100644 index 0000000..a5fe140 --- /dev/null +++ b/Docs/AirgappedSigningSchema.json @@ -0,0 +1,326 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://blockchaincommons.org/schemas/AirgappedSigning.json", + "$comment": "This scheme follows the convention of naming keys with `Capitalized` words when they represent types, and `lowercased` words when they represent instances. In general, `camelCase` is used for identifiers.", + "definitions": { + "Name": { + "$comment": "A human-assigned name for an object. In required field lists herein, `name` is intentionally left optional.", + "type": "string", + "minLength": 1 + }, + "UID": { + "$comment": "A unique identifier used to associate messages or parts of documents.", + "type": "string", + "format": "uuid" + }, + "Asset": { + "$comment": "Identifies a particular kind of asset, for example Bitcoin. Could also represent the type of any other document capable of being signed.\n`BTC`: Bitcoin, `BTCT`: Bitcoin Testnet.", + "type": "string", + "enum": ["BTC", "BTCT"] + }, + "Fragments": { + "$comment": "A whole number count of the smallest unit of an asset; in the case of Bitcoin, the Satoshi.", + "type": "number", + "multipleOf": 1 + }, + "MultiPart": { + "$comment": "Used when a document is too large to fit into a packet (like a QR code) and must be broken into multiple parts. The `data` field contains a base64-encoded chunck of another document that conforms to this spec.", + "type": "object", + "properties": { + "uid": { + "$comment": "`uid` must be the same for every part of the same multipart document.", + "$ref": "#/definitions/UID" + }, + "part": { + "type": "number", + "multipleOf": 1, + "minimum": 0, + "$comment": "Zero-based. Must be < `count`." + }, + "count": { + "type": "number", + "multipleOf": 1, + "minimum": 1 + }, + "data": { + "type": "string", + "contentEncoding": "base64" + } + }, + "required": [ + "uid", + "part", + "count", + "data" + ], + "additionalProperties": false + }, + "RecoveryWords": { + "$comment": "A list of recovery words used for BIP39 or similar.", + "type": "object", + "properties": { + "name": { "$ref": "#/definitions/Name" }, + "format": { "enum": ["BIP39"] }, + "words": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "type": "string", + "minLength": 3, + "maxLength": 30 + } + } + }, + "required": [ + "format", + "words" + ], + "additionalProperties": false + }, + "PaymentAddress": { + "$comment": "A payment address.", + "type": "string", + "minLength": 1 + }, + "KnownReceiver": { + "$comment": "A payment address associated with a name and asset type.", + "type": "object", + "properties": { + "name": { "$ref": "#/definitions/Name" }, + "asset": { "$ref": "#/definitions/Asset" }, + "address": { "$ref": "#/definitions/PaymentAddress" } + }, + "required": [ + "asset", + "address" + ], + "additionalProperties": false + }, + "AccountIndex": { + "$comment": "The `account` part of a BIP44 derivation path.", + "type": "number", + "multipleOf": 1, + "minimum": 0 + }, + "AddressIndex": { + "$comment": "The `address_index` part of a BIP44 derivation path.", + "type": "number", + "multipleOf": 1, + "minimum": 0 + }, + "HDPublicKey": { + "$comment": "An HD public key that can be watched for activity on the network, and from which payment addresses can be derived.", + "type": "string", + "minLength": 1 + }, + "ECPublicKey": { + "$comment": "A public key that can be used as part of a transaction signing.", + "type": "string", + "contentEncoding": "base64" + }, + "ECSignature": { + "$comment": "An EC signature.", + "type": "string", + "contentEncoding": "base64" + }, + "Account": { + "$comment": "A particular account derived from a particular public key.", + "type": "object", + "properties": { + "name": { "$ref": "#/definitions/Name" }, + "asset": { "$ref": "#/definitions/Asset" }, + "hdPublicKey": { "$ref": "#/definitions/HDPublicKey" }, + "index": { "$ref": "#/definitions/AccountIndex" } + }, + "required": [ + "asset", + "hdPublicKey", + "index" + ], + "additionalProperties": false + }, + "Derivation": { + "type": "object", + "properties": { + "accountIndex": { "$ref": "#/definitions/AccountIndex" }, + "addressIndex": { "$ref": "#/definitions/AddressIndex" }, + }, + "required": [ + "accountIndex", + "addressIndex" + ], + "additionalProperties": false + }, + "Input": { + "type": "object", + "properties": { + "uid": { + "$comment": "Associates this input with its signature(s).", + "$ref": "#/definitions/UID" + }, + "txHash": { "type": "string" }, + "inputIndex": { "type": "number" }, + "sender": { + "$comment": "Which private key to use for signing can be determined by searching for the private key from which `sender` can be derived by applying `accountIndex` and `addressIndex` in the derivation path.", + "$ref": "#/definitions/PaymentAddress" + }, + "derivation": { "$ref": "#/definitions/Derivation" }, + "amount": { "$ref": "#/definitions/Fragments" } + }, + "required": [ + "uid", + "txHash", + "inputIndex", + "sender", + "derivation", + "amount" + ], + "additionalProperties": false + }, + "Output": { + "type": "object", + "properties": { + "uid": { "$ref": "#/definitions/UID" }, + "receiver": { "$ref": "#/definitions/PaymentAddress" }, + "amount": { "$ref": "#/definitions/Fragments" }, + "derivation": { "$ref": "#/definitions/Derivation" } + }, + "required": [ + "uid", + "receiver", + "amount" + ], + "$comment": "`derivation` intentionally left optional because only change outputs need a derivation path so they can be confirmed as spendable.", + "additionalProperties": false + }, + "InputSignature": { + "type": "object", + "properties": { + "uid": { + "$comment": "Associates this signature with an input in the transaction.", + "$ref": "#/definitions/UID" + }, + "ecPublicKey": { + "$comment": "The public key which corresponds to this signature.", + "$ref": "#/definitions/ECPublicKey" + }, + "ecSignature": { + "$comment": "The signature as would be pushed to the stack from a scriptSig or witness.", + "$ref": "#/definitions/ECSignature" + } + }, + "required": [ + "uid", + "ecPublicKey", + "ecSignature" + ] + }, + "Transaction": { + "$comment": "Roughly equivalent to a Partially Signed Bitcoin Transaction (PSBT). Designed so multiple parties can provide inputs, outputs, and signatures to be combined later.", + "type": "object", + "properties": { + "uid": { + "$comment": "`uid` must be the same for all messages of the same signing transaction.", + "$ref": "#/definitions/UID" + }, + "asset": { "$ref": "#/definitions/Asset" }, + "inputs": { + "$comment": "If present, may not be empty.", + "type": "array", + "items": { "$ref": "#/definitions/Input" } + }, + "outputs": { + "$comment": "If present, may not be empty.", + "type": "array", + "items": { "$ref": "#/definitions/Output" } + }, + "inputSignatures": { + "$comment": "If present, may not be empty.", + "type": "array", + "items": { "$ref": "#/definitions/InputSignature" } + } + }, + "required": [ + "uid" + ], + "$comment": "`asset`, `inputs`, `outputs`, and `inputSignatures` intentionally left optional for representing partial transactions.", + "additionalProperties": false + }, + "Header": { + "type": "object", + "properties": { + "format": { "const": "AirgappedSigning" }, + "version": { "const": 1 } + }, + "required": [ + "type", + "version" + ], + "additionalProperties": false + } + }, + + "oneOf": [ + { + "type": "object", + "properties": { + "header": { "$ref": "#/definitions/Header" }, + "multiPart": { "$ref": "#/definitions/MultiPart" } + }, + "required": [ + "header", + "multiPart" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "header": { "$ref": "#/definitions/Header" }, + "recoveryWords": { "$ref": "#/definitions/RecoveryWords" } + }, + "required": [ + "header", + "recoveryWords" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "header": { "$ref": "#/definitions/Header" }, + "knownReceiver": { "$ref": "#/definitions/KnownReceiver" } + }, + "required": [ + "header", + "knownReceiver" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "header": { "$ref": "#/definitions/Header" }, + "account": { "$ref": "#/definitions/Account" } + }, + "required": [ + "header", + "account" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "header": { "$ref": "#/definitions/Header" }, + "transaction": { "$ref": "#/definitions/Transaction" } + }, + "required": [ + "header", + "transaction" + ], + "additionalProperties": false + } + ] +} diff --git a/Example/AirgappedSigning.xcodeproj/project.pbxproj b/Example/AirgappedSigning.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7162879 --- /dev/null +++ b/Example/AirgappedSigning.xcodeproj/project.pbxproj @@ -0,0 +1,610 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; + 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; + 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; + BC2B1DE04520DF63ECB1180C /* Pods_AirgappedSigning_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0482D64EDEE9715D175CF016 /* Pods_AirgappedSigning_Tests.framework */; }; + EF6DB9BDC7D6962179D78605 /* Pods_AirgappedSigning_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94FEB700BCF9984F9E44F826 /* Pods_AirgappedSigning_Example.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = AirgappedSigning; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0355E88BCB373C4803A6A18C /* AirgappedSigning.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = AirgappedSigning.podspec; path = ../AirgappedSigning.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 0482D64EDEE9715D175CF016 /* Pods_AirgappedSigning_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AirgappedSigning_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 05D0FE05BA7A72641201C4E1 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + 4EB2C27D3619D1CA0346AA96 /* Pods-AirgappedSigning_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AirgappedSigning_Tests.debug.xcconfig"; path = "Target Support Files/Pods-AirgappedSigning_Tests/Pods-AirgappedSigning_Tests.debug.xcconfig"; sourceTree = ""; }; + 51400B6C08379C0E6EC2285F /* Pods-AirgappedSigning_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AirgappedSigning_Tests.release.xcconfig"; path = "Target Support Files/Pods-AirgappedSigning_Tests/Pods-AirgappedSigning_Tests.release.xcconfig"; sourceTree = ""; }; + 5E267DBE350210DDA6D5F071 /* Pods-AirgappedSigning_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AirgappedSigning_Example.debug.xcconfig"; path = "Target Support Files/Pods-AirgappedSigning_Example/Pods-AirgappedSigning_Example.debug.xcconfig"; sourceTree = ""; }; + 607FACD01AFB9204008FA782 /* AirgappedSigning_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AirgappedSigning_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 607FACE51AFB9204008FA782 /* AirgappedSigning_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirgappedSigning_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; + 653EE703F7545ACADEA15A65 /* Pods-AirgappedSigning_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AirgappedSigning_Example.release.xcconfig"; path = "Target Support Files/Pods-AirgappedSigning_Example/Pods-AirgappedSigning_Example.release.xcconfig"; sourceTree = ""; }; + 844B88A490A39412A1A97776 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 94FEB700BCF9984F9E44F826 /* Pods_AirgappedSigning_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AirgappedSigning_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EF6DB9BDC7D6962179D78605 /* Pods_AirgappedSigning_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE21AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BC2B1DE04520DF63ECB1180C /* Pods_AirgappedSigning_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2E2E27C47B446D8DF8E09FC9 /* Pods */ = { + isa = PBXGroup; + children = ( + 5E267DBE350210DDA6D5F071 /* Pods-AirgappedSigning_Example.debug.xcconfig */, + 653EE703F7545ACADEA15A65 /* Pods-AirgappedSigning_Example.release.xcconfig */, + 4EB2C27D3619D1CA0346AA96 /* Pods-AirgappedSigning_Tests.debug.xcconfig */, + 51400B6C08379C0E6EC2285F /* Pods-AirgappedSigning_Tests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 5A95E4B96D10D8515149C1BF /* Frameworks */ = { + isa = PBXGroup; + children = ( + 94FEB700BCF9984F9E44F826 /* Pods_AirgappedSigning_Example.framework */, + 0482D64EDEE9715D175CF016 /* Pods_AirgappedSigning_Tests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD21AFB9204008FA782 /* Example for AirgappedSigning */, + 607FACE81AFB9204008FA782 /* Tests */, + 607FACD11AFB9204008FA782 /* Products */, + 2E2E27C47B446D8DF8E09FC9 /* Pods */, + 5A95E4B96D10D8515149C1BF /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* AirgappedSigning_Example.app */, + 607FACE51AFB9204008FA782 /* AirgappedSigning_Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for AirgappedSigning */ = { + isa = PBXGroup; + children = ( + 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + 607FACD71AFB9204008FA782 /* ViewController.swift */, + 607FACD91AFB9204008FA782 /* Main.storyboard */, + 607FACDC1AFB9204008FA782 /* Images.xcassets */, + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + ); + name = "Example for AirgappedSigning"; + path = AirgappedSigning; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACE81AFB9204008FA782 /* Tests */ = { + isa = PBXGroup; + children = ( + 607FACEB1AFB9204008FA782 /* Tests.swift */, + 607FACE91AFB9204008FA782 /* Supporting Files */, + ); + path = Tests; + sourceTree = ""; + }; + 607FACE91AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACEA1AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + 0355E88BCB373C4803A6A18C /* AirgappedSigning.podspec */, + 05D0FE05BA7A72641201C4E1 /* README.md */, + 844B88A490A39412A1A97776 /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* AirgappedSigning_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "AirgappedSigning_Example" */; + buildPhases = ( + D1057688AA0D43C557F0FCBA /* [CP] Check Pods Manifest.lock */, + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + 288882641AE88ECD39CD0290 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AirgappedSigning_Example; + productName = AirgappedSigning; + productReference = 607FACD01AFB9204008FA782 /* AirgappedSigning_Example.app */; + productType = "com.apple.product-type.application"; + }; + 607FACE41AFB9204008FA782 /* AirgappedSigning_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "AirgappedSigning_Tests" */; + buildPhases = ( + 31D8847F2CE4CC7175566907 /* [CP] Check Pods Manifest.lock */, + 607FACE11AFB9204008FA782 /* Sources */, + 607FACE21AFB9204008FA782 /* Frameworks */, + 607FACE31AFB9204008FA782 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 607FACE71AFB9204008FA782 /* PBXTargetDependency */, + ); + name = AirgappedSigning_Tests; + productName = Tests; + productReference = 607FACE51AFB9204008FA782 /* AirgappedSigning_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = WolfMcNally.com; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 0900; + }; + 607FACE41AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 0900; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "AirgappedSigning" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* AirgappedSigning_Example */, + 607FACE41AFB9204008FA782 /* AirgappedSigning_Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE31AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 288882641AE88ECD39CD0290 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AirgappedSigning_Example/Pods-AirgappedSigning_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/AirgappedSigning/AirgappedSigning.framework", + "${BUILT_PRODUCTS_DIR}/Bitcoin/Bitcoin.framework", + "${BUILT_PRODUCTS_DIR}/CBitcoin/CBitcoin.framework", + "${BUILT_PRODUCTS_DIR}/ExtensibleEnumeratedName/ExtensibleEnumeratedName.framework", + "${BUILT_PRODUCTS_DIR}/NonEmpty/NonEmpty.framework", + "${BUILT_PRODUCTS_DIR}/WolfConcurrency/WolfConcurrency.framework", + "${BUILT_PRODUCTS_DIR}/WolfCore/WolfCore.framework", + "${BUILT_PRODUCTS_DIR}/WolfFoundation/WolfFoundation.framework", + "${BUILT_PRODUCTS_DIR}/WolfNesting/WolfNesting.framework", + "${BUILT_PRODUCTS_DIR}/WolfNumerics/WolfNumerics.framework", + "${BUILT_PRODUCTS_DIR}/WolfOSBridge/WolfOSBridge.framework", + "${BUILT_PRODUCTS_DIR}/WolfPipe/WolfPipe.framework", + "${BUILT_PRODUCTS_DIR}/WolfStrings/WolfStrings.framework", + "${BUILT_PRODUCTS_DIR}/WolfWith/WolfWith.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AirgappedSigning.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Bitcoin.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CBitcoin.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ExtensibleEnumeratedName.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NonEmpty.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfConcurrency.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfFoundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfNesting.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfNumerics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfOSBridge.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfPipe.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfStrings.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WolfWith.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AirgappedSigning_Example/Pods-AirgappedSigning_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 31D8847F2CE4CC7175566907 /* [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-AirgappedSigning_Tests-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; + }; + D1057688AA0D43C557F0FCBA /* [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-AirgappedSigning_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 */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE11AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* AirgappedSigning_Example */; + targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 607FACD91AFB9204008FA782 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 607FACDA1AFB9204008FA782 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 607FACDF1AFB9204008FA782 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_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_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_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_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E267DBE350210DDA6D5F071 /* Pods-AirgappedSigning_Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = AirgappedSigning/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 653EE703F7545ACADEA15A65 /* Pods-AirgappedSigning_Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = AirgappedSigning/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 607FACF31AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4EB2C27D3619D1CA0346AA96 /* Pods-AirgappedSigning_Tests.debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AirgappedSigning_Example.app/AirgappedSigning_Example"; + }; + name = Debug; + }; + 607FACF41AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51400B6C08379C0E6EC2285F /* Pods-AirgappedSigning_Tests.release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AirgappedSigning_Example.app/AirgappedSigning_Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "AirgappedSigning" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "AirgappedSigning_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "AirgappedSigning_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF31AFB9204008FA782 /* Debug */, + 607FACF41AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Example/AirgappedSigning.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/AirgappedSigning.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..282c61b --- /dev/null +++ b/Example/AirgappedSigning.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/AirgappedSigning.xcodeproj/xcshareddata/xcschemes/AirgappedSigning-Example.xcscheme b/Example/AirgappedSigning.xcodeproj/xcshareddata/xcschemes/AirgappedSigning-Example.xcscheme new file mode 100644 index 0000000..5197f00 --- /dev/null +++ b/Example/AirgappedSigning.xcodeproj/xcshareddata/xcschemes/AirgappedSigning-Example.xcscheme @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/AirgappedSigning.xcworkspace/contents.xcworkspacedata b/Example/AirgappedSigning.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ae08cc7 --- /dev/null +++ b/Example/AirgappedSigning.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/AirgappedSigning.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/AirgappedSigning.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/AirgappedSigning.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/AirgappedSigning/AppDelegate.swift b/Example/AirgappedSigning/AppDelegate.swift new file mode 100644 index 0000000..173af77 --- /dev/null +++ b/Example/AirgappedSigning/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// AirgappedSigning +// +// Created by Wolf McNally on 09/15/2018. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + return true + } +} diff --git a/Example/AirgappedSigning/Base.lproj/LaunchScreen.xib b/Example/AirgappedSigning/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..a7af53e --- /dev/null +++ b/Example/AirgappedSigning/Base.lproj/LaunchScreen.xib @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/AirgappedSigning/Base.lproj/Main.storyboard b/Example/AirgappedSigning/Base.lproj/Main.storyboard new file mode 100644 index 0000000..ed717fa --- /dev/null +++ b/Example/AirgappedSigning/Base.lproj/Main.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/AirgappedSigning/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/AirgappedSigning/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..7006c9e --- /dev/null +++ b/Example/AirgappedSigning/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Example/AirgappedSigning/Info.plist b/Example/AirgappedSigning/Info.plist new file mode 100644 index 0000000..eb18faa --- /dev/null +++ b/Example/AirgappedSigning/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + + + diff --git a/Example/AirgappedSigning/ViewController.swift b/Example/AirgappedSigning/ViewController.swift new file mode 100644 index 0000000..f34160c --- /dev/null +++ b/Example/AirgappedSigning/ViewController.swift @@ -0,0 +1,28 @@ +// +// ViewController.swift +// AirgappedSigning +// +// Created by Wolf McNally on 09/15/2018. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit + +class ViewController: UIViewController { +} diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 0000000..a9b1455 --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,13 @@ +use_frameworks! + +platform :ios, '12.0' + +# pod 'Bitcoin', :path => '../../Bitcoin' + +target 'AirgappedSigning_Example' do + pod 'AirgappedSigning', :path => '../' + + target 'AirgappedSigning_Tests' do + inherit! :search_paths + end +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 0000000..bcfacce --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,81 @@ +PODS: + - AirgappedSigning (0.1.0): + - Bitcoin + - NonEmpty + - WolfCore + - Bitcoin (0.5.1): + - CBitcoin + - WolfCore + - CBitcoin (0.5.1) + - ExtensibleEnumeratedName (2.0) + - NonEmpty (0.2.0) + - WolfConcurrency (3.0.2): + - WolfFoundation + - WolfNumerics + - WolfCore (4.0.8): + - ExtensibleEnumeratedName + - WolfConcurrency + - WolfFoundation + - WolfNesting + - WolfNumerics + - WolfOSBridge + - WolfPipe + - WolfStrings + - WolfWith + - WolfFoundation (3.0.3): + - WolfNumerics + - WolfPipe + - WolfNesting (2.0.1) + - WolfNumerics (4.0) + - WolfOSBridge (2.0.0) + - WolfPipe (2.0.0) + - WolfStrings (2.0.0): + - ExtensibleEnumeratedName + - WolfNumerics + - WolfOSBridge + - WolfPipe + - WolfWith + - WolfWith (2.0.1) + +DEPENDENCIES: + - AirgappedSigning (from `../`) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - Bitcoin + - CBitcoin + - ExtensibleEnumeratedName + - NonEmpty + - WolfConcurrency + - WolfCore + - WolfFoundation + - WolfNesting + - WolfNumerics + - WolfOSBridge + - WolfPipe + - WolfStrings + - WolfWith + +EXTERNAL SOURCES: + AirgappedSigning: + :path: "../" + +SPEC CHECKSUMS: + AirgappedSigning: 70ccc68e9502cac566b3b42935dc7f1c200d2378 + Bitcoin: 94763986cdecff4b05b89c84f0afd5439d53ba6b + CBitcoin: 3f0f2e4e211aa10573940d8d9c9b0b0123ad24b5 + ExtensibleEnumeratedName: 50f2d256f6d56baecf681f6fbb6362343dcf885b + NonEmpty: ffdc4086fbb2d7b977529a1b56bd6e7d4514ad76 + WolfConcurrency: 50458b6d801209f89e41d4bb9d57c80c65984770 + WolfCore: aaa510f76683e9f038e878d11356836eb135fd52 + WolfFoundation: c38312563289428f5f0396df9af43bc81918bc53 + WolfNesting: 891247e766da604f0e8124d85a158fe123ceb8d8 + WolfNumerics: 6a57756b668073414da8a6911b86781096aded37 + WolfOSBridge: e9260681790a7c89f725e134c0c1d4172d2d2dc9 + WolfPipe: 18ffb77a188263392893a62630defc2a5b92b4f3 + WolfStrings: edc63c08fbd1149ccbf5eca5020b13465084513b + WolfWith: 2320a88a03a943df9ff1284c9cca10f86cb73c60 + +PODFILE CHECKSUM: cdec0639a121f10bcbb4c9ad550349d37a0d14c9 + +COCOAPODS: 1.7.0.beta.2 diff --git a/Example/Tests/Info.plist b/Example/Tests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Example/Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift new file mode 100644 index 0000000..43955d0 --- /dev/null +++ b/Example/Tests/Tests.swift @@ -0,0 +1,97 @@ +import XCTest +import AirgappedSigning + +class Tests: XCTestCase { + func decodeDocument(from json: String) throws -> Document { + let data = json.data(using: .utf8)! + let document = try JSONDecoder().decode(Document.self, from: data) + try document.check() + return document + } + + func testThrowsOnNoContent() { + let json = """ + { + "header": { + "format": "AirgappedSigning", + "version": 1 + } + } + """ + XCTAssertThrowsError( try decodeDocument(from: json) ) + } + + func testThrowsOnNoHeader() { + let json = """ + { + "recoveryWords": { + "name": "Rainy Day", + "format": "BIP39", + "words": ["panda", "diary", "marriage", "suffer", "basic", "glare", "surge", "auto", "scissors", "describe", "sell", "unique"] + } + } + """ + XCTAssertThrowsError( try decodeDocument(from: json) ) + } + + func testThrowsOnMoreThanOneContent() { + let json = """ + { + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "recoveryWords": { + "name": "Rainy Day", + "format": "BIP39", + "words": ["panda", "diary", "marriage", "suffer", "basic", "glare", "surge", "auto", "scissors", "describe", "sell", "unique"] + }, + "knownReceiver": { + "name": "Example", + "asset": "BTC", + "address": "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" + } + } + """ + XCTAssertThrowsError( try decodeDocument(from: json) ) + } + + func testThrowsOnEmptyWords() { + let json = """ + { + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "recoveryWords": { + "name": "Rainy Day", + "format": "BIP39", + "words": [] + } + } + """ + XCTAssertThrowsError( try decodeDocument(from: json) ) + } + + func test1() throws { + let json = """ + { + "header": { + "format": "AirgappedSigning", + "version": 1 + }, + "recoveryWords": { + "name": "Rainy Day", + "format": "BIP39", + "words": ["panda", "diary", "marriage", "suffer", "basic", "glare", "surge", "auto", "scissors", "describe", "sell", "unique"] + } + } + """ + + let data = json.data(using: .utf8)! + + let decoded = try JSONDecoder().decode(AirgappedSigning.Document.self, from: data) + + dump(decoded) + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20e0ead --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright © 2018 Wolf McNally + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..1509940 --- /dev/null +++ b/Package.swift @@ -0,0 +1,16 @@ +// swift-tools-version:5.0 +import PackageDescription + +let package = Package( + name: "AirgappedSigning", + products: [ + .library( + name: "AirgappedSigning", + targets: ["AirgappedSigning"]), + ], + targets: [ + .target( + name: "AirgappedSigning", + dependencies: []) + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..f49127a --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# AirgappedSigning + +[![CI Status](https://img.shields.io/travis/wolfmcnally/AirgappedSigning.svg?style=flat)](https://travis-ci.org/wolfmcnally/AirgappedSigning) +[![Version](https://img.shields.io/cocoapods/v/AirgappedSigning.svg?style=flat)](https://cocoapods.org/pods/AirgappedSigning) +[![License](https://img.shields.io/cocoapods/l/AirgappedSigning.svg?style=flat)](https://cocoapods.org/pods/AirgappedSigning) +[![Platform](https://img.shields.io/cocoapods/p/AirgappedSigning.svg?style=flat)](https://cocoapods.org/pods/AirgappedSigning) + +## Example + +To run the example project, clone the repo, and run `pod install` from the Example directory first. + +## Requirements + +Swift 4.2 + +## Installation + +AirgappedSigning is available through [CocoaPods](https://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod 'AirgappedSigning' +``` + +## Author + +Wolf McNally, wolf@wolfmcnally.com + +## License + +AirgappedSigning is available under the MIT license. See the LICENSE file for more info. diff --git a/Sources/AirgappedSigning/.gitkeep b/Sources/AirgappedSigning/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Sources/AirgappedSigning/Account.swift b/Sources/AirgappedSigning/Account.swift new file mode 100644 index 0000000..543c0c9 --- /dev/null +++ b/Sources/AirgappedSigning/Account.swift @@ -0,0 +1,35 @@ +// +// Account.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct Account: Codable, Checked { + public var name: String? + public var asset: String + public var hdPublicKey: String + public var index: Int + + public func check() throws { + try checkName(name, context: "Account.name") + try checkAsset(asset, context: "Account") + try checkNotEmpty(hdPublicKey, context: "Account.hdPublicKey") + try checkNotNegative(index, context: "Account.index") + } +} diff --git a/Sources/AirgappedSigning/AirgappedSigningError.swift b/Sources/AirgappedSigning/AirgappedSigningError.swift new file mode 100644 index 0000000..39073aa --- /dev/null +++ b/Sources/AirgappedSigning/AirgappedSigningError.swift @@ -0,0 +1,37 @@ +// +// AirgappedSigningError.swift +// AirgappedSigning +// +// Created by Wolf McNally on 9/15/18. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +public struct AirgappedSigningError: Error, CustomStringConvertible { + public let message: String + + public init(_ message: String) { + self.message = message + } + + public var description: String { + return message + } +} diff --git a/Sources/AirgappedSigning/Checked.swift b/Sources/AirgappedSigning/Checked.swift new file mode 100644 index 0000000..c9e6788 --- /dev/null +++ b/Sources/AirgappedSigning/Checked.swift @@ -0,0 +1,68 @@ +// +// Checked.swift +// AirgappedSigning +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public protocol Checked { + /// Performs second-level validation and throws if it fails. + func check() throws +} + +func checkName(_ name: String?, context: String) throws { + if let name = name { + try checkNotEmpty(name, context: context) + } +} + +func checkAsset(_ asset: String, context: String) throws { + guard ["BTC", "BTCT"].contains(asset) else { + throw AirgappedSigningError("\(context): Unsupported asset: \(asset).") + } +} + +func checkNotEmpty(_ string: String, context: String) throws { + guard !string.isEmpty else { + throw AirgappedSigningError("\(context): Must be non-empty.") + } +} + +func checkNotEmpty(_ data: Data, context: String) throws { + guard !data.isEmpty else { + throw AirgappedSigningError("\(context): Must be non-empty.") + } +} + +func checkNotEmpty(_ collection: C, context: String) throws where C: Collection { + guard !collection.isEmpty else { + throw AirgappedSigningError("\(context): Must be non-empty.") + } +} + +func checkNotNegative(_ n: N, context: String) throws { + guard n >= 0 else { + throw AirgappedSigningError("\(context): Must be non-negative.") + } +} + +func checkPositive(_ n: N, context: String) throws { + guard n > 0 else { + throw AirgappedSigningError("\(context): Must be positive.") + } +} diff --git a/Sources/AirgappedSigning/Document.swift b/Sources/AirgappedSigning/Document.swift new file mode 100644 index 0000000..0f95834 --- /dev/null +++ b/Sources/AirgappedSigning/Document.swift @@ -0,0 +1,42 @@ +// +// Document.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct Document: Codable, Checked { + public var header: Header + + public var multiPart: MultiPart? + public var recoveryWords: RecoveryWords? + public var knownReceiver: KnownReceiver? + public var account: Account? + public var transaction: Transaction? + + public func check() throws { + try header.check() + + let possibleContent: [Checked?] = [multiPart, recoveryWords, knownReceiver, account, transaction] + let content = possibleContent.compactMap { $0 } + guard content.count == 1 else { + throw AirgappedSigningError("Document: Exactly one content type must be included. Got: \(content.count).") + } + try content.first!.check() + } +} diff --git a/Sources/AirgappedSigning/Header.swift b/Sources/AirgappedSigning/Header.swift new file mode 100644 index 0000000..4d81d74 --- /dev/null +++ b/Sources/AirgappedSigning/Header.swift @@ -0,0 +1,37 @@ +// +// Header.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct Header: Codable, Checked { + public var format: Format + public var version: Version + + public func check() throws { + } + + public enum Format: String, Codable { + case AirgappedSigning + } + + public enum Version: Int, Codable { + case v1 = 1 + } +} diff --git a/Sources/AirgappedSigning/KnownReceiver.swift b/Sources/AirgappedSigning/KnownReceiver.swift new file mode 100644 index 0000000..51f99ec --- /dev/null +++ b/Sources/AirgappedSigning/KnownReceiver.swift @@ -0,0 +1,33 @@ +// +// KnownReceiver.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct KnownReceiver: Codable, Checked { + public var name: String? + public var asset: String + public var address: String + + public func check() throws { + try checkName(name, context: "KnownReceiver.name") + try checkAsset(asset, context: "KnownReceiver") + try checkNotEmpty(address, context: "KnownReceiver.address") + } +} diff --git a/Sources/AirgappedSigning/MultiPart.swift b/Sources/AirgappedSigning/MultiPart.swift new file mode 100644 index 0000000..7fcf0d0 --- /dev/null +++ b/Sources/AirgappedSigning/MultiPart.swift @@ -0,0 +1,34 @@ +// +// MultiPart.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct MultiPart: Codable, Checked { + public var uid: UUID + public var part: Int + public var count: Int + public var data: Data + + public func check() throws { + try checkNotNegative(part, context: "Multipart.part") + try checkPositive(count, context: "Multipart.count") + try checkNotEmpty(data, context: "Multipart.data") + } +} diff --git a/Sources/AirgappedSigning/RecoveryWords.swift b/Sources/AirgappedSigning/RecoveryWords.swift new file mode 100644 index 0000000..5cef3a4 --- /dev/null +++ b/Sources/AirgappedSigning/RecoveryWords.swift @@ -0,0 +1,37 @@ +// +// RecoveryWords.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +import NonEmpty + +public struct RecoveryWords: Codable, Checked { + public var name: String? + public var format: Format + public var words: [String] + + public func check() throws { + try checkName(name, context: "RecoveryWords.name") + try checkNotEmpty(words, context: "RecoveryWords.words") + } + + public enum Format: String, Codable { + case BIP39 + } +} diff --git a/Sources/AirgappedSigning/Transaction.swift b/Sources/AirgappedSigning/Transaction.swift new file mode 100644 index 0000000..6b2d7bb --- /dev/null +++ b/Sources/AirgappedSigning/Transaction.swift @@ -0,0 +1,98 @@ +// +// Transaction.swift +// AirgappedSigning_Example +// +// Created by Wolf McNally on 4/23/19. +// +// Copyright © 2019 Blockchain Commons. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +public struct Transaction: Codable, Checked { + public var uid: UUID + public var asset: String? + public var inputs: [Input]? + public var outputs: [Output]? + public var inputSignatures: [InputSignature]? + + public func check() throws { + if let asset = asset { + try checkAsset(asset, context: "Transaction") + } + if let inputs = inputs { + try checkNotEmpty(inputs, context: "Transaction.inputs") + try inputs.forEach { try $0.check() } + } + if let outputs = outputs { + try checkNotEmpty(outputs, context: "Transaction.outputs") + try outputs.forEach { try $0.check() } + } + if let inputSignatures = inputSignatures { + try checkNotEmpty(inputSignatures, context: "Transaction.inputSignatures") + try inputSignatures.forEach { try $0.check() } + } + } + + public struct Derivation: Codable, Checked { + public var accountIndex: Int + public var addressIndex: Int + + public func check() throws { + try checkNotNegative(accountIndex, context: "Derivation.accountIndex") + try checkNotNegative(addressIndex, context: "Derivation.addressIndex") + } + } + + public struct Input: Codable, Checked { + public var uid: UUID + public var txHash: String + public var inputIndex: Int + public var sender: String + public var derivation: Derivation + public var amount: UInt64 + + public func check() throws { + try checkNotEmpty(txHash, context: "Input.txHash") + try checkNotNegative(inputIndex, context: "Input.inputIndex") + try checkNotEmpty(sender, context: "Input.sender") + try derivation.check() + try checkPositive(amount, context: "Input.amount") + } + } + + public struct Output: Codable, Checked { + public var uid: UUID + public var receiver: String + public var amount: UInt64 + public var derivation: Derivation? + + public func check() throws { + try checkNotEmpty(receiver, context: "Output.receiver") + try checkPositive(amount, context: "Output.amount") + try derivation?.check() + } + } + + public struct InputSignature: Codable, Checked { + public var uid: UUID + public var ecPublicKey: Data + public var ecSignature: Data + + public func check() throws { + try checkNotEmpty(ecPublicKey, context: "InputSignature.ecPublicKey") + try checkNotEmpty(ecSignature, context: "InputSignature.ecSignature") + } + } +}