From f2d70b8dc0d8fbfd5d5723a898ffeb9cba7ae0a6 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 3 Mar 2015 08:51:58 +0100 Subject: [PATCH 001/163] link to new location of the glossary --- GLOSSARY.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GLOSSARY.md b/GLOSSARY.md index 5279104d..c31a47cd 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -1,6 +1,14 @@ Bitcoin Glossary ================ +### ATTENTION + +This glossary has been moved to [Oleg Andreev's Bitcoin Papers](https://github.com/oleganza/bitcoin-papers/blob/master/BitcoinGlossary.md). + +The content below will not be updated and eventually will be removed. + +*** + Some unusual terms are frequently used in Bitcoin documentation and discussions like *tx* or *coinbase*. Or words like *scriptPubKey* were badly chosen and now deserve some extra explanation. This glossary will help you understand exact meaning of all Bitcoin-related terms. If you find an inaccuracy, please report it to oleganza@gmail.com or clone this repo and submit pull requests. Thanks! From 7ca98c8336c835a6cdb20c38b9d24793675321bc Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 6 Mar 2015 08:49:01 +0100 Subject: [PATCH 002/163] optimized hash functions to read memory-mapped NSDatas efficiently (chunk by chunk) --- CoreBitcoin.podspec | 2 +- CoreBitcoin/BTCData+Tests.m | 3 ++ CoreBitcoin/BTCData.m | 57 ++++++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/CoreBitcoin.podspec b/CoreBitcoin.podspec index 17a3fcfe..e246f137 100644 --- a/CoreBitcoin.podspec +++ b/CoreBitcoin.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreBitcoin" - s.version = "0.6.3" + s.version = "0.6.4" s.summary = "CoreBitcoin is an implementation of Bitcoin protocol in Objective-C." s.description = <<-DESC CoreBitcoin is a complete toolkit to work with Bitcoin data structures. diff --git a/CoreBitcoin/BTCData+Tests.m b/CoreBitcoin/BTCData+Tests.m index b4446ca8..697f4b9c 100644 --- a/CoreBitcoin/BTCData+Tests.m +++ b/CoreBitcoin/BTCData+Tests.m @@ -19,6 +19,9 @@ + (void) runAllTests NSAssert([BTCDataWithUTF8CString("hello").SHA256.hex isEqual:@"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"], @"Test vector"); + NSAssert([BTCSHA256Concat(BTCDataWithUTF8CString("hel"), BTCDataWithUTF8CString("lo")).hex + isEqual:@"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"], @"Test vector"); + NSAssert([BTCDataWithUTF8CString("hello").SHA256.SHA256.hex isEqual:@"9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50"], @"Test vector"); NSAssert([BTCDataWithUTF8CString("hello").BTCHash256.hex diff --git a/CoreBitcoin/BTCData.m b/CoreBitcoin/BTCData.m index 58873a1d..c332f4a5 100644 --- a/CoreBitcoin/BTCData.m +++ b/CoreBitcoin/BTCData.m @@ -298,7 +298,13 @@ BOOL BTCDataClear(NSData* data) { if (!data) return nil; unsigned char digest[CC_SHA1_DIGEST_LENGTH]; - CC_SHA1([data bytes], (CC_LONG)[data length], digest); + + __block CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA1_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + CC_SHA1_Final(digest, &ctx); NSMutableData* result = [NSMutableData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; BTCSecureMemset(digest, 0, CC_SHA1_DIGEST_LENGTH); @@ -309,7 +315,13 @@ BOOL BTCDataClear(NSData* data) { if (!data) return nil; unsigned char digest[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256([data bytes], (CC_LONG)[data length], digest); + + __block CC_SHA256_CTX ctx; + CC_SHA256_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + CC_SHA256_Final(digest, &ctx); NSMutableData* result = [NSMutableData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; BTCSecureMemset(digest, 0, CC_SHA256_DIGEST_LENGTH); @@ -321,10 +333,14 @@ BOOL BTCDataClear(NSData* data) if (!data1 || !data2) return nil; unsigned char digest[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256_CTX ctx; + __block CC_SHA256_CTX ctx; CC_SHA256_Init(&ctx); - CC_SHA256_Update(&ctx, [data1 bytes], (CC_LONG)[data1 length]); - CC_SHA256_Update(&ctx, [data2 bytes], (CC_LONG)[data2 length]); + [data1 enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + [data2 enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; CC_SHA256_Final(digest, &ctx); NSMutableData* result = [NSMutableData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; @@ -337,7 +353,12 @@ BOOL BTCDataClear(NSData* data) if (!data) return nil; unsigned char digest1[CC_SHA256_DIGEST_LENGTH]; unsigned char digest2[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256([data bytes], (CC_LONG)[data length], digest1); + __block CC_SHA256_CTX ctx; + CC_SHA256_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + CC_SHA256_Final(digest1, &ctx); CC_SHA256(digest1, CC_SHA256_DIGEST_LENGTH, digest2); NSMutableData* result = [NSMutableData dataWithBytes:digest2 length:CC_SHA256_DIGEST_LENGTH]; BTCSecureMemset(digest1, 0, CC_SHA256_DIGEST_LENGTH); @@ -352,10 +373,14 @@ BOOL BTCDataClear(NSData* data) unsigned char digest1[CC_SHA256_DIGEST_LENGTH]; unsigned char digest2[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256_CTX ctx; + __block CC_SHA256_CTX ctx; CC_SHA256_Init(&ctx); - CC_SHA256_Update(&ctx, [data1 bytes], (CC_LONG)[data1 length]); - CC_SHA256_Update(&ctx, [data2 bytes], (CC_LONG)[data2 length]); + [data1 enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + [data2 enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; CC_SHA256_Final(digest1, &ctx); CC_SHA256(digest1, CC_SHA256_DIGEST_LENGTH, digest2); @@ -408,7 +433,12 @@ BOOL BTCDataClear(NSData* data) { if (!data) return nil; unsigned char digest[RIPEMD160_DIGEST_LENGTH]; - RIPEMD160([data bytes], (size_t)[data length], digest); + __block RIPEMD160_CTX ctx; + RIPEMD160_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + RIPEMD160_Update(&ctx, bytes, (size_t)byteRange.length); + }]; + RIPEMD160_Final(digest, &ctx); NSMutableData* result = [NSMutableData dataWithBytes:digest length:RIPEMD160_DIGEST_LENGTH]; BTCSecureMemset(digest, 0, RIPEMD160_DIGEST_LENGTH); @@ -420,7 +450,12 @@ BOOL BTCDataClear(NSData* data) if (!data) return nil; unsigned char digest1[CC_SHA256_DIGEST_LENGTH]; unsigned char digest2[RIPEMD160_DIGEST_LENGTH]; - CC_SHA256([data bytes], (CC_LONG)[data length], digest1); + __block CC_SHA256_CTX ctx; + CC_SHA256_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA256_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + CC_SHA256_Final(digest1, &ctx); RIPEMD160(digest1, CC_SHA256_DIGEST_LENGTH, digest2); NSMutableData* result = [NSMutableData dataWithBytes:digest2 length:RIPEMD160_DIGEST_LENGTH]; From c4e8ee521d5c21fa59d7b335a968f214ccdec9e5 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 6 Mar 2015 17:24:24 +0100 Subject: [PATCH 003/163] added BTCMerkleTree implementation --- CoreBitcoin.xcodeproj/project.pbxproj | 26 ++++++++++++++++ CoreBitcoin/BTCMerkleTree+Tests.h | 9 ++++++ CoreBitcoin/BTCMerkleTree+Tests.m | 45 +++++++++++++++++++++++++++ CoreBitcoin/BTCMerkleTree.h | 13 ++++++++ CoreBitcoin/BTCMerkleTree.m | 41 ++++++++++++++++++++++++ CoreBitcoin/CoreBitcoin.h | 1 + UnitTests/main.m | 2 ++ 7 files changed, 137 insertions(+) create mode 100644 CoreBitcoin/BTCMerkleTree+Tests.h create mode 100644 CoreBitcoin/BTCMerkleTree+Tests.m create mode 100644 CoreBitcoin/BTCMerkleTree.h create mode 100644 CoreBitcoin/BTCMerkleTree.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 57aeac90..f91e9a09 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -134,6 +134,15 @@ 20584B2218CD0DA000FDD410 /* BTC256.m in Sources */ = {isa = PBXBuildFile; fileRef = 20584B1C18CD0DA000FDD410 /* BTC256.m */; }; 20584B2318CD0DA000FDD410 /* BTC256.m in Sources */ = {isa = PBXBuildFile; fileRef = 20584B1C18CD0DA000FDD410 /* BTC256.m */; }; 20584B2418CD0DA000FDD410 /* BTC256.m in Sources */ = {isa = PBXBuildFile; fileRef = 20584B1C18CD0DA000FDD410 /* BTC256.m */; }; + 2060A2801AAA077A004531FD /* BTCMerkleTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 2060A27E1AAA077A004531FD /* BTCMerkleTree.h */; }; + 2060A2811AAA077A004531FD /* BTCMerkleTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 2060A27E1AAA077A004531FD /* BTCMerkleTree.h */; }; + 2060A2821AAA077A004531FD /* BTCMerkleTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 2060A27E1AAA077A004531FD /* BTCMerkleTree.h */; }; + 2060A2831AAA077A004531FD /* BTCMerkleTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */; }; + 2060A2841AAA077A004531FD /* BTCMerkleTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */; }; + 2060A2851AAA077A004531FD /* BTCMerkleTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */; }; + 2060A2861AAA077A004531FD /* BTCMerkleTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */; }; + 2060A2871AAA077A004531FD /* BTCMerkleTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */; }; + 2060A28A1AAA09A3004531FD /* BTCMerkleTree+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2060A2891AAA09A3004531FD /* BTCMerkleTree+Tests.m */; }; 2061D1D61A2CA771004F1E40 /* BTCHashID.h in Headers */ = {isa = PBXBuildFile; fileRef = 2061D1D41A2CA771004F1E40 /* BTCHashID.h */; }; 2061D1D71A2CA771004F1E40 /* BTCHashID.h in Headers */ = {isa = PBXBuildFile; fileRef = 2061D1D41A2CA771004F1E40 /* BTCHashID.h */; }; 2061D1D81A2CA771004F1E40 /* BTCHashID.h in Headers */ = {isa = PBXBuildFile; fileRef = 2061D1D41A2CA771004F1E40 /* BTCHashID.h */; }; @@ -420,6 +429,10 @@ 2057A9CC17CD555F00353D54 /* BTCKey+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCKey+Tests.m"; sourceTree = ""; }; 20584B1B18CD0DA000FDD410 /* BTC256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTC256.h; sourceTree = ""; }; 20584B1C18CD0DA000FDD410 /* BTC256.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTC256.m; sourceTree = ""; }; + 2060A27E1AAA077A004531FD /* BTCMerkleTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCMerkleTree.h; sourceTree = ""; }; + 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCMerkleTree.m; sourceTree = ""; }; + 2060A2881AAA09A3004531FD /* BTCMerkleTree+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCMerkleTree+Tests.h"; sourceTree = ""; }; + 2060A2891AAA09A3004531FD /* BTCMerkleTree+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCMerkleTree+Tests.m"; sourceTree = ""; }; 2061D1D41A2CA771004F1E40 /* BTCHashID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCHashID.h; sourceTree = ""; }; 2061D1D51A2CA771004F1E40 /* BTCHashID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCHashID.m; sourceTree = ""; }; 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreBitcoinOSX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -828,6 +841,10 @@ 20D09C5018BC012700794209 /* BTCBlockHeader.m */, 20D09C5918BC016B00794209 /* BTCBlock.h */, 20D09C5A18BC016B00794209 /* BTCBlock.m */, + 2060A27E1AAA077A004531FD /* BTCMerkleTree.h */, + 2060A27F1AAA077A004531FD /* BTCMerkleTree.m */, + 2060A2881AAA09A3004531FD /* BTCMerkleTree+Tests.h */, + 2060A2891AAA09A3004531FD /* BTCMerkleTree+Tests.m */, 20B9646D17BADE8F008161BB /* BTCOpcode.h */, 20B9646E17BADECE008161BB /* BTCOpcode.m */, 207B2602188C440600916AE6 /* BTCSignatureHashType.h */, @@ -912,6 +929,7 @@ 207B2606188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA518BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, 207646F11A0A8AE4000F00F2 /* BTCBitcoinURL.h in Headers */, + 2060A2811AAA077A004531FD /* BTCMerkleTree.h in Headers */, 20D09BC018BA2FEB00794209 /* BTCSignatureHashType.h in Headers */, 20B8AB96189EE88300008138 /* BTCKeychain.h in Headers */, 20148C351835650B00E68E9C /* BTCBigNumber+Tests.h in Headers */, @@ -966,6 +984,7 @@ 207B2607188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA618BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, 207646F21A0A8AE4000F00F2 /* BTCBitcoinURL.h in Headers */, + 2060A2821AAA077A004531FD /* BTCMerkleTree.h in Headers */, 20D09BBF18BA2FEB00794209 /* BTCSignatureHashType.h in Headers */, 20B8AB97189EE88300008138 /* BTCKeychain.h in Headers */, 20148CDF183643FC00E68E9C /* BTCBigNumber+Tests.h in Headers */, @@ -1020,6 +1039,7 @@ 207B2605188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA418BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, 207646F01A0A8AE4000F00F2 /* BTCBitcoinURL.h in Headers */, + 2060A2801AAA077A004531FD /* BTCMerkleTree.h in Headers */, 20D09BBE18BA2FEA00794209 /* BTCSignatureHashType.h in Headers */, 20B8AB95189EE88300008138 /* BTCKeychain.h in Headers */, 206B01451835484300878B8D /* BTCBase58+Tests.h in Headers */, @@ -1187,6 +1207,7 @@ 20148B0518355DAD00E68E9C /* BTCData.m in Sources */, 20D09C5618BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D7FF19E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 2060A2851AAA077A004531FD /* BTCMerkleTree.m in Sources */, 207647091A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148B0718355DAD00E68E9C /* NSData+BTCData.m in Sources */, 207647131A0A8D16000F00F2 /* BTCQRCode.m in Sources */, @@ -1230,6 +1251,7 @@ 20148C13183563D000E68E9C /* BTCData.m in Sources */, 20D09C5718BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D80019E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 2060A2861AAA077A004531FD /* BTCMerkleTree.m in Sources */, 2076470A1A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148C15183563D000E68E9C /* NSData+BTCData.m in Sources */, 207647141A0A8D16000F00F2 /* BTCQRCode.m in Sources */, @@ -1273,6 +1295,7 @@ 20148CBE183643E700E68E9C /* BTCData.m in Sources */, 20D09C5818BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D80119E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 2060A2871AAA077A004531FD /* BTCMerkleTree.m in Sources */, 2076470B1A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148CC0183643E700E68E9C /* NSData+BTCData.m in Sources */, 207647151A0A8D16000F00F2 /* BTCQRCode.m in Sources */, @@ -1316,6 +1339,7 @@ 206B015D1835485D00878B8D /* BTCBigNumber.m in Sources */, 20D09C5518BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D7FE19E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 2060A2841AAA077A004531FD /* BTCMerkleTree.m in Sources */, 207647081A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 206B01561835485D00878B8D /* NSData+BTCData.m in Sources */, 207647121A0A8D16000F00F2 /* BTCQRCode.m in Sources */, @@ -1370,6 +1394,7 @@ 2084DD8C17B8FF76005AC9E6 /* BTCProtocolSerialization.m in Sources */, 207B2608188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 2084DD8D17B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.m in Sources */, + 2060A2831AAA077A004531FD /* BTCMerkleTree.m in Sources */, 20D008C218D1AFA800079B79 /* BTC256+Tests.m in Sources */, 2054DC781950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, 200756DA1A5D6A44009A24A9 /* BTCPriceSource+Tests.m in Sources */, @@ -1380,6 +1405,7 @@ 207647111A0A8D16000F00F2 /* BTCQRCode.m in Sources */, 2084DD9017B8FF76005AC9E6 /* BTCTransactionInput.m in Sources */, 2057A9CD17CD555F00353D54 /* BTCKey+Tests.m in Sources */, + 2060A28A1AAA09A3004531FD /* BTCMerkleTree+Tests.m in Sources */, 2084DD9117B8FF76005AC9E6 /* BTCTransactionOutput.m in Sources */, 20B5A64018924F350035582D /* BTCTransaction+Tests.m in Sources */, 209D1E1518D48EA200293483 /* BTCNetwork.m in Sources */, diff --git a/CoreBitcoin/BTCMerkleTree+Tests.h b/CoreBitcoin/BTCMerkleTree+Tests.h new file mode 100644 index 00000000..fa5980e9 --- /dev/null +++ b/CoreBitcoin/BTCMerkleTree+Tests.h @@ -0,0 +1,9 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCMerkleTree.h" + +@interface BTCMerkleTree (Tests) + ++ (void) runAllTests; + +@end diff --git a/CoreBitcoin/BTCMerkleTree+Tests.m b/CoreBitcoin/BTCMerkleTree+Tests.m new file mode 100644 index 00000000..a03495c9 --- /dev/null +++ b/CoreBitcoin/BTCMerkleTree+Tests.m @@ -0,0 +1,45 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCData.h" +#import "BTCMerkleTree+Tests.h" + +@implementation BTCMerkleTree (Tests) + ++ (void) runAllTests { + + { + BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:nil]; + NSAssert(tree == nil, @"Empty tree is not allowed"); + } + + { + BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[]]; + NSAssert(tree == nil, @"Empty tree is not allowed"); + } + + { + NSData* a = BTCDataFromHex(@"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"); + BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a]]; + NSAssert([tree.merkleRoot isEqualTo:a], @"One-hash tree should have the root == that hash"); + } + + { + NSData* a = BTCDataFromHex(@"9c2e4d8fe97d881430de4e754b4205b9c27ce96715231cffc4337340cb110280"); + NSData* b = BTCDataFromHex(@"0c08173828583fc6ecd6ecdbcca7b6939c49c242ad5107e39deb7b0a5996b903"); + NSData* r = BTCDataFromHex(@"7de236613dd3d9fa1d86054a84952f1e0df2f130546b394a4d4dd7b76997f607"); + BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a, b]]; + NSAssert([tree.merkleRoot isEqualTo:r], @"Two-hash tree should have the root == Hash(a+b)"); + } + + { + NSData* a = BTCDataFromHex(@"9c2e4d8fe97d881430de4e754b4205b9c27ce96715231cffc4337340cb110280"); + NSData* b = BTCDataFromHex(@"0c08173828583fc6ecd6ecdbcca7b6939c49c242ad5107e39deb7b0a5996b903"); + NSData* c = BTCDataFromHex(@"80903da4e6bbdf96e8ff6fc3966b0cfd355c7e860bdd1caa8e4722d9230e40ac"); + NSData* r = BTCDataFromHex(@"5b7534123197114fa7e7459075f39d89ffab74b5c3f31fad48a025b931ff5a01"); + BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a, b, c]]; + NSAssert([tree.merkleRoot isEqualTo:r], @"Root(a,b,c) == Hash(Hash(a+b)+Hash(c+c))"); + } + +} + +@end diff --git a/CoreBitcoin/BTCMerkleTree.h b/CoreBitcoin/BTCMerkleTree.h new file mode 100644 index 00000000..368abe6d --- /dev/null +++ b/CoreBitcoin/BTCMerkleTree.h @@ -0,0 +1,13 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import + +@interface BTCMerkleTree : NSObject + +@property(nonatomic, readonly) NSData* merkleRoot; + +- (id) initWithHashes:(NSArray*)hashes; + +- (id) initWithTransactions:(NSArray*)transactions; + +@end diff --git a/CoreBitcoin/BTCMerkleTree.m b/CoreBitcoin/BTCMerkleTree.m new file mode 100644 index 00000000..55d0fb2a --- /dev/null +++ b/CoreBitcoin/BTCMerkleTree.m @@ -0,0 +1,41 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCMerkleTree.h" +#import "BTCData.h" + +@interface BTCMerkleTree () +@property(nonatomic) NSArray* hashes; +@end + +@implementation BTCMerkleTree + +- (id) initWithHashes:(NSArray*)hashes { + if (hashes.count == 0) return nil; + if (self = [super init]) { + self.hashes = hashes; + } + return self; +} + +- (id) initWithTransactions:(NSArray*)transactions { + if (transactions.count == 0) return nil; + return [self initWithHashes:[transactions valueForKey:@"transactionHash"]]; +} + +- (NSData*) merkleRoot { + // Based on original Satoshi implementation. + NSMutableArray* tree = [self.hashes mutableCopy]; + + NSInteger j = 0; + for (NSInteger length = (NSInteger)tree.count; length > 1; length = (length + 1) / 2) { + for (NSInteger i = 0; i < length; i += 2) { + NSInteger i2 = MIN(i + 1, length - 1); + NSData* hash = BTCHash256Concat(tree[j+i], tree[j+i2]); + [tree addObject:hash]; + } + j += length; + } + return tree.lastObject; +} + +@end diff --git a/CoreBitcoin/CoreBitcoin.h b/CoreBitcoin/CoreBitcoin.h index bd53f6e6..697c5365 100644 --- a/CoreBitcoin/CoreBitcoin.h +++ b/CoreBitcoin/CoreBitcoin.h @@ -5,6 +5,7 @@ #import #import #import +#import #import #import #import diff --git a/UnitTests/main.m b/UnitTests/main.m index baebdd78..0f715da0 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -16,6 +16,7 @@ #import "BTCTransaction+Tests.h" #import "BTCBlockchainInfo+Tests.h" #import "BTCPriceSource+Tests.h" +#import "BTCMerkleTree+Tests.h" int main(int argc, const char * argv[]) { @@ -34,6 +35,7 @@ int main(int argc, const char * argv[]) [BTCBlindSignature runAllTests]; [BTCEncryptedMessage runAllTests]; [BTCScript runAllTests]; + [BTCMerkleTree runAllTests]; [BTCBlockchainInfo runAllTests]; [BTCPriceSource runAllTests]; [BTCTransaction runAllTests]; From d9cbee4864cef361162276503d379140839083fd Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 6 Mar 2015 17:25:22 +0100 Subject: [PATCH 004/163] bumped to 0.6.5 --- CoreBitcoin.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreBitcoin.podspec b/CoreBitcoin.podspec index e246f137..a21eecda 100644 --- a/CoreBitcoin.podspec +++ b/CoreBitcoin.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreBitcoin" - s.version = "0.6.4" + s.version = "0.6.5" s.summary = "CoreBitcoin is an implementation of Bitcoin protocol in Objective-C." s.description = <<-DESC CoreBitcoin is a complete toolkit to work with Bitcoin data structures. From 96fb397f3e8e9942f1faff53afbe2bb819d5de45 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sat, 7 Mar 2015 11:19:30 +0100 Subject: [PATCH 005/163] added tail mutation checks to BTCMerkleTree --- CoreBitcoin/BTCMerkleTree.h | 12 +++++- CoreBitcoin/BTCMerkleTree.m | 74 ++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/CoreBitcoin/BTCMerkleTree.h b/CoreBitcoin/BTCMerkleTree.h index 368abe6d..db14b04f 100644 --- a/CoreBitcoin/BTCMerkleTree.h +++ b/CoreBitcoin/BTCMerkleTree.h @@ -4,10 +4,20 @@ @interface BTCMerkleTree : NSObject +// Returns the merkle root of the tree, a 256-bit hash. @property(nonatomic, readonly) NSData* merkleRoot; +// Returns YES if the merkle tree has duplicate items in the tail that cause merkle root collision. +// See also CVE-2012-2459. +@property(nonatomic, readonly) BOOL hasTailDuplicates; + +// Builds a merkle tree based on raw hashes. - (id) initWithHashes:(NSArray*)hashes; -- (id) initWithTransactions:(NSArray*)transactions; +// Builds a merkle tree based on transaction hashes. +- (id) initWithTransactions:(NSArray* /* [BTCTransaction] */)transactions; + +// Builds a merkle tree based on BTCHash256 hashes of each NSData item. +- (id) initWithDataItems:(NSArray* /* [NSData] */)dataItems; @end diff --git a/CoreBitcoin/BTCMerkleTree.m b/CoreBitcoin/BTCMerkleTree.m index 55d0fb2a..bb650b92 100644 --- a/CoreBitcoin/BTCMerkleTree.m +++ b/CoreBitcoin/BTCMerkleTree.m @@ -4,6 +4,8 @@ #import "BTCData.h" @interface BTCMerkleTree () +@property(nonatomic, readwrite) NSData* merkleRoot; +@property(nonatomic, readwrite) BOOL hasTailDuplicates; @property(nonatomic) NSArray* hashes; @end @@ -22,18 +24,80 @@ - (id) initWithTransactions:(NSArray*)transactions { return [self initWithHashes:[transactions valueForKey:@"transactionHash"]]; } +- (id) initWithDataItems:(NSArray* /* [NSData] */)dataItems { + if (dataItems.count == 0) return nil; + NSMutableArray* hashes = [NSMutableArray arrayWithCapacity:dataItems.count]; + for (NSData* data in dataItems) { + [hashes addObject:BTCHash256(data)]; + } + return [self initWithHashes:hashes]; +} + - (NSData*) merkleRoot { - // Based on original Satoshi implementation. + if (!_merkleRoot) { + _merkleRoot = [self computeMerkleRoot]; + } + return _merkleRoot; +} + +- (BOOL) hasTailDuplicates { + if (!_merkleRoot) { + _merkleRoot = [self computeMerkleRoot]; + } + return _hasTailDuplicates; +} + +- (NSData*) computeMerkleRoot { + // Based on original Satoshi implementation + vulnerability fix: + /* WARNING! If you're reading this because you're learning about crypto + and/or designing a new system that will use merkle trees, keep in mind + that the following merkle tree algorithm has a serious flaw related to + duplicate txids, resulting in a vulnerability (CVE-2012-2459). + + The reason is that if the number of hashes in the list at a given time + is odd, the last one is duplicated before computing the next level (which + is unusual in Merkle trees). This results in certain sequences of + transactions leading to the same merkle root. For example, these two + trees: + + A A + / \ / \ + B C B C + / \ | / \ / \ + D E F D E F F + / \ / \ / \ / \ / \ / \ / \ + 1 2 3 4 5 6 1 2 3 4 5 6 5 6 + + for transaction lists [1,2,3,4,5,6] and [1,2,3,4,5,6,5,6] (where 5 and + 6 are repeated) result in the same root hash A (because the hash of both + of (F) and (F,F) is C). + + The vulnerability results from being able to send a block with such a + transaction list, with the same merkle root, and the same block hash as + the original without duplication, resulting in failed validation. If the + receiving node proceeds to mark that block as permanently invalid + however, it will fail to accept further unmodified (and thus potentially + valid) versions of the same block. We defend against this by detecting + the case where we would hash two identical hashes at the end of the list + together, and treating that identically to the block having an invalid + merkle root. Assuming no double-SHA256 collisions, this will detect all + known ways of changing the transactions without affecting the merkle + root. + */ NSMutableArray* tree = [self.hashes mutableCopy]; NSInteger j = 0; - for (NSInteger length = (NSInteger)tree.count; length > 1; length = (length + 1) / 2) { - for (NSInteger i = 0; i < length; i += 2) { - NSInteger i2 = MIN(i + 1, length - 1); + for (NSInteger size = (NSInteger)tree.count; size > 1; size = (size + 1) / 2) { + for (NSInteger i = 0; i < size; i += 2) { + NSInteger i2 = MIN(i + 1, size - 1); + if (i2 == i + 1 && i2 + 1 == size && [tree[j+i] isEqual:tree[j+i2]]) { + // Two identical hashes at the end of the list at a particular level. + _hasTailDuplicates = YES; + } NSData* hash = BTCHash256Concat(tree[j+i], tree[j+i2]); [tree addObject:hash]; } - j += length; + j += size; } return tree.lastObject; } From 9f2397829e27bff9812b81be1cb6d78a088a6246 Mon Sep 17 00:00:00 2001 From: Mark Pfluger Date: Tue, 17 Mar 2015 11:51:52 -0400 Subject: [PATCH 006/163] Use xcode command line tools to get developer path and SKD versions --- update_openssl.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/update_openssl.sh b/update_openssl.sh index ef635ba4..2950c584 100755 --- a/update_openssl.sh +++ b/update_openssl.sh @@ -8,10 +8,10 @@ OPENSSL_VERSION="1.0.1e" -DEVELOPER="/Applications/Xcode.app/Contents/Developer" +DEVELOPER=`xcode-select -print-path` -IOS_SDK_VERSION="8.1" -OSX_SDK_VERSION="10.9" +IOS_SDK_VERSION=`xcrun -sdk iphoneos --show-sdk-version` +OSX_SDK_VERSION=`xcrun -sdk macosx --show-sdk-version` WORKDIR=${PWD} From 34a06b7858f93e2894f27aebee6bdb9c27eda2c2 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 27 Mar 2015 10:54:58 +0100 Subject: [PATCH 007/163] added BIP70 implementation --- CoreBitcoin.xcodeproj/project.pbxproj | 60 ++ CoreBitcoin/BTCEncryptedBackup.h | 12 + CoreBitcoin/BTCEncryptedBackup.m | 29 + CoreBitcoin/BTCPaymentProtocol.h | 211 +++++++ CoreBitcoin/BTCPaymentProtocol.m | 782 ++++++++++++++++++++++++++ CoreBitcoin/BTCProtocolBuffers.h | 29 + CoreBitcoin/BTCProtocolBuffers.m | 97 ++++ 7 files changed, 1220 insertions(+) create mode 100644 CoreBitcoin/BTCEncryptedBackup.h create mode 100644 CoreBitcoin/BTCEncryptedBackup.m create mode 100644 CoreBitcoin/BTCPaymentProtocol.h create mode 100644 CoreBitcoin/BTCPaymentProtocol.m create mode 100644 CoreBitcoin/BTCProtocolBuffers.h create mode 100644 CoreBitcoin/BTCProtocolBuffers.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index f91e9a09..d5f46555 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -266,6 +266,14 @@ 2084DD9317B8FF76005AC9E6 /* BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 2084DD8517B8FF76005AC9E6 /* BTCData.m */; }; 2084DD9817B8FFA2005AC9E6 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2084DD9617B8FFA2005AC9E6 /* Security.framework */; }; 208642BB194C9EAF003A3A2C /* BTCBlindSignature+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 208642BA194C9EAF003A3A2C /* BTCBlindSignature+Tests.m */; }; + 208E30361AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */ = {isa = PBXBuildFile; fileRef = 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */; }; + 208E30371AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */ = {isa = PBXBuildFile; fileRef = 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */; }; + 208E30381AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */ = {isa = PBXBuildFile; fileRef = 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */; }; + 208E30391AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; + 208E303A1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; + 208E303B1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; + 208E303C1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; + 208E303D1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; 209D1E1218D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; 209D1E1318D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; 209D1E1418D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; @@ -282,6 +290,22 @@ 209D1E2118D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; 209D1E2218D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; 209D1E2318D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; + 20A443B51AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; + 20A443B61AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; + 20A443B71AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; + 20A443B81AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */; }; + 20A443B91AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */; }; + 20A443BA1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */; }; + 20A443BB1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */; }; + 20A443BC1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */; }; + 20A443BD1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */; }; + 20A443BE1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */; }; + 20A443BF1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */; }; + 20A443C01AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; + 20A443C11AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; + 20A443C21AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; + 20A443C31AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; + 20A443C41AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; 20B5A64018924F350035582D /* BTCTransaction+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */; }; 20B66C8717BFE4F9007128CE /* BTCErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B66C8617BFE4F9007128CE /* BTCErrors.m */; }; 20B8AB92189E7E0100008138 /* BTCCurvePoint+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B8AB91189E7E0100008138 /* BTCCurvePoint+Tests.m */; }; @@ -493,12 +517,18 @@ 2084DD9F17B90174005AC9E6 /* update_openssl.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = update_openssl.sh; sourceTree = SOURCE_ROOT; }; 208642B9194C9EAF003A3A2C /* BTCBlindSignature+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCBlindSignature+Tests.h"; sourceTree = ""; }; 208642BA194C9EAF003A3A2C /* BTCBlindSignature+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCBlindSignature+Tests.m"; sourceTree = ""; }; + 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCEncryptedBackup.h; sourceTree = ""; }; + 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCEncryptedBackup.m; sourceTree = ""; }; 209818911839688D00541747 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 209818921839694000541747 /* CoreBitcoin.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = CoreBitcoin.podspec; sourceTree = ""; }; 209D1E1018D48EA200293483 /* BTCNetwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCNetwork.h; sourceTree = ""; }; 209D1E1118D48EA200293483 /* BTCNetwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCNetwork.m; sourceTree = ""; }; 209D1E1A18D4F12500293483 /* BTCProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCProcessor.h; sourceTree = ""; }; 209D1E1B18D4F12500293483 /* BTCProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCProcessor.m; sourceTree = ""; }; + 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCPaymentProtocol.h; sourceTree = ""; }; + 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCPaymentProtocol.m; sourceTree = ""; }; + 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCProtocolBuffers.h; sourceTree = ""; }; + 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCProtocolBuffers.m; sourceTree = ""; }; 20B5A63E18924F350035582D /* BTCTransaction+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCTransaction+Tests.h"; sourceTree = ""; }; 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCTransaction+Tests.m"; sourceTree = ""; }; 20B66C8517BFE4F9007128CE /* BTCErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCErrors.h; sourceTree = ""; }; @@ -837,6 +867,10 @@ 2084DD7617B8FF76005AC9E6 /* BTCProtocolSerialization.m */, 2084DD7717B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.h */, 2084DD7817B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.m */, + 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */, + 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */, + 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */, + 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */, 20D09C4F18BC012700794209 /* BTCBlockHeader.h */, 20D09C5018BC012700794209 /* BTCBlockHeader.m */, 20D09C5918BC016B00794209 /* BTCBlock.h */, @@ -888,6 +922,8 @@ 207C1E7F1A5D18A10005A341 /* BTCPriceSource.m */, 200756D81A5D6A44009A24A9 /* BTCPriceSource+Tests.h */, 200756D91A5D6A44009A24A9 /* BTCPriceSource+Tests.m */, + 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */, + 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */, ); path = CoreBitcoin; sourceTree = ""; @@ -906,6 +942,7 @@ 20148C2B1835650B00E68E9C /* BTCData.h in Headers */, 2061D1D71A2CA771004F1E40 /* BTCHashID.h in Headers */, 20148C2C1835650B00E68E9C /* BTCData+Tests.h in Headers */, + 208E30371AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */, 207C1E811A5D18A10005A341 /* BTCPriceSource.h in Headers */, 20CD68DB189B18820083E1A9 /* BTCCurvePoint.h in Headers */, 20584B1E18CD0DA000FDD410 /* BTC256.h in Headers */, @@ -943,10 +980,12 @@ 20148C3D1835650B00E68E9C /* BTCScriptMachine.h in Headers */, 20148C3E1835650B00E68E9C /* BTCTransaction.h in Headers */, 20148C3F1835650B00E68E9C /* BTCTransactionInput.h in Headers */, + 20A443B61AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */, 20148C401835650B00E68E9C /* BTCTransactionOutput.h in Headers */, C9C3C172195B535500D9F6FB /* BTCChainCom.h in Headers */, 2076470F1A0A8D16000F00F2 /* BTCQRCode.h in Headers */, 20148C411835651C00E68E9C /* openssl in Headers */, + 20A443BE1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -961,6 +1000,7 @@ 20148CD5183643FC00E68E9C /* BTCData.h in Headers */, 2061D1D81A2CA771004F1E40 /* BTCHashID.h in Headers */, 20148CD6183643FC00E68E9C /* BTCData+Tests.h in Headers */, + 208E30381AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */, 207C1E821A5D18A10005A341 /* BTCPriceSource.h in Headers */, 20CD68DC189B18820083E1A9 /* BTCCurvePoint.h in Headers */, 20584B1F18CD0DA000FDD410 /* BTC256.h in Headers */, @@ -998,10 +1038,12 @@ 20148CE7183643FC00E68E9C /* BTCScriptMachine.h in Headers */, 20148CE8183643FC00E68E9C /* BTCTransaction.h in Headers */, 20148CE9183643FC00E68E9C /* BTCTransactionInput.h in Headers */, + 20A443B71AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */, 20148CEA183643FC00E68E9C /* BTCTransactionOutput.h in Headers */, C9C3C173195B535500D9F6FB /* BTCChainCom.h in Headers */, 207647101A0A8D16000F00F2 /* BTCQRCode.h in Headers */, 20148CEB1836446500E68E9C /* openssl in Headers */, + 20A443BF1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1016,6 +1058,7 @@ 206B01491835484300878B8D /* BTCKey.h in Headers */, 2061D1D61A2CA771004F1E40 /* BTCHashID.h in Headers */, 206B014D1835484300878B8D /* BTCOpcode.h in Headers */, + 208E30361AC012CE0020F830 /* BTCEncryptedBackup.h in Headers */, 207C1E801A5D18A10005A341 /* BTCPriceSource.h in Headers */, 20CD68DA189B18820083E1A9 /* BTCCurvePoint.h in Headers */, 20584B1D18CD0DA000FDD410 /* BTC256.h in Headers */, @@ -1053,10 +1096,12 @@ 206B014A1835484300878B8D /* BTCKey+Tests.h in Headers */, 206B01411835484300878B8D /* BTCErrors.h in Headers */, 206B01401835484300878B8D /* NSData+BTCData.h in Headers */, + 20A443B51AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */, 206B01461835484300878B8D /* NS+BTCBase58.h in Headers */, C9C3C171195B535500D9F6FB /* BTCChainCom.h in Headers */, 2076470E1A0A8D16000F00F2 /* BTCQRCode.h in Headers */, 206B016A1835487700878B8D /* openssl in Headers */, + 20A443BD1AC55F52008B3447 /* BTCProtocolBuffers.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1220,6 +1265,7 @@ 20148B0B18355DAD00E68E9C /* BTCBase58.m in Sources */, 209D1E2118D4F12500293483 /* BTCProcessor.m in Sources */, 20148B0D18355DAD00E68E9C /* NS+BTCBase58.m in Sources */, + 208E303B1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148B0E18355DAD00E68E9C /* BTCBigNumber.m in Sources */, 204FB507194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EB1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, @@ -1234,12 +1280,14 @@ 20D09C6018BC016C00794209 /* BTCBlock.m in Sources */, 20148B1518355DAD00E68E9C /* BTCScript.m in Sources */, 20148B1718355DAD00E68E9C /* BTCScriptMachine.m in Sources */, + 20A443C21AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */, 20148B1818355DAD00E68E9C /* BTCTransaction.m in Sources */, 207B260A188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 200AAE4119FA3F010004F908 /* BTCOutpoint.m in Sources */, 20C2D80919E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */, 2061D1DB1A2CA771004F1E40 /* BTCHashID.m in Sources */, 20148B1918355DAD00E68E9C /* BTCTransactionInput.m in Sources */, + 20A443BA1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 20148B1A18355DAD00E68E9C /* BTCTransactionOutput.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1264,6 +1312,7 @@ 20148C19183563D000E68E9C /* BTCBase58.m in Sources */, 209D1E2218D4F12500293483 /* BTCProcessor.m in Sources */, 20148C1B183563D000E68E9C /* NS+BTCBase58.m in Sources */, + 208E303C1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148C1C183563D000E68E9C /* BTCBigNumber.m in Sources */, 204FB508194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EC1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, @@ -1278,12 +1327,14 @@ 20D09C6118BC016C00794209 /* BTCBlock.m in Sources */, 20148C23183563D000E68E9C /* BTCScript.m in Sources */, 20148C25183563D000E68E9C /* BTCScriptMachine.m in Sources */, + 20A443C31AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */, 20148C26183563D000E68E9C /* BTCTransaction.m in Sources */, 207B260B188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 200AAE4219FA3F020004F908 /* BTCOutpoint.m in Sources */, 20C2D80A19E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */, 2061D1DC1A2CA771004F1E40 /* BTCHashID.m in Sources */, 20148C27183563D000E68E9C /* BTCTransactionInput.m in Sources */, + 20A443BB1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 20148C28183563D000E68E9C /* BTCTransactionOutput.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1308,6 +1359,7 @@ 20148CC4183643E700E68E9C /* BTCBase58.m in Sources */, 209D1E2318D4F12500293483 /* BTCProcessor.m in Sources */, 20148CC6183643E700E68E9C /* NS+BTCBase58.m in Sources */, + 208E303D1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148CC7183643E700E68E9C /* BTCBigNumber.m in Sources */, 204FB509194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646ED1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, @@ -1322,12 +1374,14 @@ 20D09C6218BC016C00794209 /* BTCBlock.m in Sources */, 20148CCE183643E700E68E9C /* BTCScript.m in Sources */, 20148CD0183643E700E68E9C /* BTCScriptMachine.m in Sources */, + 20A443C41AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */, 20148CD1183643E700E68E9C /* BTCTransaction.m in Sources */, 207B260C188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 200AAE4319FA3F020004F908 /* BTCOutpoint.m in Sources */, 20C2D80B19E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */, 2061D1DD1A2CA771004F1E40 /* BTCHashID.m in Sources */, 20148CD2183643E700E68E9C /* BTCTransactionInput.m in Sources */, + 20A443BC1AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 20148CD3183643E700E68E9C /* BTCTransactionOutput.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1352,6 +1406,7 @@ 206B01681835485D00878B8D /* BTCTransactionInput.m in Sources */, 209D1E2018D4F12500293483 /* BTCProcessor.m in Sources */, 206B015C1835485D00878B8D /* NS+BTCBase58.m in Sources */, + 208E303A1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 206B01571835485D00878B8D /* BTCErrors.m in Sources */, 204FB506194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EA1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, @@ -1366,12 +1421,14 @@ 20D09C5F18BC016C00794209 /* BTCBlock.m in Sources */, 20CD68DE189B18820083E1A9 /* BTCCurvePoint.m in Sources */, 206B01631835485D00878B8D /* BTCOpcode.m in Sources */, + 20A443C11AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */, 206B015A1835485D00878B8D /* BTCBase58.m in Sources */, 207B2609188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 200AAE4019FA3F010004F908 /* BTCOutpoint.m in Sources */, 20C2D80819E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */, 2061D1DA1A2CA771004F1E40 /* BTCHashID.m in Sources */, 206B01611835485D00878B8D /* BTCProtocolSerialization.m in Sources */, + 20A443B91AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 206B015F1835485D00878B8D /* BTCKey.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1380,6 +1437,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 20A443B81AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 2084DD6117B8FF47005AC9E6 /* main.m in Sources */, 20C4860A1955A88C0061DF75 /* BTCEncryptedMessage+Tests.m in Sources */, 20B8AB98189EE88300008138 /* BTCKeychain.m in Sources */, @@ -1415,8 +1473,10 @@ 207646F31A0A8AE4000F00F2 /* BTCBitcoinURL.m in Sources */, 206304EA17CBE72200DF3F18 /* NS+BTCBase58.m in Sources */, 208642BB194C9EAF003A3A2C /* BTCBlindSignature+Tests.m in Sources */, + 208E30391AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 207C1E831A5D18A10005A341 /* BTCPriceSource.m in Sources */, 20E154DA195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m in Sources */, + 20A443C01AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */, 20C2D7FD19E2B2920022CAAC /* BTCMnemonic.m in Sources */, 2084DD9217B8FF76005AC9E6 /* BTCData+Tests.m in Sources */, 209D1E1F18D4F12500293483 /* BTCProcessor.m in Sources */, diff --git a/CoreBitcoin/BTCEncryptedBackup.h b/CoreBitcoin/BTCEncryptedBackup.h new file mode 100644 index 00000000..6b5e9187 --- /dev/null +++ b/CoreBitcoin/BTCEncryptedBackup.h @@ -0,0 +1,12 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import + +@class BTCNetwork; +@interface BTCEncryptedBackup : NSObject + +- (id) initWithBackupKey:(NSData*)backupKey; + ++ (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKey; + +@end diff --git a/CoreBitcoin/BTCEncryptedBackup.m b/CoreBitcoin/BTCEncryptedBackup.m new file mode 100644 index 00000000..42d58f04 --- /dev/null +++ b/CoreBitcoin/BTCEncryptedBackup.m @@ -0,0 +1,29 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCEncryptedBackup.h" +#import "BTCData.h" +#import "BTCNetwork.h" + +@interface BTCEncryptedBackup () +@property(nonatomic, readwrite) NSData* backupKey; +@end + +@implementation BTCEncryptedBackup + +- (id) initWithBackupKey:(NSData*)backupKey { + if (!backupKey) return nil; + if (self = [super init]) { + self.backupKey = backupKey; + } + return self; +} + ++ (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKey { + if (!network || network.isMainnet) { + return BTCHMACSHA256(masterKey, [@"Automatic Backup Key Mainnet" dataUsingEncoding:NSASCIIStringEncoding]); + } else { + return BTCHMACSHA256(masterKey, [@"Automatic Backup Key Testnet" dataUsingEncoding:NSASCIIStringEncoding]); + } +} + +@end diff --git a/CoreBitcoin/BTCPaymentProtocol.h b/CoreBitcoin/BTCPaymentProtocol.h new file mode 100644 index 00000000..1d03e58d --- /dev/null +++ b/CoreBitcoin/BTCPaymentProtocol.h @@ -0,0 +1,211 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import + +// Interface to BIP70 payment protocol. +// Spec: https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki +// +// * BTCPaymentProtocol implements high-level request and response API. +// * BTCPaymentRequest object that represents "PaymentRequest" as described in BIP70. +// * BTCPaymentDetails object that represents "PaymentDetails" as described in BIP70. +// * BTCPayment object that represents "Payment" as described in BIP70. +// * BTCPaymentACK object that represents "PaymentACK" as described in BIP70. + +extern NSInteger const BTCPaymentRequestVersion1; + +extern NSString* const BTCPaymentRequestPKITypeNone; +extern NSString* const BTCPaymentRequestPKITypeX509SHA1; +extern NSString* const BTCPaymentRequestPKITypeX509SHA256; + +// Special value indicating that amount on the output is not specified. +extern BTCAmount const BTCUnspecifiedPaymentAmount; + +// Status allows to correctly display information about security of the request to the user. +typedef NS_ENUM(NSInteger, BTCPaymentRequestStatus) { + // Payment request is valid and the user can trust it. + BTCPaymentRequestStatusValid = 0, // signed with a valid and known certificate. + + // These allow Payment Request to be accepted with a warning to the user. + BTCPaymentRequestStatusUnsigned = 101, // PKI type is "none" + BTCPaymentRequestStatusUnknown = 102, // PKI type is unknown (for forward compatibility may allow sending or warn to upgrade). + + // These generally mean we should decline the Payment Request. + BTCPaymentRequestStatusExpired = 201, + BTCPaymentRequestStatusInvalidSignature = 202, + BTCPaymentRequestStatusMissingCertificate = 203, + BTCPaymentRequestStatusUntrustedCertificate = 204, +}; + +@class BTCNetwork; +@class BTCPayment; +@class BTCPaymentACK; +@class BTCPaymentRequest; +@class BTCPaymentDetails; + +@interface BTCPaymentProtocol : NSObject + +// Convenience API + +// Loads a BTCPaymentRequest object from a given URL. ++ (void) loadPaymentRequestFromURL:(NSURL*)paymentRequestURL completionHandler:(void(^)(BTCPaymentRequest* pr, NSError* error))completionHandler; + +// Posts completed payment object to a given payment URL (provided in BTCPaymentDetails) and +// returns a PaymentACK object. ++ (void) postPayment:(BTCPayment*)payment URL:(NSURL*)paymentURL completionHandler:(void(^)(BTCPaymentACK* ack, NSError* error))completionHandler; + + +// Low-level API +// (use these if you have your own connection queue). + ++ (NSURLRequest*) requestForPaymentRequestWithURL:(NSURL*)paymentRequestURL; // default timeout is 10 sec ++ (NSURLRequest*) requestForPaymentRequestWithURL:(NSURL*)paymentRequestURL timeout:(NSTimeInterval)timeout; ++ (BTCPaymentRequest*) paymentRequestFromData:(NSData*)data response:(NSURLResponse*)response error:(NSError**)errorOut; + ++ (NSURLRequest*) requestForPayment:(BTCPayment*)payment url:(NSURL*)paymentURL; // default timeout is 10 sec ++ (NSURLRequest*) requestForPayment:(BTCPayment*)payment url:(NSURL*)paymentURL timeout:(NSTimeInterval)timeout; ++ (BTCPaymentACK*) paymentACKFromData:(NSData*)data response:(NSURLResponse*)response error:(NSError**)errorOut; + +@end + +// Payment requests are split into two messages to support future extensibility. +// The bulk of the information is contained in the PaymentDetails message. +// It is wrapped inside a PaymentRequest message, which contains meta-information +// about the merchant and a digital signature. +@interface BTCPaymentRequest : NSObject + +// Version of the payment request and payment details. +// Default is BTCPaymentRequestVersion1. +@property(nonatomic, readonly) NSInteger version; + +// Public-key infrastructure (PKI) system being used to identify the merchant. +// All implementation should support "none", "x509+sha256" and "x509+sha1". +// See BTCPaymentRequestPKIType* constants. +@property(nonatomic, readonly) NSString* pkiType; + +// PKI-system data that identifies the merchant and can be used to create a digital signature. +// In the case of X.509 certificates, pki_data contains one or more X.509 certificates. +// Depends on pkiType. Optional. +@property(nonatomic, readonly) NSData* pkiData; + +// A BTCPaymentDetails object. +@property(nonatomic, readonly) BTCPaymentDetails* details; + +// Digital signature over a hash of the protocol buffer serialized variation of +// the PaymentRequest message, with all serialized fields serialized in numerical order +// (all current protocol buffer implementations serialize fields in numerical order) and +// signed using the private key that corresponds to the public key in pki_data. +// Optional fields that are not set are not serialized (however, setting a field to its default value will cause it to be serialized and will affect the signature). +// Before serialization, the signature field must be set to an empty value so that +// the field is included in the signed PaymentRequest hash but contains no data. +@property(nonatomic, readonly) NSData* signature; + +// Array of DER encoded certificates or nil if pkiType does offer certificates. +// This list is extracted from raw `pkiData`. +// If set, certificates are cerialized in X509Certificates object and set to pkiData. +@property(nonatomic, readonly) NSArray* certificates; + +// Returns YES if payment request is correctly signed by a trusted certificate if needed +// and expiration date is valid. +// Accessing this property also updates `status` and `signerName`. +@property(nonatomic, readonly) BOOL isValid; + +// Human-readable name of the signer or nil if it's unsigned. +// You should display this to the user as a name of the merchant. +// Accessing this property also updates `status` and `isValid`. +@property(nonatomic, readonly) NSString* signerName; + +// Validation status. +// Accessing this property also updates `commonName` and `isValid`. +@property(nonatomic, readonly) BTCPaymentRequestStatus status; + +// Binary serialization in protocol buffer format. +@property(nonatomic, readonly) NSData* data; + +- (id) initWithData:(NSData*)data; + +- (BTCPayment*) paymentWithTransaction:(BTCTransaction*)tx; + +- (BTCPayment*) paymentWithTransactions:(NSArray*)txs memo:(NSString*)memo; + +@end + +@interface BTCPaymentDetails : NSObject + +// Mainnet or testnet. Default is mainnet. +@property(nonatomic, readonly) BTCNetwork* network; + +// Array of transaction outputs storing `value` in satoshis and `script` where payment should be sent. +// Unspecified amounts are set to BTC_MAX_MONEY so you can know if zero amount was actually specified (e.g. for OP_RETURN or proof-of-burn etc). +@property(nonatomic, readonly) NSArray* /*[BTCTransactionOutput]*/ outputs; + +// Date when the PaymentRequest was created. +@property(nonatomic, readonly) NSDate* date; + +// Date after which the PaymentRequest should be considered invalid. +@property(nonatomic, readonly) NSDate* expirationDate; + +// Plain-text (no formatting) note that should be displayed to the customer, explaining what this PaymentRequest is for. +@property(nonatomic, readonly) NSString* memo; + +// Secure location (usually https) where a Payment message (see below) may be sent to obtain a PaymentACK. +// The payment_url specified in the PaymentDetails should remain valid at least until the PaymentDetails expires +// (or as long as possible if the PaymentDetails does not expire). +// Note that this is irrespective of any state change in the underlying payment request; +// for example cancellation of an order should not invalidate the payment_url, +// as it is important that the merchant's server can record mis-payments in order to refund the payment. +@property(nonatomic, readonly) NSURL* paymentURL; + +// Arbitrary data that may be used by the merchant to identify the PaymentRequest. +// May be omitted if the merchant does not need to associate Payments with PaymentRequest or +// if they associate each PaymentRequest with a separate payment address. +@property(nonatomic, readonly) NSData* merchantData; + +// Binary serialization in protocol buffer format. +@property(nonatomic, readonly) NSData* data; + +- (id) initWithData:(NSData*)data; + +@end + +// Payment messages are sent after the customer has authorized payment. +@interface BTCPayment : NSObject + +// Should be copied from PaymentDetails.merchant_data. +// Merchants may use invoice numbers or any other data they require +// to match Payments to PaymentRequests. +@property(nonatomic, readonly) NSData* merchantData; + +// One or more valid, signed Bitcoin transactions that fully pay the PaymentRequest +@property(nonatomic, readonly) NSArray* /*[BTCTransaction]*/ transactions; + +// Output scripts and amounts. Amounts are optional and can be zero. +@property(nonatomic, readonly) NSArray* /*[BTCTransactionOutput]*/ refundOutputs; + +// Plain-text note from the customer to the merchant. +@property(nonatomic, readonly) NSString* memo; + +// Binary serialization in protocol buffer format. +@property(nonatomic, readonly) NSData* data; + +- (id) initWithData:(NSData*)data; + +@end + +// PaymentACK is the final message in the payment protocol; +// it is sent from the merchant's server to the bitcoin wallet in response to a Payment message. +@interface BTCPaymentACK : NSObject + +// Copy of the Payment message that triggered this PaymentACK. +// Clients may ignore this if they implement another way of associating Payments with PaymentACKs. +@property(nonatomic, readonly) BTCPayment* payment; + +// Note that should be displayed to the customer giving the status of the transaction +// (e.g. "Payment of 1 BTC for eleven tribbles accepted for processing.") +@property(nonatomic, readonly) NSString* memo; + +// Binary serialization in protocol buffer format. +@property(nonatomic, readonly) NSData* data; + +- (id) initWithData:(NSData*)data; + +@end \ No newline at end of file diff --git a/CoreBitcoin/BTCPaymentProtocol.m b/CoreBitcoin/BTCPaymentProtocol.m new file mode 100644 index 00000000..998ebd0b --- /dev/null +++ b/CoreBitcoin/BTCPaymentProtocol.m @@ -0,0 +1,782 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCPaymentProtocol.h" +#import "BTCProtocolBuffers.h" + +NSInteger const BTCPaymentRequestVersion1 = 1; + +NSString* const BTCPaymentRequestPKITypeNone = @"none"; +NSString* const BTCPaymentRequestPKITypeX509SHA1 = @"x509+sha1"; +NSString* const BTCPaymentRequestPKITypeX509SHA256 = @"x509+sha256"; + +BTCAmount const BTCUnspecifiedPaymentAmount = -1; + +typedef NS_ENUM(NSInteger, BTCOutputKey) { + BTCOutputKeyAmount = 1, + BTCOutputKeyScript = 2 +}; + +typedef NS_ENUM(NSInteger, BTCRequestKey) { + BTCRequestKeyVersion = 1, + BTCRequestKeyPkiType = 2, + BTCRequestKeyPkiData = 3, + BTCRequestKeyPaymentDetails = 4, + BTCRequestKeySignature = 5 +}; + +typedef NS_ENUM(NSInteger, BTCDetailsKey) { + BTCDetailsKeyNetwork = 1, + BTCDetailsKeyOutputs = 2, + BTCDetailsKeyTime = 3, + BTCDetailsKeyExpires = 4, + BTCDetailsKeyMemo = 5, + BTCDetailsKeyPaymentURL = 6, + BTCDetailsKeyMerchantData = 7 +}; + +typedef NS_ENUM(NSInteger, BTCCertificatesKey) { + BTCCertificatesKeyCertificate = 1 +}; + +typedef NS_ENUM(NSInteger, BTCPaymentKey) { + BTCPaymentKeyMerchantData = 1, + BTCPaymentKeyTransactions = 2, + BTCPaymentKeyRefundTo = 3, + BTCPaymentKeyMemo = 4 +}; + +typedef NS_ENUM(NSInteger, BTCPaymentAckKey) { + BTCPaymentAckKeyPayment = 1, + BTCPaymentAckKeyMemo = 2 +}; + + + + +@implementation BTCPaymentProtocol + +// Convenience API + +// Loads a BTCPaymentRequest object from a given URL. ++ (void) loadPaymentRequestFromURL:(NSURL*)paymentRequestURL completionHandler:(void(^)(BTCPaymentRequest* pr, NSError* error))completionHandler { + NSParameterAssert(paymentRequestURL); + NSParameterAssert(completionHandler); + + NSURLRequest* request = [self requestForPaymentRequestWithURL:paymentRequestURL]; + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + NSURLResponse* response = nil; + NSError* error = nil; + NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + if (!data) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + BTCPaymentRequest* pr = [self paymentRequestFromData:data response:response error:&error]; + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(pr, pr ? nil : error); + }); + }); +} + +// Posts completed payment object to a given payment URL (provided in BTCPaymentDetails) and +// returns a PaymentACK object. ++ (void) postPayment:(BTCPayment*)payment URL:(NSURL*)paymentURL completionHandler:(void(^)(BTCPaymentACK* ack, NSError* error))completionHandler { + NSParameterAssert(payment); + NSParameterAssert(paymentURL); + NSParameterAssert(completionHandler); + + NSURLRequest* request = [self requestForPayment:payment url:paymentURL]; + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + NSURLResponse* response = nil; + NSError* error = nil; + NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + if (!data) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(nil, error); + }); + return; + } + BTCPaymentACK* ack = [self paymentACKFromData:data response:response error:&error]; + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(ack, ack ? nil : error); + }); + }); +} + + +// Low-level API +// (use this if you have your own connection queue). + ++ (NSURLRequest*) requestForPaymentRequestWithURL:(NSURL*)paymentRequestURL { + return [self requestForPaymentRequestWithURL:paymentRequestURL timeout:10]; +} + ++ (NSURLRequest*) requestForPaymentRequestWithURL:(NSURL*)paymentRequestURL timeout:(NSTimeInterval)timeout { + if (!paymentRequestURL) return nil; + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:paymentRequestURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:timeout]; + [request addValue:@"application/bitcoin-paymentrequest" forHTTPHeaderField:@"Accept"]; + return request; +} + ++ (BTCPaymentRequest*) paymentRequestFromData:(NSData*)data response:(NSURLResponse*)response error:(NSError**)errorOut { + + if (![response.MIMEType.lowercaseString isEqual:@"application/bitcoin-paymentrequest"]) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestInvalidResponse userInfo:@{}]; + return nil; + } + if (data.length > 50000) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestTooBig userInfo:@{}]; + return nil; + } + BTCPaymentRequest* pr = [[BTCPaymentRequest alloc] initWithData:data]; + if (!pr) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestInvalidResponse userInfo:@{}]; + return nil; + } + return pr; +} + ++ (NSURLRequest*) requestForPayment:(BTCPayment*)payment url:(NSURL*)paymentURL { + return [self requestForPayment:payment url:paymentURL timeout:10]; +} + ++ (NSURLRequest*) requestForPayment:(BTCPayment*)payment url:(NSURL*)paymentURL timeout:(NSTimeInterval)timeout { + if (!payment) return nil; + if (!paymentURL) return nil; + + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:paymentURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:timeout]; + + [request addValue:@"application/bitcoin-payment" forHTTPHeaderField:@"Content-Type"]; + [request addValue:@"application/bitcoin-paymentack" forHTTPHeaderField:@"Accept"]; + [request setHTTPMethod:@"POST"]; + [request setHTTPBody:payment.data]; + return request; +} + ++ (BTCPaymentACK*) paymentACKFromData:(NSData*)data response:(NSURLResponse*)response error:(NSError**)errorOut { + + if (![response.MIMEType.lowercaseString isEqual:@"application/bitcoin-paymentack"]) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestInvalidResponse userInfo:@{}]; + return nil; + } + if (data.length > 50000) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestTooBig userInfo:@{}]; + return nil; + } + + BTCPaymentACK* ack = [[BTCPaymentACK alloc] initWithData:data]; + + if (!ack) { + if (errorOut) *errorOut = [NSError errorWithDomain:BTCErrorDomain code:BTCErrorPaymentRequestInvalidResponse userInfo:@{}]; + return nil; + } + + return ack; +} + +@end + + + + + + + + + + + + + +@interface BTCPaymentRequest () +// If you make these publicly writable, make sure to set _data to nil and _isValidated to NO. +@property(nonatomic, readwrite) NSInteger version; +@property(nonatomic, readwrite) NSString* pkiType; +@property(nonatomic, readwrite) NSData* pkiData; +@property(nonatomic, readwrite) BTCPaymentDetails* details; +@property(nonatomic, readwrite) NSData* signature; +@property(nonatomic, readwrite) NSArray* certificates; +@property(nonatomic, readwrite) NSData* data; + +@property(nonatomic) BOOL isValidated; +@property(nonatomic, readwrite) BOOL isValid; +@property(nonatomic, readwrite) NSString* signerName; +@property(nonatomic, readwrite) BTCPaymentRequestStatus status; +@end + + +@interface BTCPaymentDetails () +@property(nonatomic, readwrite) BTCNetwork* network; +@property(nonatomic, readwrite) NSArray* /*[BTCTransactionOutput]*/ outputs; +@property(nonatomic, readwrite) NSDate* date; +@property(nonatomic, readwrite) NSDate* expirationDate; +@property(nonatomic, readwrite) NSString* memo; +@property(nonatomic, readwrite) NSURL* paymentURL; +@property(nonatomic, readwrite) NSData* merchantData; +@property(nonatomic, readwrite) NSData* data; +@end + + +@interface BTCPayment () +@property(nonatomic, readwrite) NSData* merchantData; +@property(nonatomic, readwrite) NSArray* /*[BTCTransaction]*/ transactions; +@property(nonatomic, readwrite) NSArray* /*[BTCTransactionOutput]*/ refundOutputs; +@property(nonatomic, readwrite) NSString* memo; +@property(nonatomic, readwrite) NSData* data; +@end + + +@interface BTCPaymentACK () +@property(nonatomic, readwrite) BTCPayment* payment; +@property(nonatomic, readwrite) NSString* memo; +@property(nonatomic, readwrite) NSData* data; +@end + + + + + + + +@implementation BTCPaymentRequest + +- (id) initWithData:(NSData*)data { + if (!data) return nil; + + if (self = [super init]) { + + NSInteger offset = 0; + while (offset < data.length) { + uint64_t i = 0; + NSData* d = nil; + + switch ([BTCProtocolBuffers fieldAtOffset:&offset int:&i data:&d fromData:data]) { + case BTCRequestKeyVersion: + if (i) _version = (uint32_t)i; + break; + case BTCRequestKeyPkiType: + if (d) _pkiType = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; + break; + case BTCRequestKeyPkiData: + if (d) _pkiData = d; + break; + case BTCRequestKeyPaymentDetails: + if (d) _details = [[BTCPaymentDetails alloc] initWithData:d]; + break; + case BTCRequestKeySignature: + if (d) _signature = d; + break; + default: break; + } + } + + // Payment details are required. + if (!_details) return nil; + } + return self; +} + +- (NSData*) data { + if (!_data) { + _data = [self dataWithSignature:_signature]; + } + return _data; +} + +- (NSData*) dataForSigning { + return [self dataWithSignature:[NSData data]]; +} + +- (NSData*) dataWithSignature:(NSData*)signature { + NSMutableData* data = [NSMutableData data]; + + // Note: we should reconstruct the data exactly as it was on the input. + if (_version > 0) { + [BTCProtocolBuffers writeInt:_version withKey:BTCRequestKeyVersion toData:data]; + } + if (_pkiType) { + [BTCProtocolBuffers writeString:_pkiType withKey:BTCRequestKeyPkiType toData:data]; + } + if (_pkiData) { + [BTCProtocolBuffers writeData:_pkiData withKey:BTCRequestKeyPkiData toData:data]; + } + + [BTCProtocolBuffers writeData:self.details.data withKey:BTCRequestKeyPaymentDetails toData:data]; + + if (signature) { + [BTCProtocolBuffers writeData:signature withKey:BTCRequestKeySignature toData:data]; + } + return data; +} + +- (NSInteger) version +{ + return (_version > 0) ? _version : BTCPaymentRequestVersion1; +} + +- (NSString*) pkiType +{ + return _pkiType ?: BTCPaymentRequestPKITypeNone; +} + +- (NSArray*) certificates { + if (!_certificates) { + NSMutableArray* certs = [NSMutableArray array]; + NSInteger offset = 0; + while (offset < self.pkiData.length) { + NSData* d = nil; + NSInteger key = [BTCProtocolBuffers fieldAtOffset:&offset int:NULL data:&d fromData:self.pkiData]; + if (key == BTCCertificatesKeyCertificate && d) { + [certs addObject:d]; + } + } + _certificates = certs; + } + return _certificates; +} + +- (BOOL) isValid { + if (!_isValidated) [self validatePaymentRequest]; + return _isValid; +} + +- (NSString*) signerName { + if (!_isValidated) [self validatePaymentRequest]; + return _signerName; +} + +- (BTCPaymentRequestStatus) status { + if (!_isValidated) [self validatePaymentRequest]; + return _status; +} + +- (void) validatePaymentRequest { + _isValidated = YES; + _isValid = NO; + + if ([self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA1] || + [self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA256]) { + + // 1. Verify chain of trust + + NSMutableArray *certs = [NSMutableArray array]; + NSArray *policies = @[CFBridgingRelease(SecPolicyCreateBasicX509())]; + SecTrustRef trust = NULL; + SecTrustResultType trustResult = kSecTrustResultInvalid; + + for (NSData *certData in self.certificates) { + SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData); + if (cert) [certs addObject:CFBridgingRelease(cert)]; + } + + if (certs.count > 0) { + _signerName = CFBridgingRelease(SecCertificateCopySubjectSummary((__bridge SecCertificateRef)certs[0])); + } + + SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, (__bridge CFArrayRef)policies, &trust); + SecTrustEvaluate(trust, &trustResult); // verify certificate chain + + // kSecTrustResultUnspecified indicates the evaluation succeeded + // and the certificate is implicitly trusted, but user intent was not + // explicitly specified. + if (trustResult != kSecTrustResultUnspecified && trustResult != kSecTrustResultProceed) { + if (certs.count > 0) { + _status = BTCPaymentRequestStatusUntrustedCertificate; + } else { + _status = BTCPaymentRequestStatusMissingCertificate; + } + return; + } + + // 2. Verify signature + + SecKeyRef pubKey = SecTrustCopyPublicKey(trust); + SecPadding padding = kSecPaddingPKCS1; + NSData* hash = nil; + + NSData* dataForSigning = [self dataForSigning]; + if ([self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA256]) { + hash = BTCSHA256(dataForSigning); + padding = kSecPaddingPKCS1SHA256; + } + else if ([self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA1]) { + hash = BTCSHA1(dataForSigning); + padding = kSecPaddingPKCS1SHA1; + } + + OSStatus status = SecKeyRawVerify(pubKey, padding, hash.bytes, hash.length, _signature.bytes, _signature.length); + + CFRelease(pubKey); + + if (status != errSecSuccess) { + _status = BTCPaymentRequestStatusInvalidSignature; + return; + } + + _status = BTCPaymentRequestStatusValid; + _isValid = YES; + + } else { + // Either "none" PKI type or some new and unsupported PKI. + + if (self.certificates.count > 0) { + // Non-standard extension to include a signer's name without actually signing request. + _signerName = [[NSString alloc] initWithData:self.certificates[0] encoding:NSUTF8StringEncoding]; + } + + if ([self.pkiType isEqual:BTCPaymentRequestPKITypeNone]) { + _isValid = YES; + _status = BTCPaymentRequestStatusUnsigned; + } else { + _isValid = NO; + _status = BTCPaymentRequestStatusUnknown; + } + } + + if (self.details.expirationDate && [[NSDate date] timeIntervalSinceDate:self.details.expirationDate] > 0.0) { + _status = BTCPaymentRequestStatusExpired; + _isValid = NO; + return; + } +} + +- (BTCPayment*) paymentWithTransaction:(BTCTransaction*)tx { + if (!tx) return nil; + return [self paymentWithTransactions:@[ tx ] memo:nil]; +} + +- (BTCPayment*) paymentWithTransactions:(NSArray*)txs memo:(NSString*)memo { + if (!txs || txs.count == 0) return nil; + BTCPayment* payment = [[BTCPayment alloc] init]; + payment.merchantData = self.details.merchantData; + payment.transactions = txs; + payment.memo = memo; + return payment; +} + +@end + + + + + + + + + + + + + + + + + + + + + + + + +@implementation BTCPaymentDetails + +- (id) initWithData:(NSData*)data { + if (!data) return nil; + + if (self = [super init]) { + NSMutableArray* outputs = [NSMutableArray array]; + + NSInteger offset = 0; + while (offset < data.length) { + uint64_t integer = 0; + NSData* d = nil; + + switch ([BTCProtocolBuffers fieldAtOffset:&offset int:&integer data:&d fromData:data]) { + case BTCDetailsKeyNetwork: + if (d) { + NSString* networkName = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; + if ([networkName isEqual:@"main"]) { + _network = [BTCNetwork mainnet]; + } else if ([networkName isEqual:@"test"]) { + _network = [BTCNetwork testnet]; + } else { + _network = [[BTCNetwork alloc] initWithName:networkName]; + } + } + break; + case BTCDetailsKeyOutputs: { + NSInteger offset2 = 0; + BTCAmount amount = BTCUnspecifiedPaymentAmount; + NSData* scriptData = nil; + // both amount and scriptData are optional, so we try to read any of them + while (offset2 < d.length) { + [BTCProtocolBuffers fieldAtOffset:&offset2 int:(uint64_t*)&amount data:&scriptData fromData:d]; + } + if (scriptData) { + BTCScript* script = [[BTCScript alloc] initWithData:scriptData]; + if (!script) { + NSLog(@"CoreBitcoin ERROR: Received invalid script data in Payment Request Details: %@", scriptData); + return nil; + } + BTCTransactionOutput* txout = [[BTCTransactionOutput alloc] initWithValue:amount script:script]; + [outputs addObject:txout]; + } + break; + } + case BTCDetailsKeyTime: + if (integer) _date = [NSDate dateWithTimeIntervalSince1970:integer]; + break; + case BTCDetailsKeyExpires: + if (integer) _expirationDate = [NSDate dateWithTimeIntervalSince1970:integer]; + break; + case BTCDetailsKeyMemo: + if (d) _memo = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; + break; + case BTCDetailsKeyPaymentURL: + if (d) _paymentURL = [NSURL URLWithString:[[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]]; + break; + case BTCDetailsKeyMerchantData: + if (d) _merchantData = d; + break; + default: break; + } + } + + // PR must have at least one output + if (outputs.count == 0) return nil; + + // PR requires a creation time. + if (!_date) return nil; + + _outputs = outputs; + } + return self; +} + +- (BTCNetwork*) network { + return _network ?: [BTCNetwork mainnet]; +} + +- (NSData*) data { + if (!_data) { + NSMutableData* dst = [NSMutableData data]; + + // Note: we should reconstruct the data exactly as it was on the input. + + if (_network) { + [BTCProtocolBuffers writeString:_network.paymentProtocolName withKey:BTCDetailsKeyNetwork toData:dst]; + } + + for (BTCTransactionOutput* txout in _outputs) { + NSMutableData* outputData = [NSMutableData data]; + + if (txout.value != BTCUnspecifiedPaymentAmount) { + [BTCProtocolBuffers writeInt:txout.value withKey:BTCOutputKeyAmount toData:outputData]; + } + [BTCProtocolBuffers writeData:txout.script.data withKey:BTCOutputKeyScript toData:outputData]; + [BTCProtocolBuffers writeData:outputData withKey:BTCDetailsKeyOutputs toData:dst]; + } + + if (_date) { + [BTCProtocolBuffers writeInt:(uint64_t)[_date timeIntervalSince1970] withKey:BTCDetailsKeyTime toData:dst]; + } + if (_expirationDate) { + [BTCProtocolBuffers writeInt:(uint64_t)[_expirationDate timeIntervalSince1970] withKey:BTCDetailsKeyExpires toData:dst]; + } + if (_memo) { + [BTCProtocolBuffers writeString:_memo withKey:BTCDetailsKeyMemo toData:dst]; + } + if (_paymentURL) { + [BTCProtocolBuffers writeString:_paymentURL.absoluteString withKey:BTCDetailsKeyPaymentURL toData:dst]; + } + if (_merchantData) { + [BTCProtocolBuffers writeData:_merchantData withKey:BTCDetailsKeyMerchantData toData:dst]; + } + _data = dst; + } + return _data; +} + +@end + + + + + + + + + + + + + + + + + + + + +@implementation BTCPayment + +- (id) initWithData:(NSData*)data { + if (!data) return nil; + + if (self = [super init]) { + + NSInteger offset = 0; + NSMutableArray* txs = [NSMutableArray array]; + NSMutableArray* outputs = [NSMutableArray array]; + + while (offset < data.length) { + uint64_t integer = 0; + NSData* d = nil; + BTCTransaction* tx = nil; + + switch ([BTCProtocolBuffers fieldAtOffset:&offset int:&integer data:&d fromData:data]) { + case BTCPaymentKeyMerchantData: + if (d) _merchantData = d; + break; + case BTCPaymentKeyTransactions: + if (d) tx = [[BTCTransaction alloc] initWithData:d]; + if (tx) [txs addObject:tx]; + break; + case BTCPaymentKeyRefundTo: { + NSInteger offset2 = 0; + BTCAmount amount = BTCUnspecifiedPaymentAmount; + NSData* scriptData = nil; + // both amount and scriptData are optional, so we try to read any of them + while (offset2 < d.length) { + [BTCProtocolBuffers fieldAtOffset:&offset2 int:(uint64_t*)&amount data:&scriptData fromData:d]; + } + if (scriptData) { + BTCScript* script = [[BTCScript alloc] initWithData:scriptData]; + if (!script) { + NSLog(@"CoreBitcoin ERROR: Received invalid script data in Payment Request Details: %@", scriptData); + return nil; + } + BTCTransactionOutput* txout = [[BTCTransactionOutput alloc] initWithValue:amount script:script]; + [outputs addObject:txout]; + } + break; + } + case BTCPaymentKeyMemo: + if (d) _memo = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; + break; + default: break; + } + + } + + _transactions = txs; + _refundOutputs = outputs; + } + return self; +} + +- (NSData*) data { + + if (!_data) { + NSMutableData* dst = [NSMutableData data]; + + if (_merchantData) { + [BTCProtocolBuffers writeData:_merchantData withKey:BTCPaymentKeyMerchantData toData:dst]; + } + + for (BTCTransaction* tx in _transactions) { + [BTCProtocolBuffers writeData:tx.data withKey:BTCPaymentKeyTransactions toData:dst]; + } + + for (BTCTransactionOutput* txout in _refundOutputs) { + NSMutableData* outputData = [NSMutableData data]; + + if (txout.value != BTCUnspecifiedPaymentAmount) { + [BTCProtocolBuffers writeInt:txout.value withKey:BTCOutputKeyAmount toData:outputData]; + } + [BTCProtocolBuffers writeData:txout.script.data withKey:BTCOutputKeyScript toData:outputData]; + [BTCProtocolBuffers writeData:outputData withKey:BTCPaymentKeyRefundTo toData:dst]; + } + + if (_memo) { + [BTCProtocolBuffers writeString:_memo withKey:BTCPaymentKeyMemo toData:dst]; + } + + _data = dst; + } + return _data; +} + +@end + + + + + + + + + + + + + + + + + + + + + + +@implementation BTCPaymentACK + +- (id) initWithData:(NSData*)data { + if (!data) return nil; + + if (self = [super init]) { + NSInteger offset = 0; + while (offset < data.length) { + uint64_t integer = 0; + NSData* d = nil; + + switch ([BTCProtocolBuffers fieldAtOffset:&offset int:&integer data:&d fromData:data]) { + case BTCPaymentAckKeyPayment: + if (d) _payment = [[BTCPayment alloc] initWithData:d]; + break; + case BTCPaymentAckKeyMemo: + if (d) _memo = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; + break; + default: break; + } + } + + // payment object is required. + if (! _payment) return nil; + } + return self; +} + + +- (NSData*) data { + + if (!_data) { + NSMutableData* dst = [NSMutableData data]; + + [BTCProtocolBuffers writeData:_payment.data withKey:BTCPaymentAckKeyPayment toData:dst]; + + if (_memo) { + [BTCProtocolBuffers writeString:_memo withKey:BTCPaymentAckKeyMemo toData:dst]; + } + + _data = dst; + } + return _data; +} + + +@end diff --git a/CoreBitcoin/BTCProtocolBuffers.h b/CoreBitcoin/BTCProtocolBuffers.h new file mode 100644 index 00000000..d8b8df12 --- /dev/null +++ b/CoreBitcoin/BTCProtocolBuffers.h @@ -0,0 +1,29 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import + +// An API to parse and encode protocol buffers. +// Currently incomplete and only supports BIP70 / BTCPaymentProtocol +@interface BTCProtocolBuffers : NSObject + +// Reading + +// Returns a variable-length integer value at a given offset in source data. ++ (uint64_t) varIntAtOffset:(NSInteger*)offset fromData:(NSData*)src; + +// Returns a length-delimited data at a given offset in source data. ++ (NSData *) lenghtDelimitedDataAtOffset:(NSInteger *)offset fromData:(NSData*)src; + +// Returns either int or data depending on field type, and returns a field key. ++ (NSInteger) fieldAtOffset:(NSInteger *)offset int:(uint64_t *)i data:(NSData **)d fromData:(NSData*)src; + + +// Writing + ++ (void) writeVarInt:(uint64_t)i toData:(NSMutableData*)dst; ++ (void) writeInt:(uint64_t)i withKey:(NSInteger)key toData:(NSMutableData*)dst; ++ (void) writeLengthDelimitedData:(NSData*)data toData:(NSMutableData*)dst; ++ (void) writeData:(NSData*)d withKey:(NSInteger)key toData:(NSMutableData*)dst; ++ (void) writeString:(NSString*)string withKey:(NSInteger)key toData:(NSMutableData*)dst; + +@end diff --git a/CoreBitcoin/BTCProtocolBuffers.m b/CoreBitcoin/BTCProtocolBuffers.m new file mode 100644 index 00000000..5decbbcb --- /dev/null +++ b/CoreBitcoin/BTCProtocolBuffers.m @@ -0,0 +1,97 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCProtocolBuffers.h" + +typedef NS_ENUM(NSInteger, BTCProtobufType) { + BTCProtobufTypeVarInt = 0, // int32, int64, uint32, uint64, sint32, sint64, bool, enum + BTCProtobufType64bit = 1, // fixed64, sfixed64, double + BTCProtobufTypeLengthDelimited = 2, // string, bytes, embedded messages, packed repeated fields + BTCProtobufType32bit = 5, // fixed32, sfixed32, float +}; + +@implementation BTCProtocolBuffers + +// Returns a variable-length integer value at a given offset in source data. ++ (uint64_t) varIntAtOffset:(NSInteger*)offset fromData:(NSData*)src { + uint64_t varInt = 0; + uint8_t b = 0x80; + NSUInteger i = 0; + while ((b & 0x80) && *offset < src.length) { + b = ((const uint8_t *)src.bytes)[(*offset)++]; + varInt += (uint64_t)(b & 0x7f) << 7*i++; + } + return varInt; +} + +// Returns a length-delimited data at a given offset in source data. ++ (NSData *) lenghtDelimitedDataAtOffset:(NSInteger *)offset fromData:(NSData*)src { + NSData *lengthDelimitedData = nil; + NSUInteger length = (NSUInteger)[self varIntAtOffset:offset fromData:src]; + if (*offset + length <= src.length) { + lengthDelimitedData = [src subdataWithRange:NSMakeRange(*offset, length)]; + } + *offset += length; + return lengthDelimitedData; +} + +// Returns either int or data depending on field type, and returns a field key. ++ (NSInteger) fieldAtOffset:(NSInteger *)offset int:(uint64_t *)i data:(NSData **)d fromData:(NSData*)src { + NSInteger key = (NSInteger)[self varIntAtOffset:offset fromData:src]; + uint64_t varInt = 0; + NSData *lengthDelimitedData = nil; + + switch (key & 0x07) { + case BTCProtobufTypeVarInt: { + varInt = [self varIntAtOffset:offset fromData:src]; + if (i) *i = varInt; + break; + } + case BTCProtobufType64bit: { // not used by BIP70 + *offset += sizeof(uint64_t); + break; + } + case BTCProtobufTypeLengthDelimited: { + lengthDelimitedData = [self lenghtDelimitedDataAtOffset:offset fromData:src]; + if (d) *d = lengthDelimitedData; + break; + } + case BTCProtobufType32bit: { // not used by BIP70 + *offset += sizeof(uint32_t); + break; + } + default: break; + } + + return key >> 3; +} + ++ (void) writeVarInt:(uint64_t)i toData:(NSMutableData*)dst { + do { + uint8_t b = i & 0x7f; + i >>= 7; + if (i > 0) b |= 0x80; + [dst appendBytes:&b length:1]; + } while (i > 0); +} + ++ (void) writeInt:(uint64_t)i withKey:(NSInteger)key toData:(NSMutableData*)dst { + [self writeVarInt:(key << 3) + BTCProtobufTypeVarInt toData:dst]; + [self writeVarInt:i toData:dst]; +} + ++ (void) writeLengthDelimitedData:(NSData*)data toData:(NSMutableData*)dst { + [self writeVarInt:data.length toData:dst]; + [dst appendData:data]; +} + ++ (void) writeData:(NSData*)data withKey:(NSInteger)key toData:(NSMutableData*)dst { + [self writeVarInt:(key << 3) + BTCProtobufTypeLengthDelimited toData:dst]; + [self writeLengthDelimitedData:data toData:dst]; +} + ++ (void) writeString:(NSString*)string withKey:(NSInteger)key toData:(NSMutableData*)dst { + [self writeData:[string dataUsingEncoding:NSUTF8StringEncoding] withKey:key toData:dst]; +} + +@end + From d43f12c254fdb4b804b984c04cecaa6c11797500 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 27 Mar 2015 11:17:28 +0100 Subject: [PATCH 008/163] updated README --- README.md | 80 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1b7b0e68..4ed7e26a 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,79 @@ CoreBitcoin =========== -CoreBitcoin implements Bitcoin protocol in Objective-C. It is far from being complete, but it is already a useful toolkit. - -Due to "all or nothing" nature of blockchain, CoreBitcoin must perfectly match implementation of BitcoinQT ("Satoshi client"), including all its features, oddities and bugs. If you come across things that CoreBitcoin does differently from BitcoinQT, this might be a subtle bug in our implementation and should be investigated. +CoreBitcoin implements Bitcoin protocol in Objective-C and provides many additional APIs to make great apps. CoreBitcoin deliberately implements as much as possible directly in Objective-C with limited dependency on OpenSSL. This gives everyone an opportunity to learn Bitcoin on a clean codebase and enables all Mac and iOS developers to extend and improve Bitcoin protocol. Note that "Bitcoin Core" (previously known as BitcoinQT or "Satoshi client") is a completely different project. +Projects using CoreBitcoin +-------------------------- + +- [Chain-iOS SDK](https://github.com/chain-engineering/chain-ios) (written by Oleg Andreev) +- [Mycelium iOS Wallet](https://itunes.apple.com/us/app/mycelium-bitcoin-wallet/id943912290) (written by Oleg Andreev) +- [bitWallet](https://itunes.apple.com/us/app/bitwallet-bitcoin-wallet/id777634714) +- [Yallet](https://www.yallet.com) +- [BitStore](http://bitstoreapp.com) + + Features -------- -- EC keys and signatures for binary and Bitcoin text messages. -- Addresses: P2PK, P2PKH, P2SH, WIF format. -- Transaction building blocks: inputs, outputs, scripts. -- Script evaluation machine to actually validate individual transactions. -- Convenient and safe transaction builder to fetch inputs, compute fees and change and sign transactions easily. -- Utils to parse and compose bitcoin URL and payment requests. -- Flexible bitcoin currency formatter with support for BTC, mBTC, bits, satoshis. -- Flexible currency converter (not linked to any exchange) with support for various methods to calculate exchange rate. -- QR Code generator and scanner in a unified API (iOS only for now). -- Blockchain.info and Chain.com API to fetch unspent outputs and broadcast transactions. -- BIP32 hierarchical deterministic wallets (BTCKeychain). -- BIP39 implementation (BTCMnemonic). -- BIP44 implementation (BTCKeychain). -- Blind signatures implementation. -- Math on elliptic curves: big numbers, curve points, conversion between keys, numbers and points. -- Various cryptographic primitives like hash functions and AES encryption. - -Not done yet: - -- Blocks and block headers. +- Encoding/decoding addresses: P2PK, P2PKH, P2SH, WIF format ([BTCAddress](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCAddress.h)). +- Transaction building blocks: inputs, outputs, scripts ([BTCTransaction](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCTransaction.h), [BTCScript](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCScript.h)). +- EC keys and signatures ([BTCKey](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKey.h)). +- High-level convenient and safe transaction builder ([BTCTransactionBuilder](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCTransactionBuilder.h)). +- Parsing and composing bitcoin URLs and payment requests ([BTCBitcoinURL](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBitcoinURL.h)). +- QR Code generator and scanner in a unified API (iOS only for now; [BTCQRCode](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCQRCode.h)). +- BIP32, BIP44 hierarchical deterministic wallets ([BTCKeychain](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKeychain.h)). +- BIP39 implementation ([BTCMnemonic](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCMnemonic.h)). +- BIP70 implementation ([BTCPaymentProtocol](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCPaymentProtocol.h)). + +Currency Tools +-------------- + +- Bitcoin currency formatter with support for BTC, mBTC, bits ([BTCNumberFormatter](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCNumberFormatter.h)). +- Currency converter (not linked to any exchange) with support for various methods to calculate exchange rate ([BTCCurrencyConverter](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCCurrencyConverter.h)). +- Various price sources and abstract interface for adding new ones ([BTCPriceSource](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCPriceSource.h) with support for Winkdex, Coindesk, Coinbase, Paymium). + +Advanced Features +----------------- + +- Script evaluation machine to actually validate individual transactions ([BTCScriptMachine](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCScriptMachine.h)). +- Blind signatures implementation ([BTCBlindSignature](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlindSignature.h)). +- Math on elliptic curves: big numbers, curve points, conversion between keys, numbers and points ([BTCBigNumber](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBigNumber.h), [BTCCurvePoint](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCCurvePoint.h)). +- Various cryptographic primitives like hash functions and AES encryption (see [BTCData.h](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCData.h)). + + +On the roadmap +-------------- + +- Blocks and block headers (incomplete support for blocks in [BTCBlock](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlock.h) and [BTCBlockHeader](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlockHeader.h)). - P2P communication with other nodes. - Full blockchain verification procedure and storage. - Importing BitcoinQT, Electrum and Blockchain.info wallets. - SPV mode. -- Various BIPs (BIP38, BIP70 etc.) +- BIP38 and some other BIPs. -The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Ladies and gentlemen, send me your pull requests. +The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Pull requests are welcome. Starting points --------------- -To encode/decode addresses, P2SH and private keys in sipa format see BTCAddress. +To encode/decode addresses see [BTCAddress](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCAddress.h). -To perform cryptographic operations, use BTCKey, BTCBigNumber and BTCCurvePoint. BTCKeychain implements BIP32 (hierarchical deterministic wallet). +To perform cryptographic operations, use [BTCKey](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKey.h), [BTCBigNumber](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBigNumber.h) and [BTCCurvePoint](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCCurvePoint). [BTCKeychain](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKeychain.h) implements BIP32 (hierarchical deterministic wallet). -To fetch unspent coins and broadcast transactions use one of the 3rd party APIs: BTCBlockchainInfo (blockchain.info) or BTCChainCom (chain.com). +To fetch unspent coins and broadcast transactions use one of the 3rd party APIs: [BTCBlockchainInfo](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlockchainInfo.h) (blockchain.info) or [Chain-iOS](https://github.com/chain-engineering/chain-ios) (recommended). -For full wallet workflow see BTCTransaction+Tests.m (fetch unspent outputs, compose a transaction, sign inputs, verify and broadcast). +For full wallet workflow see [BTCTransaction+Tests.m](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCTransaction+Tests.m) (fetch unspent outputs, compose a transaction, sign inputs, verify and broadcast). -For multisignature scripts usage see BTCScript+Tests.m: compose and unlock multisig output. +For multisignature scripts usage see [BTCScript+Tests.m](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCScript+Tests.m): compose and unlock multisig output. -All other files with "+Tests" in their name are worth checking out as they contain useful sample code. +All other files with `+Tests` in their name are worth checking out as they contain useful sample code. Using CoreBitcoin CocoaPod (recommended) From e77962a4d6d59c405006a8d87cdc6069503e8fe2 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 27 Mar 2015 11:17:38 +0100 Subject: [PATCH 009/163] minor fix to BTCMerkleTree --- CoreBitcoin/BTCMerkleTree.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreBitcoin/BTCMerkleTree.m b/CoreBitcoin/BTCMerkleTree.m index bb650b92..654dc326 100644 --- a/CoreBitcoin/BTCMerkleTree.m +++ b/CoreBitcoin/BTCMerkleTree.m @@ -48,7 +48,7 @@ - (BOOL) hasTailDuplicates { } - (NSData*) computeMerkleRoot { - // Based on original Satoshi implementation + vulnerability fix: + // Based on original Satoshi implementation + vulnerability detection API: /* WARNING! If you're reading this because you're learning about crypto and/or designing a new system that will use merkle trees, keep in mind that the following merkle tree algorithm has a serious flaw related to @@ -85,7 +85,7 @@ - (NSData*) computeMerkleRoot { root. */ NSMutableArray* tree = [self.hashes mutableCopy]; - + _hasTailDuplicates = NO; NSInteger j = 0; for (NSInteger size = (NSInteger)tree.count; size > 1; size = (size + 1) / 2) { for (NSInteger i = 0; i < size; i += 2) { From 84720f61019196e38bcb739dc6e74f7becbd3412 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 27 Mar 2015 11:23:16 +0100 Subject: [PATCH 010/163] notes on libsecp256k1 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4ed7e26a..7b08ea61 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ On the roadmap - Importing BitcoinQT, Electrum and Blockchain.info wallets. - SPV mode. - BIP38 and some other BIPs. +- Support for [libsecp256k1](https://github.com/bitcoin/secp256k1) in addition to OpenSSL. +- Eventual support for libconsensus as it gets more mature and feature-full. The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Pull requests are welcome. From 3c2ae75274eb75d3ba65bc643732cfa72b70f3d6 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 27 Mar 2015 12:05:15 +0100 Subject: [PATCH 011/163] added link to issues --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7b08ea61..41d5ae69 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,12 @@ Advanced Features On the roadmap -------------- -- Blocks and block headers (incomplete support for blocks in [BTCBlock](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlock.h) and [BTCBlockHeader](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlockHeader.h)). -- P2P communication with other nodes. +See all [todo items here](https://github.com/oleganza/CoreBitcoin/issues). + +- Complete support for blocks and block headers. +- SPV mode and P2P communication with other nodes. - Full blockchain verification procedure and storage. - Importing BitcoinQT, Electrum and Blockchain.info wallets. -- SPV mode. -- BIP38 and some other BIPs. - Support for [libsecp256k1](https://github.com/bitcoin/secp256k1) in addition to OpenSSL. - Eventual support for libconsensus as it gets more mature and feature-full. From 4282b854bde16ee12421689a4962793a3fbea400 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 14:19:20 +0200 Subject: [PATCH 012/163] fixes to BTCPaymentProtocol and WIP on OS X signature verification --- CoreBitcoin/BTCPaymentProtocol.h | 2 ++ CoreBitcoin/BTCPaymentProtocol.m | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/CoreBitcoin/BTCPaymentProtocol.h b/CoreBitcoin/BTCPaymentProtocol.h index 1d03e58d..0eed64ac 100644 --- a/CoreBitcoin/BTCPaymentProtocol.h +++ b/CoreBitcoin/BTCPaymentProtocol.h @@ -1,6 +1,7 @@ // CoreBitcoin by Oleg Andreev , WTFPL. #import +#import "BTCUnitsAndLimits.h" // Interface to BIP70 payment protocol. // Spec: https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki @@ -41,6 +42,7 @@ typedef NS_ENUM(NSInteger, BTCPaymentRequestStatus) { @class BTCPaymentACK; @class BTCPaymentRequest; @class BTCPaymentDetails; +@class BTCTransaction; @interface BTCPaymentProtocol : NSObject diff --git a/CoreBitcoin/BTCPaymentProtocol.m b/CoreBitcoin/BTCPaymentProtocol.m index 998ebd0b..d9c971ca 100644 --- a/CoreBitcoin/BTCPaymentProtocol.m +++ b/CoreBitcoin/BTCPaymentProtocol.m @@ -2,6 +2,13 @@ #import "BTCPaymentProtocol.h" #import "BTCProtocolBuffers.h" +#import "BTCErrors.h" +#import "BTCData.h" +#import "BTCNetwork.h" +#import "BTCScript.h" +#import "BTCTransaction.h" +#import "BTCTransactionOutput.h" +#import NSInteger const BTCPaymentRequestVersion1 = 1; @@ -392,6 +399,7 @@ - (void) validatePaymentRequest { // 2. Verify signature +#if TARGET_OS_IPHONE SecKeyRef pubKey = SecTrustCopyPublicKey(trust); SecPadding padding = kSecPaddingPKCS1; NSData* hash = nil; @@ -414,6 +422,47 @@ - (void) validatePaymentRequest { _status = BTCPaymentRequestStatusInvalidSignature; return; } +#else + // On OS X 10.10 we don't have kSecPaddingPKCS1SHA256 and SecKeyRawVerify. + // So we have to verify the signature using Security Transforms API. + // For now, just don't verify until someone has time to + + // Here's a draft of what needs to be done here. + /* + CFErrorRef* error = NULL; + verifier = SecVerifyTransformCreate(publickey, signature, &error); + if (!verifier) { CFShow(error); exit(-1); } + if (!SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, dataForSigning, &error) { + CFShow(error); + exit(-1); + } + // if it's sha256, then set SHA2 digest type and 32 bytes length. + if (!SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA2, &error) { + CFShow(error); + exit(-1); + } + // Not sure if the length is in bytes or bits. + if (!SecTransformSetAttribute(verifier, kSecDigestLengthAttribute, @(32), &error) { + CFShow(error); + exit(-1); + } + + result = SecTransformExecute(verifier, &error); + if (error) { + CFShow(error); + exit(-1); + } + if (result == kCFBooleanTrue) { + // signature is valid + } else { + // signature is invalid. + } + */ + + _status = BTCPaymentRequestStatusUnknown; + _isValid = NO; + return; +#endif _status = BTCPaymentRequestStatusValid; _isValid = YES; From 8e12763c85fcf43d73b3fed5b7652724880e0e09 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 14:19:54 +0200 Subject: [PATCH 013/163] moved fancy encrypted message implementation on the side --- CoreBitcoin.xcodeproj/project.pbxproj | 52 +++++++++---------- ...sts.h => BTCFancyEncryptedMessage+Tests.h} | 6 +-- ...sts.m => BTCFancyEncryptedMessage+Tests.m} | 38 +++++++------- ...edMessage.h => BTCFancyEncryptedMessage.h} | 16 +++--- ...edMessage.m => BTCFancyEncryptedMessage.m} | 24 ++++----- UnitTests/main.m | 4 +- 6 files changed, 70 insertions(+), 70 deletions(-) rename CoreBitcoin/{BTCEncryptedMessage+Tests.h => BTCFancyEncryptedMessage+Tests.h} (57%) rename CoreBitcoin/{BTCEncryptedMessage+Tests.m => BTCFancyEncryptedMessage+Tests.m} (69%) rename CoreBitcoin/{BTCEncryptedMessage.h => BTCFancyEncryptedMessage.h} (91%) rename CoreBitcoin/{BTCEncryptedMessage.m => BTCFancyEncryptedMessage.m} (95%) diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index d5f46555..6caa84ee 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -119,14 +119,14 @@ 204FB507194C63B500C131DE /* BTCBlindSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 204FB501194C63B500C131DE /* BTCBlindSignature.m */; }; 204FB508194C63B500C131DE /* BTCBlindSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 204FB501194C63B500C131DE /* BTCBlindSignature.m */; }; 204FB509194C63B500C131DE /* BTCBlindSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 204FB501194C63B500C131DE /* BTCBlindSignature.m */; }; - 2054DC751950E35E007175C8 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCEncryptedMessage.h */; }; - 2054DC761950E35E007175C8 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCEncryptedMessage.h */; }; - 2054DC771950E35E007175C8 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCEncryptedMessage.h */; }; - 2054DC781950E35E007175C8 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */; }; - 2054DC791950E35E007175C8 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */; }; - 2054DC7A1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */; }; - 2054DC7B1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */; }; - 2054DC7C1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */; }; + 2054DC751950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */; }; + 2054DC761950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */; }; + 2054DC771950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */; }; + 2054DC781950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */; }; + 2054DC791950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */; }; + 2054DC7A1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */; }; + 2054DC7B1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */; }; + 2054DC7C1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */; }; 2057A9CD17CD555F00353D54 /* BTCKey+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2057A9CC17CD555F00353D54 /* BTCKey+Tests.m */; }; 20584B1D18CD0DA000FDD410 /* BTC256.h in Headers */ = {isa = PBXBuildFile; fileRef = 20584B1B18CD0DA000FDD410 /* BTC256.h */; }; 20584B1E18CD0DA000FDD410 /* BTC256.h in Headers */ = {isa = PBXBuildFile; fileRef = 20584B1B18CD0DA000FDD410 /* BTC256.h */; }; @@ -347,7 +347,7 @@ 20C2D80A19E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C2D80319E2F2280022CAAC /* BTCMnemonic+Tests.m */; }; 20C2D80B19E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C2D80319E2F2280022CAAC /* BTCMnemonic+Tests.m */; }; 20C3F16917BFDCF8009DB89C /* libSystem.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 20C3F16817BFDCF8009DB89C /* libSystem.dylib */; }; - 20C4860A1955A88C0061DF75 /* BTCEncryptedMessage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C486091955A88B0061DF75 /* BTCEncryptedMessage+Tests.m */; }; + 20C4860A1955A88C0061DF75 /* BTCFancyEncryptedMessage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */; }; 20CD68DA189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20CD68DB189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20CD68DC189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -447,8 +447,8 @@ 204F8A801845FAF8002649CE /* TODO.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TODO.txt; sourceTree = ""; }; 204FB500194C63B500C131DE /* BTCBlindSignature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCBlindSignature.h; sourceTree = ""; }; 204FB501194C63B500C131DE /* BTCBlindSignature.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCBlindSignature.m; sourceTree = ""; }; - 2054DC731950E35E007175C8 /* BTCEncryptedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCEncryptedMessage.h; sourceTree = ""; }; - 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCEncryptedMessage.m; sourceTree = ""; }; + 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCFancyEncryptedMessage.h; sourceTree = ""; }; + 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCFancyEncryptedMessage.m; sourceTree = ""; }; 2057A9CB17CD555F00353D54 /* BTCKey+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCKey+Tests.h"; sourceTree = ""; }; 2057A9CC17CD555F00353D54 /* BTCKey+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCKey+Tests.m"; sourceTree = ""; }; 20584B1B18CD0DA000FDD410 /* BTC256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTC256.h; sourceTree = ""; }; @@ -552,8 +552,8 @@ 20C2D80219E2F2280022CAAC /* BTCMnemonic+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCMnemonic+Tests.h"; sourceTree = ""; }; 20C2D80319E2F2280022CAAC /* BTCMnemonic+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCMnemonic+Tests.m"; sourceTree = ""; }; 20C3F16817BFDCF8009DB89C /* libSystem.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libSystem.dylib; path = usr/lib/libSystem.dylib; sourceTree = SDKROOT; }; - 20C486081955A88B0061DF75 /* BTCEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCEncryptedMessage+Tests.h"; sourceTree = ""; }; - 20C486091955A88B0061DF75 /* BTCEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCEncryptedMessage+Tests.m"; sourceTree = ""; }; + 20C486081955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCFancyEncryptedMessage+Tests.h"; sourceTree = ""; }; + 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCFancyEncryptedMessage+Tests.m"; sourceTree = ""; }; 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCCurvePoint.h; sourceTree = ""; }; 20CD68D9189B18820083E1A9 /* BTCCurvePoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCCurvePoint.m; sourceTree = ""; }; 20D008C018D1AFA800079B79 /* BTC256+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTC256+Tests.h"; sourceTree = ""; }; @@ -859,10 +859,10 @@ 204FB501194C63B500C131DE /* BTCBlindSignature.m */, 208642B9194C9EAF003A3A2C /* BTCBlindSignature+Tests.h */, 208642BA194C9EAF003A3A2C /* BTCBlindSignature+Tests.m */, - 2054DC731950E35E007175C8 /* BTCEncryptedMessage.h */, - 2054DC741950E35E007175C8 /* BTCEncryptedMessage.m */, - 20C486081955A88B0061DF75 /* BTCEncryptedMessage+Tests.h */, - 20C486091955A88B0061DF75 /* BTCEncryptedMessage+Tests.m */, + 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */, + 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */, + 20C486081955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.h */, + 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */, 2084DD7517B8FF76005AC9E6 /* BTCProtocolSerialization.h */, 2084DD7617B8FF76005AC9E6 /* BTCProtocolSerialization.m */, 2084DD7717B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.h */, @@ -961,7 +961,7 @@ 20148C321835650B00E68E9C /* BTCBase58+Tests.h in Headers */, 207646E71A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, 20148C331835650B00E68E9C /* NS+BTCBase58.h in Headers */, - 2054DC761950E35E007175C8 /* BTCEncryptedMessage.h in Headers */, + 2054DC761950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */, 20148C341835650B00E68E9C /* BTCBigNumber.h in Headers */, 207B2606188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA518BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, @@ -1019,7 +1019,7 @@ 20148CDC183643FC00E68E9C /* BTCBase58+Tests.h in Headers */, 207646E81A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, 20148CDD183643FC00E68E9C /* NS+BTCBase58.h in Headers */, - 2054DC771950E35E007175C8 /* BTCEncryptedMessage.h in Headers */, + 2054DC771950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */, 20148CDE183643FC00E68E9C /* BTCBigNumber.h in Headers */, 207B2607188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA618BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, @@ -1077,7 +1077,7 @@ 206B014F1835484300878B8D /* BTCScript+Tests.h in Headers */, 207646E61A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, 206B01441835484300878B8D /* BTCBase58.h in Headers */, - 2054DC751950E35E007175C8 /* BTCEncryptedMessage.h in Headers */, + 2054DC751950E35E007175C8 /* BTCFancyEncryptedMessage.h in Headers */, 206B01471835484300878B8D /* BTCBigNumber.h in Headers */, 207B2605188DC47800916AE6 /* BTCBlockchainInfo.h in Headers */, 20D09BA418BA14F900794209 /* CoreBitcoin+Categories.h in Headers */, @@ -1275,7 +1275,7 @@ 20B8AB9A189EE88300008138 /* BTCKeychain.m in Sources */, 20148B1218355DAD00E68E9C /* BTCProtocolSerialization.m in Sources */, 20148B1418355DAD00E68E9C /* BTCOpcode.m in Sources */, - 2054DC7A1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, + 2054DC7A1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, 20CD68DF189B18820083E1A9 /* BTCCurvePoint.m in Sources */, 20D09C6018BC016C00794209 /* BTCBlock.m in Sources */, 20148B1518355DAD00E68E9C /* BTCScript.m in Sources */, @@ -1322,7 +1322,7 @@ 20B8AB9B189EE88300008138 /* BTCKeychain.m in Sources */, 20148C20183563D000E68E9C /* BTCProtocolSerialization.m in Sources */, 20148C22183563D000E68E9C /* BTCOpcode.m in Sources */, - 2054DC7B1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, + 2054DC7B1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, 20CD68E0189B18820083E1A9 /* BTCCurvePoint.m in Sources */, 20D09C6118BC016C00794209 /* BTCBlock.m in Sources */, 20148C23183563D000E68E9C /* BTCScript.m in Sources */, @@ -1369,7 +1369,7 @@ 20B8AB9C189EE88300008138 /* BTCKeychain.m in Sources */, 20148CCB183643E700E68E9C /* BTCProtocolSerialization.m in Sources */, 20148CCD183643E700E68E9C /* BTCOpcode.m in Sources */, - 2054DC7C1950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, + 2054DC7C1950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, 20CD68E1189B18820083E1A9 /* BTCCurvePoint.m in Sources */, 20D09C6218BC016C00794209 /* BTCBlock.m in Sources */, 20148CCE183643E700E68E9C /* BTCScript.m in Sources */, @@ -1416,7 +1416,7 @@ 20D008BE18CFEAD000079B79 /* BTC256.m in Sources */, 206B01671835485D00878B8D /* BTCTransaction.m in Sources */, 20B8AB99189EE88300008138 /* BTCKeychain.m in Sources */, - 2054DC791950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, + 2054DC791950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, 206B01641835485D00878B8D /* BTCScript.m in Sources */, 20D09C5F18BC016C00794209 /* BTCBlock.m in Sources */, 20CD68DE189B18820083E1A9 /* BTCCurvePoint.m in Sources */, @@ -1439,7 +1439,7 @@ files = ( 20A443B81AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, 2084DD6117B8FF47005AC9E6 /* main.m in Sources */, - 20C4860A1955A88C0061DF75 /* BTCEncryptedMessage+Tests.m in Sources */, + 20C4860A1955A88C0061DF75 /* BTCFancyEncryptedMessage+Tests.m in Sources */, 20B8AB98189EE88300008138 /* BTCKeychain.m in Sources */, 2084DD8617B8FF76005AC9E6 /* BTCAddress.m in Sources */, 2037AB1517D3BFF900DB248C /* BTCScript+Tests.m in Sources */, @@ -1454,7 +1454,7 @@ 2084DD8D17B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.m in Sources */, 2060A2831AAA077A004531FD /* BTCMerkleTree.m in Sources */, 20D008C218D1AFA800079B79 /* BTC256+Tests.m in Sources */, - 2054DC781950E35E007175C8 /* BTCEncryptedMessage.m in Sources */, + 2054DC781950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, 200756DA1A5D6A44009A24A9 /* BTCPriceSource+Tests.m in Sources */, 2084DD8E17B8FF76005AC9E6 /* BTCScript.m in Sources */, 20D09C5E18BC016C00794209 /* BTCBlock.m in Sources */, diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.h b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h similarity index 57% rename from CoreBitcoin/BTCEncryptedMessage+Tests.h rename to CoreBitcoin/BTCFancyEncryptedMessage+Tests.h index 00717178..316fcbf3 100644 --- a/CoreBitcoin/BTCEncryptedMessage+Tests.h +++ b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h @@ -1,14 +1,14 @@ // -// BTCEncryptedMessage+Tests.h +// BTCFancyEncryptedMessage+Tests.h // CoreBitcoin // // Created by Oleg Andreev on 21.06.2014. // Copyright (c) 2014 Oleg Andreev. All rights reserved. // -#import "BTCEncryptedMessage.h" +#import "BTCFancyEncryptedMessage.h" -@interface BTCEncryptedMessage (Tests) +@interface BTCFancyEncryptedMessage (Tests) + (void) runAllTests; diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.m b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.m similarity index 69% rename from CoreBitcoin/BTCEncryptedMessage+Tests.m rename to CoreBitcoin/BTCFancyEncryptedMessage+Tests.m index 1e75c2a8..e8ede459 100644 --- a/CoreBitcoin/BTCEncryptedMessage+Tests.m +++ b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.m @@ -1,19 +1,19 @@ // -// BTCEncryptedMessage+Tests.m +// BTCFancyEncryptedMessage+Tests.m // CoreBitcoin // // Created by Oleg Andreev on 21.06.2014. // Copyright (c) 2014 Oleg Andreev. All rights reserved. // -#import "BTCEncryptedMessage+Tests.h" +#import "BTCFancyEncryptedMessage+Tests.h" #import "BTCKey.h" #import "BTCData.h" #import "BTCBigNumber.h" #import "BTCCurvePoint.h" -@implementation BTCEncryptedMessage (Tests) +@implementation BTCFancyEncryptedMessage (Tests) + (void) runAllTests { @@ -27,7 +27,7 @@ + (void) testMessages NSString* originalString = @"Hello!"; - BTCEncryptedMessage* msg = [[BTCEncryptedMessage alloc] initWithData:[originalString dataUsingEncoding:NSUTF8StringEncoding]]; + BTCFancyEncryptedMessage* msg = [[BTCFancyEncryptedMessage alloc] initWithData:[originalString dataUsingEncoding:NSUTF8StringEncoding]]; msg.difficultyTarget = 0x00FFFFFF; @@ -39,7 +39,7 @@ + (void) testMessages //NSLog(@"encrypted msg = %@ hash: %@...", BTCHexFromData(encryptedMsg), BTCHexFromData([BTCHash256(encryptedMsg) subdataWithRange:NSMakeRange(0, 8)])); - BTCEncryptedMessage* receivedMsg = [[BTCEncryptedMessage alloc] initWithEncryptedData:encryptedMsg]; + BTCFancyEncryptedMessage* receivedMsg = [[BTCFancyEncryptedMessage alloc] initWithEncryptedData:encryptedMsg]; NSAssert(receivedMsg, @"pow and format are correct"); @@ -57,18 +57,18 @@ + (void) testMessages + (void) testProofOfWork { - NSAssert([BTCEncryptedMessage targetForCompactTarget:0] == 0, @"0x00 -> 0"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:0xFF] == 0xFFFFFFFF, @"0x00 -> 0"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:1] == 0, @"order is zero"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:2] == 0, @"order is zero"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:3] == 0, @"order is zero"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:4] == 1, @"order is zero, and tail starts with 1"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:5] == 1, @"order is zero, and tail starts with 1"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:6] == 1, @"order is zero, and tail starts with 1"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:7] == 1, @"order is zero, and tail starts with 1"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:8] == 2, @"order is one, but tail is zero"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:8+3] == 2, @"order is one, but tail is zero"); - NSAssert([BTCEncryptedMessage targetForCompactTarget:8+4] == 3, @"order is one, and tail starts with 1"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:0] == 0, @"0x00 -> 0"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:0xFF] == 0xFFFFFFFF, @"0x00 -> 0"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:1] == 0, @"order is zero"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:2] == 0, @"order is zero"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:3] == 0, @"order is zero"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:4] == 1, @"order is zero, and tail starts with 1"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:5] == 1, @"order is zero, and tail starts with 1"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:6] == 1, @"order is zero, and tail starts with 1"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:7] == 1, @"order is zero, and tail starts with 1"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:8] == 2, @"order is one, but tail is zero"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:8+3] == 2, @"order is one, but tail is zero"); + NSAssert([BTCFancyEncryptedMessage targetForCompactTarget:8+4] == 3, @"order is one, and tail starts with 1"); uint8_t t = 0; do @@ -80,9 +80,9 @@ + (void) testProofOfWork if (order == 1) nt = t & (0xff - 1 - 2); if (order == 2) nt = t & (0xff - 1); - uint32_t target = [BTCEncryptedMessage targetForCompactTarget:t]; + uint32_t target = [BTCFancyEncryptedMessage targetForCompactTarget:t]; - uint8_t t2 = [BTCEncryptedMessage compactTargetForTarget:target]; + uint8_t t2 = [BTCFancyEncryptedMessage compactTargetForTarget:target]; // uncomment this line to visualize data diff --git a/CoreBitcoin/BTCEncryptedMessage.h b/CoreBitcoin/BTCFancyEncryptedMessage.h similarity index 91% rename from CoreBitcoin/BTCEncryptedMessage.h rename to CoreBitcoin/BTCFancyEncryptedMessage.h index 77106bd9..64b7a470 100644 --- a/CoreBitcoin/BTCEncryptedMessage.h +++ b/CoreBitcoin/BTCFancyEncryptedMessage.h @@ -24,27 +24,27 @@ // We do not add checksum of the entire message as it's a job of a transport layer. // Also, a reasonably difficult proof-of-work implicitly acts as a checksum of the entire message. -static unsigned char BTCEncryptedMessageVersion[4] = {0xc1, 0xb9, 0xf0, 0xe3}; +static unsigned char BTCFancyEncryptedMessageVersion[4] = {0xc1, 0xb9, 0xf0, 0xe3}; -typedef NS_ENUM(uint8_t, BTCEncryptedMessageAddressLength) { +typedef NS_ENUM(uint8_t, BTCFancyEncryptedMessageAddressLength) { // No address is given, recipient must attempt to decrypt the message to figure if he is indeed a recipient. - BTCEncryptedMessageAddressLengthNone = 0, + BTCFancyEncryptedMessageAddressLengthNone = 0, // First 4 bits of the address. To route message efficiently to 6% of all users yet keeping recipient secret. - BTCEncryptedMessageAddressLengthLightRouting = 4, + BTCFancyEncryptedMessageAddressLengthLightRouting = 4, // First 7 bits of the address. To route message to 0.8% of the users. Good anonymity of recipient is maintained if number of users is more than 200K. - BTCEncryptedMessageAddressLengthNormalRouting = 7, + BTCFancyEncryptedMessageAddressLengthNormalRouting = 7, // Fingerprint is a short 32-bit identifier to be compact, but yet allow fast identification like in BIP32 / BTCKeychain. - BTCEncryptedMessageAddressLengthFingerprint = 32, + BTCFancyEncryptedMessageAddressLengthFingerprint = 32, // Full 160-bit address fully identifying the recipient. - BTCEncryptedMessageAddressLengthFull = 160, + BTCFancyEncryptedMessageAddressLengthFull = 160, }; @class BTCKey; -@interface BTCEncryptedMessage : NSObject +@interface BTCFancyEncryptedMessage : NSObject // Proof-of-work difficulty target. // Set it before encrypting the message that needs some Proof-of-Work attached. diff --git a/CoreBitcoin/BTCEncryptedMessage.m b/CoreBitcoin/BTCFancyEncryptedMessage.m similarity index 95% rename from CoreBitcoin/BTCEncryptedMessage.m rename to CoreBitcoin/BTCFancyEncryptedMessage.m index 472ff34c..119d773f 100644 --- a/CoreBitcoin/BTCEncryptedMessage.m +++ b/CoreBitcoin/BTCFancyEncryptedMessage.m @@ -1,7 +1,7 @@ // CoreBitcoin by Oleg Andreev , WTFPL. #import "BTCErrors.h" -#import "BTCEncryptedMessage.h" +#import "BTCFancyEncryptedMessage.h" #import "BTCData.h" #import "BTCKey.h" #import "BTCCurvePoint.h" @@ -10,12 +10,12 @@ #import // First 16 bytes of the hash of a shared key and decrypted message appended to the end of the complete message. -#define BTCEncryptedMessageChecksumLength 16 +#define BTCFancyEncryptedMessageChecksumLength 16 static uint8_t BTCEMCompactTargetForFullTarget(uint32_t fullTarget); static uint32_t BTCEMFullTargetForCompactTarget(uint8_t compactTarget); -@implementation BTCEncryptedMessage { +@implementation BTCFancyEncryptedMessage { // Decrypted message data: @@ -35,7 +35,7 @@ - (id) initWithData:(NSData*)data if (self = [super init]) { _difficultyTarget = 0xFFFFFFFF; - _addressLength = BTCEncryptedMessageAddressLengthNone; + _addressLength = BTCFancyEncryptedMessageAddressLengthNone; _decryptedData = data; } return self; @@ -98,7 +98,7 @@ - (NSData*) encryptedDataWithKey:(BTCKey*)recipientKey seed:(NSData*)seed0 [messageData setLength:0]; - [messageData appendBytes:BTCEncryptedMessageVersion length:4]; + [messageData appendBytes:BTCFancyEncryptedMessageVersion length:4]; [messageData appendBytes:&compactTarget length:1]; @@ -223,7 +223,7 @@ - (NSData*) encryptedDataWithKey:(BTCKey*)recipientKey seed:(NSData*)seed0 CC_SHA256_Update(&ctx256, digest256, CC_SHA256_DIGEST_LENGTH); CC_SHA256_Final(digest256, &ctx256); - [messageData appendBytes:digest256 length:BTCEncryptedMessageChecksumLength]; + [messageData appendBytes:digest256 length:BTCFancyEncryptedMessageChecksumLength]; BTCSecureMemset(&ctx256, 0, sizeof(ctx256)); @@ -285,7 +285,7 @@ - (id) initWithEncryptedData:(NSData*)data // Check for minimum length. uint32_t datalength = (uint32_t)data.length; - if (datalength < (4 + 1 + 1 + 4 + 1 + 1 + 32 + 1 + BTCEncryptedMessageChecksumLength)) return nil; + if (datalength < (4 + 1 + 1 + 4 + 1 + 1 + 32 + 1 + BTCFancyEncryptedMessageChecksumLength)) return nil; // Compute full hash for PoW. unsigned char digest512[CC_SHA512_DIGEST_LENGTH]; @@ -297,7 +297,7 @@ - (id) initWithEncryptedData:(NSData*)data uint8_t* msgbytes = (uint8_t*)data.bytes; // Check the magic prefix. - if (memcmp(BTCEncryptedMessageVersion, msgbytes, sizeof(BTCEncryptedMessageVersion)) != 0) + if (memcmp(BTCFancyEncryptedMessageVersion, msgbytes, sizeof(BTCFancyEncryptedMessageVersion)) != 0) { return nil; } @@ -370,9 +370,9 @@ - (id) initWithEncryptedData:(NSData*)data offset += encodedMessageLength; - if (datalength < offset + BTCEncryptedMessageChecksumLength) return nil; + if (datalength < offset + BTCFancyEncryptedMessageChecksumLength) return nil; - _checksum = [data subdataWithRange:NSMakeRange(offset, BTCEncryptedMessageChecksumLength)]; + _checksum = [data subdataWithRange:NSMakeRange(offset, BTCFancyEncryptedMessageChecksumLength)]; // Now the user must call decryptedDataWithKey: with some of his keys to see if this message is for him or not. } @@ -468,13 +468,13 @@ - (NSData*) decryptedDataWithKey:(BTCKey*)key error:(NSError**)errorOut CC_SHA256_Update(&ctx256, digest256, CC_SHA256_DIGEST_LENGTH); CC_SHA256_Final(digest256, &ctx256); - if (_checksum.length != BTCEncryptedMessageChecksumLength) + if (_checksum.length != BTCFancyEncryptedMessageChecksumLength) { BTCSecureMemset(digest256, 0, CC_SHA256_DIGEST_LENGTH); return nil; } - if (memcmp(digest256, _checksum.bytes, BTCEncryptedMessageChecksumLength) != 0) + if (memcmp(digest256, _checksum.bytes, BTCFancyEncryptedMessageChecksumLength) != 0) { BTCSecureMemset(digest256, 0, CC_SHA256_DIGEST_LENGTH); return nil; diff --git a/UnitTests/main.m b/UnitTests/main.m index 0f715da0..ce6f20ea 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -11,7 +11,7 @@ #import "BTCKeychain+Tests.h" #import "BTCCurvePoint+Tests.h" #import "BTCBlindSignature+Tests.h" -#import "BTCEncryptedMessage+Tests.h" +#import "BTCFancyEncryptedMessage+Tests.h" #import "BTCScript+Tests.h" #import "BTCTransaction+Tests.h" #import "BTCBlockchainInfo+Tests.h" @@ -33,7 +33,7 @@ int main(int argc, const char * argv[]) [BTCCurvePoint runAllTests]; [BTCKeychain runAllTests]; [BTCBlindSignature runAllTests]; - [BTCEncryptedMessage runAllTests]; + [BTCFancyEncryptedMessage runAllTests]; [BTCScript runAllTests]; [BTCMerkleTree runAllTests]; [BTCBlockchainInfo runAllTests]; From 730b1a32a24501b1cdcae99a06f7005d7f662132 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 14:55:22 +0200 Subject: [PATCH 014/163] added BTCSHA512 --- CoreBitcoin/BTCData.h | 1 + CoreBitcoin/BTCData.m | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CoreBitcoin/BTCData.h b/CoreBitcoin/BTCData.h index fd8c1be4..f51823ba 100644 --- a/CoreBitcoin/BTCData.h +++ b/CoreBitcoin/BTCData.h @@ -63,6 +63,7 @@ NSMutableData* BTCDataRange(NSData* data, NSRange range); // If the argument is nil, returns nil. NSMutableData* BTCSHA1(NSData* data); NSMutableData* BTCSHA256(NSData* data); +NSMutableData* BTCSHA512(NSData* data); NSMutableData* BTCSHA256Concat(NSData* data1, NSData* data2); // SHA256(data1 || data2) NSMutableData* BTCHash256(NSData* data); // == SHA256(SHA256(data)) (aka Hash() in BitcoinQT) NSMutableData* BTCHash256Concat(NSData* data1, NSData* data2); // SHA256(SHA256(data1 || data2)) diff --git a/CoreBitcoin/BTCData.m b/CoreBitcoin/BTCData.m index c332f4a5..f94edde8 100644 --- a/CoreBitcoin/BTCData.m +++ b/CoreBitcoin/BTCData.m @@ -328,6 +328,23 @@ BOOL BTCDataClear(NSData* data) return result; } +NSMutableData* BTCSHA512(NSData* data) +{ + if (!data) return nil; + unsigned char digest[CC_SHA512_DIGEST_LENGTH]; + + __block CC_SHA512_CTX ctx; + CC_SHA512_Init(&ctx); + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { + CC_SHA512_Update(&ctx, bytes, (CC_LONG)byteRange.length); + }]; + CC_SHA512_Final(digest, &ctx); + + NSMutableData* result = [NSMutableData dataWithBytes:digest length:CC_SHA512_DIGEST_LENGTH]; + BTCSecureMemset(digest, 0, CC_SHA512_DIGEST_LENGTH); + return result; +} + NSMutableData* BTCSHA256Concat(NSData* data1, NSData* data2) { if (!data1 || !data2) return nil; From a92d38dc1bfd95de201bdd2186b024c2c7f6f4e6 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 15:40:45 +0200 Subject: [PATCH 015/163] basic support for ECIES --- CoreBitcoin.xcodeproj/project.pbxproj | 26 ++++ CoreBitcoin/BTCEncryptedMessage+Tests.h | 15 +++ CoreBitcoin/BTCEncryptedMessage+Tests.m | 35 ++++++ CoreBitcoin/BTCEncryptedMessage.h | 20 ++++ CoreBitcoin/BTCEncryptedMessage.m | 150 ++++++++++++++++++++++++ CoreBitcoin/CoreBitcoin.h | 1 + UnitTests/main.m | 2 + 7 files changed, 249 insertions(+) create mode 100644 CoreBitcoin/BTCEncryptedMessage+Tests.h create mode 100644 CoreBitcoin/BTCEncryptedMessage+Tests.m create mode 100644 CoreBitcoin/BTCEncryptedMessage.h create mode 100644 CoreBitcoin/BTCEncryptedMessage.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 6caa84ee..78338d76 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -306,6 +306,15 @@ 20A443C21AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; 20A443C31AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; 20A443C41AC55F52008B3447 /* BTCProtocolBuffers.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */; }; + 20A443C71AC82594008B3447 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443C51AC82594008B3447 /* BTCEncryptedMessage.h */; }; + 20A443C81AC82594008B3447 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443C51AC82594008B3447 /* BTCEncryptedMessage.h */; }; + 20A443C91AC82594008B3447 /* BTCEncryptedMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443C51AC82594008B3447 /* BTCEncryptedMessage.h */; }; + 20A443CA1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; + 20A443CB1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; + 20A443CC1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; + 20A443CD1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; + 20A443CE1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; + 20A443D11AC825DA008B3447 /* BTCEncryptedMessage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */; }; 20B5A64018924F350035582D /* BTCTransaction+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */; }; 20B66C8717BFE4F9007128CE /* BTCErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B66C8617BFE4F9007128CE /* BTCErrors.m */; }; 20B8AB92189E7E0100008138 /* BTCCurvePoint+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B8AB91189E7E0100008138 /* BTCCurvePoint+Tests.m */; }; @@ -529,6 +538,10 @@ 20A443B21AC55F52008B3447 /* BTCPaymentProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCPaymentProtocol.m; sourceTree = ""; }; 20A443B31AC55F52008B3447 /* BTCProtocolBuffers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCProtocolBuffers.h; sourceTree = ""; }; 20A443B41AC55F52008B3447 /* BTCProtocolBuffers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCProtocolBuffers.m; sourceTree = ""; }; + 20A443C51AC82594008B3447 /* BTCEncryptedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCEncryptedMessage.h; sourceTree = ""; }; + 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCEncryptedMessage.m; sourceTree = ""; }; + 20A443CF1AC825DA008B3447 /* BTCEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCEncryptedMessage+Tests.h"; sourceTree = ""; }; + 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCEncryptedMessage+Tests.m"; sourceTree = ""; }; 20B5A63E18924F350035582D /* BTCTransaction+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCTransaction+Tests.h"; sourceTree = ""; }; 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCTransaction+Tests.m"; sourceTree = ""; }; 20B66C8517BFE4F9007128CE /* BTCErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCErrors.h; sourceTree = ""; }; @@ -863,6 +876,10 @@ 2054DC741950E35E007175C8 /* BTCFancyEncryptedMessage.m */, 20C486081955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.h */, 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */, + 20A443C51AC82594008B3447 /* BTCEncryptedMessage.h */, + 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */, + 20A443CF1AC825DA008B3447 /* BTCEncryptedMessage+Tests.h */, + 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */, 2084DD7517B8FF76005AC9E6 /* BTCProtocolSerialization.h */, 2084DD7617B8FF76005AC9E6 /* BTCProtocolSerialization.m */, 2084DD7717B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.h */, @@ -954,6 +971,7 @@ 20C2D7FB19E2B2920022CAAC /* BTCMnemonic.h in Headers */, 20D09C5218BC012700794209 /* BTCBlockHeader.h in Headers */, 20148C2E1835650B00E68E9C /* BTCErrors.h in Headers */, + 20A443C81AC82594008B3447 /* BTCEncryptedMessage.h in Headers */, 207646FB1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 20148C2F1835650B00E68E9C /* BTCAddress.h in Headers */, 20148C301835650B00E68E9C /* BTCAddress+Tests.h in Headers */, @@ -1012,6 +1030,7 @@ 20C2D7FC19E2B2920022CAAC /* BTCMnemonic.h in Headers */, 20D09C5318BC012700794209 /* BTCBlockHeader.h in Headers */, 20148CD8183643FC00E68E9C /* BTCErrors.h in Headers */, + 20A443C91AC82594008B3447 /* BTCEncryptedMessage.h in Headers */, 207646FC1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 20148CD9183643FC00E68E9C /* BTCAddress.h in Headers */, 20148CDA183643FC00E68E9C /* BTCAddress+Tests.h in Headers */, @@ -1070,6 +1089,7 @@ 20C2D7FA19E2B2920022CAAC /* BTCMnemonic.h in Headers */, 20D09C5118BC012700794209 /* BTCBlockHeader.h in Headers */, 206B01501835484300878B8D /* BTCScriptMachine.h in Headers */, + 20A443C71AC82594008B3447 /* BTCEncryptedMessage.h in Headers */, 207646FA1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 206B013E1835484300878B8D /* BTCData.h in Headers */, 206B014B1835484300878B8D /* BTCProtocolSerialization.h in Headers */, @@ -1252,6 +1272,7 @@ 20148B0518355DAD00E68E9C /* BTCData.m in Sources */, 20D09C5618BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D7FF19E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 20A443CC1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */, 2060A2851AAA077A004531FD /* BTCMerkleTree.m in Sources */, 207647091A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148B0718355DAD00E68E9C /* NSData+BTCData.m in Sources */, @@ -1299,6 +1320,7 @@ 20148C13183563D000E68E9C /* BTCData.m in Sources */, 20D09C5718BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D80019E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 20A443CD1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */, 2060A2861AAA077A004531FD /* BTCMerkleTree.m in Sources */, 2076470A1A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148C15183563D000E68E9C /* NSData+BTCData.m in Sources */, @@ -1346,6 +1368,7 @@ 20148CBE183643E700E68E9C /* BTCData.m in Sources */, 20D09C5818BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D80119E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 20A443CE1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */, 2060A2871AAA077A004531FD /* BTCMerkleTree.m in Sources */, 2076470B1A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 20148CC0183643E700E68E9C /* NSData+BTCData.m in Sources */, @@ -1393,6 +1416,7 @@ 206B015D1835485D00878B8D /* BTCBigNumber.m in Sources */, 20D09C5518BC012700794209 /* BTCBlockHeader.m in Sources */, 20C2D7FE19E2B2920022CAAC /* BTCMnemonic.m in Sources */, + 20A443CB1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */, 2060A2841AAA077A004531FD /* BTCMerkleTree.m in Sources */, 207647081A0A8C15000F00F2 /* BTCCurrencyConverter.m in Sources */, 206B01561835485D00878B8D /* NSData+BTCData.m in Sources */, @@ -1438,6 +1462,7 @@ buildActionMask = 2147483647; files = ( 20A443B81AC55F52008B3447 /* BTCPaymentProtocol.m in Sources */, + 20A443D11AC825DA008B3447 /* BTCEncryptedMessage+Tests.m in Sources */, 2084DD6117B8FF47005AC9E6 /* main.m in Sources */, 20C4860A1955A88C0061DF75 /* BTCFancyEncryptedMessage+Tests.m in Sources */, 20B8AB98189EE88300008138 /* BTCKeychain.m in Sources */, @@ -1445,6 +1470,7 @@ 2037AB1517D3BFF900DB248C /* BTCScript+Tests.m in Sources */, 20B8AB9F189F0CEF00008138 /* BTCKeychain+Tests.m in Sources */, 2084DD8717B8FF76005AC9E6 /* BTCAddress+Tests.m in Sources */, + 20A443CA1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */, 2084DD8817B8FF76005AC9E6 /* BTCBase58.m in Sources */, 2084DD8917B8FF76005AC9E6 /* BTCBigNumber.m in Sources */, 2084DD8A17B8FF76005AC9E6 /* BTCBigNumber+Tests.m in Sources */, diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.h b/CoreBitcoin/BTCEncryptedMessage+Tests.h new file mode 100644 index 00000000..4516fc0d --- /dev/null +++ b/CoreBitcoin/BTCEncryptedMessage+Tests.h @@ -0,0 +1,15 @@ +// +// BTCEncryptedMessage+Tests.h +// CoreBitcoin +// +// Created by Oleg Andreev on 29.03.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCEncryptedMessage.h" + +@interface BTCEncryptedMessage (Tests) + ++ (void) runAllTests; + +@end diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.m b/CoreBitcoin/BTCEncryptedMessage+Tests.m new file mode 100644 index 00000000..40deb147 --- /dev/null +++ b/CoreBitcoin/BTCEncryptedMessage+Tests.m @@ -0,0 +1,35 @@ +// +// BTCEncryptedMessage+Tests.m +// CoreBitcoin +// +// Created by Oleg Andreev on 29.03.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCEncryptedMessage+Tests.h" +#import "BTCKey.h" +#import "BTCData.h" + +@implementation BTCEncryptedMessage (Tests) + ++ (void) runAllTests { + + BTCEncryptedMessage* em = [[BTCEncryptedMessage alloc] init]; + em.senderKey = [[BTCKey alloc] initWithWIF:@"L1Ejc5dAigm5XrM3mNptMEsNnHzS7s51YxU7J61ewGshZTKkbmzJ"]; + em.recipientKey = [[BTCKey alloc] initWithWIF:@"KxfxrUXSMjJQcb3JgnaaA6MqsrKQ1nBSxvhuigdKRyFiEm6BZDgG"]; + + NSData* message = [@"attack at dawn" dataUsingEncoding:NSUTF8StringEncoding]; + NSData* expectedCiphertext = BTCDataFromHex(@"0339e504d6492b082da96e11e8f039796b06cd4855c101e2492a6f10f3e056a9e712c732611c6917ab5c57a1926973bc44a1586e94a783f81d05ce72518d9b0a80e2e13c7ff7d1306583f9cc7a48def5b37fbf2d5f294f128472a6e9c78dede5f5"); + + NSData* ciphertext = [em encrypt:message]; + NSAssert([ciphertext isEqualTo:expectedCiphertext], @"Must encrypt correctly"); + + + // Must decrypt. + + NSData* plaintext = [em decrypt:expectedCiphertext]; + NSAssert([plaintext isEqualTo:message], @"Must decrypt correctly"); +} + + +@end diff --git a/CoreBitcoin/BTCEncryptedMessage.h b/CoreBitcoin/BTCEncryptedMessage.h new file mode 100644 index 00000000..7521db7f --- /dev/null +++ b/CoreBitcoin/BTCEncryptedMessage.h @@ -0,0 +1,20 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import + +@class BTCKey; + +// Implementation of [ECIES](http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme) +// compatible with [Bitcore ECIES](https://github.com/bitpay/bitcore-ecies) implementation. +@interface BTCEncryptedMessage : NSObject + +// When encrypting, sender's keypair must contain a private key. +@property(nonatomic) BTCKey* senderKey; + +// When decrypting, recipient's keypair must contain a private key. +@property(nonatomic) BTCKey* recipientKey; + +- (NSData*) encrypt:(NSData*)plaintext; +- (NSData*) decrypt:(NSData*)ciphertext; + +@end diff --git a/CoreBitcoin/BTCEncryptedMessage.m b/CoreBitcoin/BTCEncryptedMessage.m new file mode 100644 index 00000000..8e9821b7 --- /dev/null +++ b/CoreBitcoin/BTCEncryptedMessage.m @@ -0,0 +1,150 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCKey.h" +#import "BTCData.h" +#import "BTCCurvePoint.h" +#import "BTCBigNumber.h" +#import "BTCEncryptedMessage.h" +#import + +@implementation BTCEncryptedMessage + +- (NSData*) encrypt:(NSData*)plaintext { + + NSData* privkey = self.senderKey.privateKey; + NSData* iv = [BTCHMACSHA256(privkey, plaintext) subdataWithRange:NSMakeRange(0, 16)]; + + BTCBigNumber* r = [[BTCBigNumber alloc] initWithUnsignedBigEndian:privkey]; + NSData* Rbuf = self.senderKey.compressedPublicKey; + BTCCurvePoint* cp = self.recipientKey.curvePoint; + BTCCurvePoint* P = [cp multiply:r]; + BTCBigNumber* S = P.x; + NSData* Sbuf = S.unsignedBigEndian; // ensures padding to 32 bytes + NSData* kEkM = BTCSHA512(Sbuf); + NSData* kE = [kEkM subdataWithRange:NSMakeRange(0, 32)]; + NSData* kM = [kEkM subdataWithRange:NSMakeRange(32, 32)]; + + size_t dataOutMoved = 0; + NSMutableData* ivct = [NSMutableData dataWithLength:16 + plaintext.length + 16]; // IV + cipher text + memcpy(ivct.mutableBytes, iv.bytes, iv.length); // put IV in front, to match Bitcore-ECIES + CCCryptorStatus cryptstatus = CCCrypt( + kCCEncrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES, // CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ + kCCOptionPKCS7Padding, // CCOptions options, /* kCCOptionPKCS7Padding, etc. */ + kE.bytes, // const void *key, + kE.length, // size_t keyLength, + iv.bytes, // const void *iv, /* optional initialization vector */ + plaintext.bytes, // const void *dataIn, /* optional per op and alg */ + plaintext.length, // size_t dataInLength, + ivct.mutableBytes + iv.length, // void *dataOut, /* data RETURNED here */ + ivct.length - iv.length, // size_t dataOutAvailable, + &dataOutMoved // size_t *dataOutMoved + ); + + if (cryptstatus != kCCSuccess) { + return nil; + } + + [ivct setLength:dataOutMoved + iv.length]; + + NSData* d = BTCHMACSHA256(kM, ivct); + + NSMutableData* result = [NSMutableData dataWithData:Rbuf]; + [result appendData:ivct]; + [result appendData:d]; + return result; + +// Based on Bitcore JS implementation: +// var r = this._privateKey.bn; +// var Rpubkey = this._privateKey.publicKey; +// var Rbuf = Rpubkey.toDER(true); +// var KB = this._publicKey.point; +// var P = KB.mul(r); +// var S = P.getX(); +// var Sbuf = S.toBuffer({size: 32}); +// var kEkM = Hash.sha512(Sbuf); +// var kE = kEkM.slice(0, 32); +// var kM = kEkM.slice(32, 64); +// var c = AESCBC.encryptCipherkey(message, kE, ivbuf); +// var d = Hash.sha256hmac(c, kM); +// var encbuf = Buffer.concat([Rbuf, c, d]); +// return encbuf; +} + +- (NSData*) decrypt:(NSData*)ciphertext { + + if (ciphertext.length < (33 + 16 + 16 + 32)) { + return nil; + } + + NSData* privkey = self.recipientKey.privateKey; + BTCBigNumber* kB = [[BTCBigNumber alloc] initWithUnsignedBigEndian:privkey]; + + self.senderKey = [[BTCKey alloc] initWithPublicKey:[ciphertext subdataWithRange:NSMakeRange(0, 33)]]; + BTCCurvePoint* R = self.senderKey.curvePoint; + BTCCurvePoint* P = [R multiply:kB]; + BTCBigNumber* S = P.x; + NSData* Sbuf = S.unsignedBigEndian; // ensures padding to 32 bytes + NSData* kEkM = BTCSHA512(Sbuf); + NSData* kE = [kEkM subdataWithRange:NSMakeRange(0, 32)]; + NSData* kM = [kEkM subdataWithRange:NSMakeRange(32, 32)]; + + NSData* ivct = [ciphertext subdataWithRange:NSMakeRange(33, ciphertext.length - 32 - 33)]; + NSData* d = [ciphertext subdataWithRange:NSMakeRange(ciphertext.length - 32, 32)]; + + NSData* d2 = BTCHMACSHA256(kM, ivct); + + if (![d isEqual:d2]) { + // Invalid checksum. + return nil; + } + + size_t dataOutMoved = 0; + NSMutableData* plaintext = [NSMutableData dataWithLength:ivct.length]; + CCCryptorStatus cryptstatus = CCCrypt( + kCCDecrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES, // CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ + kCCOptionPKCS7Padding, // CCOptions options, /* kCCOptionPKCS7Padding, etc. */ + kE.bytes, // const void *key, + kE.length, // size_t keyLength, + ivct.bytes, // const void *iv, /* optional initialization vector */ + ivct.bytes + 16, // const void *dataIn, /* optional per op and alg */ + ivct.length - 16, // size_t dataInLength, + plaintext.mutableBytes, // void *dataOut, /* data RETURNED here */ + plaintext.length, // size_t dataOutAvailable, + &dataOutMoved // size_t *dataOutMoved + ); + + if (cryptstatus != kCCSuccess) { + return nil; + } + + [plaintext setLength:dataOutMoved]; + return plaintext; + +// var kB = this._privateKey.bn; +// this._publicKey = PublicKey.fromDER(encbuf.slice(0, 33)); +// var R = this._publicKey.point; +// var P = R.mul(kB); +// var S = P.getX(); +// +// var Sbuf = S.toBuffer({ +// size: 32 +// }); +// var kEkM = Hash.sha512(Sbuf); +// +// var kE = kEkM.slice(0, 32); +// var kM = kEkM.slice(32, 64); +// +// var c = encbuf.slice(33, encbuf.length - 32); +// var d = encbuf.slice(encbuf.length - 32, encbuf.length); +// +// var d2 = Hash.sha256hmac(c, kM); +// if (d.toString('hex') !== d2.toString('hex')) throw new Error('Invalid checksum'); +// var messagebuf = AESCBC.decryptCipherkey(c, kE); + + +} + + +@end diff --git a/CoreBitcoin/CoreBitcoin.h b/CoreBitcoin/CoreBitcoin.h index 697c5365..c4ca45c3 100644 --- a/CoreBitcoin/CoreBitcoin.h +++ b/CoreBitcoin/CoreBitcoin.h @@ -32,3 +32,4 @@ #import #import #import +#import diff --git a/UnitTests/main.m b/UnitTests/main.m index ce6f20ea..5e787906 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -11,6 +11,7 @@ #import "BTCKeychain+Tests.h" #import "BTCCurvePoint+Tests.h" #import "BTCBlindSignature+Tests.h" +#import "BTCEncryptedMessage+Tests.h" #import "BTCFancyEncryptedMessage+Tests.h" #import "BTCScript+Tests.h" #import "BTCTransaction+Tests.h" @@ -33,6 +34,7 @@ int main(int argc, const char * argv[]) [BTCCurvePoint runAllTests]; [BTCKeychain runAllTests]; [BTCBlindSignature runAllTests]; + [BTCEncryptedMessage runAllTests]; [BTCFancyEncryptedMessage runAllTests]; [BTCScript runAllTests]; [BTCMerkleTree runAllTests]; From 657544c10f0f3fc729fbf7cd797f70311115d63b Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 15:47:01 +0200 Subject: [PATCH 016/163] tagged 0.6.6 --- CoreBitcoin.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreBitcoin.podspec b/CoreBitcoin.podspec index a21eecda..b16309d0 100644 --- a/CoreBitcoin.podspec +++ b/CoreBitcoin.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreBitcoin" - s.version = "0.6.5" + s.version = "0.6.6" s.summary = "CoreBitcoin is an implementation of Bitcoin protocol in Objective-C." s.description = <<-DESC CoreBitcoin is a complete toolkit to work with Bitcoin data structures. From 0a1df95c6cd8bb4234534c8022417c8d12748891 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 21:43:53 +0200 Subject: [PATCH 017/163] fixed unreachable code warning --- CoreBitcoin/BTCPaymentProtocol.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CoreBitcoin/BTCPaymentProtocol.m b/CoreBitcoin/BTCPaymentProtocol.m index d9c971ca..77e2b52a 100644 --- a/CoreBitcoin/BTCPaymentProtocol.m +++ b/CoreBitcoin/BTCPaymentProtocol.m @@ -422,6 +422,9 @@ - (void) validatePaymentRequest { _status = BTCPaymentRequestStatusInvalidSignature; return; } + + _status = BTCPaymentRequestStatusValid; + _isValid = YES; #else // On OS X 10.10 we don't have kSecPaddingPKCS1SHA256 and SecKeyRawVerify. // So we have to verify the signature using Security Transforms API. @@ -454,8 +457,13 @@ - (void) validatePaymentRequest { } if (result == kCFBooleanTrue) { // signature is valid + _status = BTCPaymentRequestStatusValid; + _isValid = YES; } else { // signature is invalid. + _status = BTCPaymentRequestStatusInvalidSignature; + _isValid = NO; + return NO; } */ @@ -464,9 +472,6 @@ - (void) validatePaymentRequest { return; #endif - _status = BTCPaymentRequestStatusValid; - _isValid = YES; - } else { // Either "none" PKI type or some new and unsupported PKI. From 792d633ea5b53952abfebe6ebdd621d81553248e Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 29 Mar 2015 21:45:04 +0200 Subject: [PATCH 018/163] README update --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 41d5ae69..b4ccdda7 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,11 @@ Email: [oleganza@gmail.com](mailto:oleganza@gmail.com) Twitter: [@oleganza](http://twitter.com/oleganza) +To publish on CocoaPods: + +``` +$ pod trunk push --verbose --use-libraries +``` Donate ------ From 6fe9c79c9733f372319d4f0a4c850db99236d82b Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 10:23:53 +0200 Subject: [PATCH 019/163] fixed crash in BTCBitcoinURL on incorrect amounts --- CoreBitcoin/BTCBitcoinURL.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CoreBitcoin/BTCBitcoinURL.m b/CoreBitcoin/BTCBitcoinURL.m index 25df941c..5f7d4309 100644 --- a/CoreBitcoin/BTCBitcoinURL.m +++ b/CoreBitcoin/BTCBitcoinURL.m @@ -104,6 +104,13 @@ + (BTCAmount) parseAmount:(NSString*)string { NSLocale* locale = [[NSLocale localeWithLocaleIdentifier:@"en_US"] copy]; // uses period (".") as a decimal point. NSAssert([[locale objectForKey:NSLocaleDecimalSeparator] isEqual:@"."], @"must be point as a decimal separator"); NSDecimalNumber* dn = [NSDecimalNumber decimalNumberWithString:string locale:locale]; + // Fixes crash on URL like "bitcoin:1shaYanre36PBhspFL9zG7nt6tfDhxQ4u?amount=#" (https://twitter.com/sbetamc/status/581974120440700929) + if ([dn isEqual:[NSDecimalNumber notANumber]]) { + return 0; + } + if (BTCAmountFromDecimalNumber(dn) > 21000000) { // prevent overflow when multiplying by 8. + return 0; + } dn = [dn decimalNumberByMultiplyingByPowerOf10:8]; return BTCAmountFromDecimalNumber(dn); } From 76ff93c0997f63118925e5ff85fdbe40ea2b2897 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 11:04:29 +0200 Subject: [PATCH 020/163] added release notes --- CoreBitcoin.xcodeproj/project.pbxproj | 2 + README.md | 2 + ReleaseNotes.md | 116 ++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 ReleaseNotes.md diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 78338d76..ce7fcc1e 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -542,6 +542,7 @@ 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCEncryptedMessage.m; sourceTree = ""; }; 20A443CF1AC825DA008B3447 /* BTCEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCEncryptedMessage+Tests.h"; sourceTree = ""; }; 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCEncryptedMessage+Tests.m"; sourceTree = ""; }; + 20A443D21AC941F7008B3447 /* ReleaseNotes.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ReleaseNotes.md; sourceTree = ""; }; 20B5A63E18924F350035582D /* BTCTransaction+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCTransaction+Tests.h"; sourceTree = ""; }; 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCTransaction+Tests.m"; sourceTree = ""; }; 20B66C8517BFE4F9007128CE /* BTCErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCErrors.h; sourceTree = ""; }; @@ -771,6 +772,7 @@ isa = PBXGroup; children = ( 2084DD9417B8FF7E005AC9E6 /* README.md */, + 20A443D21AC941F7008B3447 /* ReleaseNotes.md */, 204F8A801845FAF8002649CE /* TODO.txt */, 2039DB0117E2ED2B0051177B /* GLOSSARY.md */, 20203ADD19F66B6E0085C01F /* MEME_GLOSSARY.md */, diff --git a/README.md b/README.md index b4ccdda7..0dd68f9b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ See all [todo items here](https://github.com/oleganza/CoreBitcoin/issues). The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Pull requests are welcome. +[Release Notes](ReleaseNotes.md) + Starting points --------------- diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 00000000..27589a84 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,116 @@ +CoreBitcoin Release Notes +========================= + +CoreBitcoin 0.6.7 (pending) +--------------------------- + +* Fixed crash in BTCBitcoinURL parser on invalid amounts. + + +CoreBitcoin 0.6.6 +----------------- + +March 29, 2015. + +* Added support for BIP70 Payment Requests (`BTCPaymentProtocol`). Note: X.509 signatures are [not verified on OS X](https://github.com/oleganza/CoreBitcoin/issues/42) yet. +* Implemented ECIES compatible with [Bitcore-ECIES](https://github.com/bitpay/bitcore-ecies) implementation (`BTCEncryptedMessage`). +* Merged improved Xcode SDK detection to `update_openssl.sh` by Mark Pfluger (@mpfluger). +* Added SHA512 function (`BTCSHA512`). +* Added tail mutation checks to `BTCMerkleTree`. + +CoreBitcoin 0.6.5 +----------------- + +March 6, 2015. + +* Added merkle tree implementation (`BTCMerkleTree`). + +CoreBitcoin 0.6.4 +----------------- + +March 6, 2015. + +* Optimized hash functions to efficiently work with memory-mapped `NSData` instances (`BTCSHA1`, `BTCSHA256`, `BTCSHA256Concat` etc). + + +CoreBitcoin 0.6.3 +----------------- + +March 3, 2015. + +* Added Payment Request support to `BTCBitcoinURL` according to [BIP72](https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki). +* Added Payment Request support to `BTCNetwork` according to [BIP70](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki). +* Added support for `tpub...` and `tprv...` extended key encoding on testnet (`BTCKeychain`). +* Improved format conversion API of `BTCBigNumber`. + + +CoreBitcoin 0.6.2 +----------------- + +January 30, 2015. + +* Added price source API (`BTCPriceSource`) with support for Coinbase, Coindesk, Winkdex, Paymium and custom implementations. +* Added label to `BTCBitcoinURL`. +* Improved linking of inputs and outputs to their transaction instance (`BTCTransaction`). +* Added safety check to QR code scanner (`BTCQRCode`). +* Fixed rounding bug in `BTCNumberFormatter`. + + +CoreBitcoin 0.6.0 +----------------- + +December 3, 2014. + +* Improved property declarations to work better with Swift. +* Streamlined hex-related methods (`BTCHexFromData`, `BTCDataFromHex` etc) + + +CoreBitcoin 0.5.3 +----------------- + +December 2, 2014. + +* Block and block headers API (`BTCBlock`, `BTCBlockHeader`). +* Unified hash-to-ID conversion for transactions and blocks (`BTCHashFromID`, `BTCIDFromHash`). +* Added various optional properties to transaction, inputs and outputs (`BTCTransaction`, `BTCTransactionInput`, `BTCTransactionOutput`). +* Renamed type `BTCSatoshi` to `BTCAmount`. + + +CoreBitcoin 0.5.2 +----------------- + +November 21, 2014. + +* Added WIF API and testnet support to `BTCKey`. +* Swift interoperability improvements. + +CoreBitcoin 0.5.1 +----------------- + +November 18, 2014. + +* Fixed dependencies on UIKit and AppKit. + + +CoreBitcoin 0.5.0 +----------------- + +November 18, 2014. + +* First CocoaPod published. + + +CoreBitcoin 0.1.0 +----------------- + +August 11, 2013. + +* First commit. + + + + + + + + From 9b582ee0e223255b1382106e096c91f9e0bbea66 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 11:05:28 +0200 Subject: [PATCH 021/163] release notes link in Features section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0dd68f9b..0810a3f5 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Projects using CoreBitcoin Features -------- +See also [Release Notes](ReleaseNotes.md). + - Encoding/decoding addresses: P2PK, P2PKH, P2SH, WIF format ([BTCAddress](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCAddress.h)). - Transaction building blocks: inputs, outputs, scripts ([BTCTransaction](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCTransaction.h), [BTCScript](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCScript.h)). - EC keys and signatures ([BTCKey](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKey.h)). @@ -61,8 +63,6 @@ See all [todo items here](https://github.com/oleganza/CoreBitcoin/issues). The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Pull requests are welcome. -[Release Notes](ReleaseNotes.md) - Starting points --------------- From 788c5141919038bdb47a4febacbee01a9666ce67 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 11:17:39 +0200 Subject: [PATCH 022/163] notes to X.509 verification on OS X based on reply from Quinn The Eskimo --- CoreBitcoin/BTCPaymentProtocol.m | 62 ++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/CoreBitcoin/BTCPaymentProtocol.m b/CoreBitcoin/BTCPaymentProtocol.m index 77e2b52a..8c5bdfff 100644 --- a/CoreBitcoin/BTCPaymentProtocol.m +++ b/CoreBitcoin/BTCPaymentProtocol.m @@ -428,7 +428,6 @@ - (void) validatePaymentRequest { #else // On OS X 10.10 we don't have kSecPaddingPKCS1SHA256 and SecKeyRawVerify. // So we have to verify the signature using Security Transforms API. - // For now, just don't verify until someone has time to // Here's a draft of what needs to be done here. /* @@ -444,8 +443,9 @@ - (void) validatePaymentRequest { CFShow(error); exit(-1); } - // Not sure if the length is in bytes or bits. - if (!SecTransformSetAttribute(verifier, kSecDigestLengthAttribute, @(32), &error) { + // Not sure if the length is in bytes or bits. Quinn The Eskimo says it's in bits: + // https://devforums.apple.com/message/1119092#1119092 + if (!SecTransformSetAttribute(verifier, kSecDigestLengthAttribute, @(256), &error) { CFShow(error); exit(-1); } @@ -465,6 +465,62 @@ - (void) validatePaymentRequest { _isValid = NO; return NO; } + + // ----------------------------------------------------------------------- + + // From CryptoCompatibility sample code (QCCRSASHA1VerifyT.m): + + BOOL success; + SecTransformRef transform; + CFBooleanRef result; + CFErrorRef errorCF; + + result = NULL; + errorCF = NULL; + + // Set up the transform. + + transform = SecVerifyTransformCreate(self.publicKey, (__bridge CFDataRef) self.signatureData, &errorCF); + success = (transform != NULL); + + // Note: kSecInputIsAttributeName defaults to kSecInputIsPlainText, which is what we want. + + if (success) { + success = SecTransformSetAttribute(transform, kSecDigestTypeAttribute, kSecDigestSHA1, &errorCF) != false; + } + + if (success) { + success = SecTransformSetAttribute(transform, kSecTransformInputAttributeName, (__bridge CFDataRef) self.inputData, &errorCF) != false; + } + + // Run it. + + if (success) { + result = SecTransformExecute(transform, &errorCF); + success = (result != NULL); + } + + // Process the results. + + if (success) { + assert(CFGetTypeID(result) == CFBooleanGetTypeID()); + self.verified = (CFBooleanGetValue(result) != false); + } else { + assert(errorCF != NULL); + self.error = (__bridge NSError *) errorCF; + } + + // Clean up. + + if (result != NULL) { + CFRelease(result); + } + if (errorCF != NULL) { + CFRelease(errorCF); + } + if (transform != NULL) { + CFRelease(transform); + } */ _status = BTCPaymentRequestStatusUnknown; From dbc380cfd2f7ca66286a46bf1a5fdff7bd8a98e1 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 15:45:10 +0200 Subject: [PATCH 023/163] no noisy logging --- CoreBitcoin/BTCTransaction+Tests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreBitcoin/BTCTransaction+Tests.m b/CoreBitcoin/BTCTransaction+Tests.m index 0dddbeac..4d0497ca 100644 --- a/CoreBitcoin/BTCTransaction+Tests.m +++ b/CoreBitcoin/BTCTransaction+Tests.m @@ -66,7 +66,7 @@ + (void) testSerialization BTCScript* outputScript = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithBase58String:@"n3ZRkPLZZLdjWi4Wn3AazjqWBw7WWXWtVb"]]; NSData* sighash = [tx signatureHashForScript:outputScript inputIndex:0 hashType:SIGHASH_ALL error:nil]; - NSLog(@"sighash = %@ (%@)", BTCHexFromData(sighash), sighash); + //NSLog(@"sighash = %@", BTCHexFromData(sighash)); BTCTransactionInput* txin = tx.inputs.firstObject; From 34469aaa925e641c712f48973c88a901e4bd2964 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:22:30 +0200 Subject: [PATCH 024/163] warning fix --- CoreBitcoin/BTCTransaction+Tests.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CoreBitcoin/BTCTransaction+Tests.m b/CoreBitcoin/BTCTransaction+Tests.m index 4d0497ca..3215a904 100644 --- a/CoreBitcoin/BTCTransaction+Tests.m +++ b/CoreBitcoin/BTCTransaction+Tests.m @@ -63,9 +63,8 @@ + (void) testSerialization BTCTransaction* tx = [[BTCTransaction alloc] initWithData:txdata]; - BTCScript* outputScript = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithBase58String:@"n3ZRkPLZZLdjWi4Wn3AazjqWBw7WWXWtVb"]]; - NSData* sighash = [tx signatureHashForScript:outputScript inputIndex:0 hashType:SIGHASH_ALL error:nil]; - + //BTCScript* outputScript = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithBase58String:@"n3ZRkPLZZLdjWi4Wn3AazjqWBw7WWXWtVb"]]; + //NSData* sighash = [tx signatureHashForScript:outputScript inputIndex:0 hashType:SIGHASH_ALL error:nil]; //NSLog(@"sighash = %@", BTCHexFromData(sighash)); BTCTransactionInput* txin = tx.inputs.firstObject; From e7f15ab76cc115e6b873f06470adb82e475edb2a Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:22:42 +0200 Subject: [PATCH 025/163] Implemented RFC6979 deterministic nonce in BTCKey (previously used simpler, but non-standard HMAC-SHA256). --- CoreBitcoin/BTCBigNumber.h | 4 +++ CoreBitcoin/BTCBigNumber.m | 8 +++++ CoreBitcoin/BTCKey+Tests.m | 19 +++++++++++ CoreBitcoin/BTCKey.h | 13 +++++--- CoreBitcoin/BTCKey.m | 67 +++++++++++++++++++++++++++++++++++--- 5 files changed, 102 insertions(+), 9 deletions(-) diff --git a/CoreBitcoin/BTCBigNumber.h b/CoreBitcoin/BTCBigNumber.h index b0c21b18..9d86c325 100644 --- a/CoreBitcoin/BTCBigNumber.h +++ b/CoreBitcoin/BTCBigNumber.h @@ -32,6 +32,10 @@ // To modify, use [[bn mutableCopy] mutableBIGNUM] methods. @property(nonatomic, readonly) const BIGNUM* BIGNUM; +@property(nonatomic, readonly) BOOL isZero; +@property(nonatomic, readonly) BOOL isOne; + + // BTCBigNumber returns always the same object for these constants. // BTCMutableBigNumber returns a new object every time. + (instancetype) zero; // 0 diff --git a/CoreBitcoin/BTCBigNumber.m b/CoreBitcoin/BTCBigNumber.m index cda094a1..79761bfd 100644 --- a/CoreBitcoin/BTCBigNumber.m +++ b/CoreBitcoin/BTCBigNumber.m @@ -151,6 +151,14 @@ - (const BIGNUM*) BIGNUM { return &_bignum; } +- (BOOL) isZero { + return BN_is_zero(&_bignum); +} + +- (BOOL) isOne { + return BN_is_one(&_bignum); +} + #pragma mark - NSObject diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index 6ed34d49..7e640f43 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -9,6 +9,7 @@ @implementation BTCKey (Tests) + (void) runAllTests { + [self testRFC6979]; [self testDiffieHellman]; [self testCanonicality]; [self testRandomKeys]; @@ -17,6 +18,24 @@ + (void) runAllTests [self testBitcoinSignedMessage]; } ++ (void) testRFC6979 { + + void(^verifyRFC6979TestVector)(NSString* keyhex, NSString* msg, NSString* khex) = ^(NSString* keyhex, NSString* msg, NSString* khex) { + NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); + BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; + NSData* k = [key signatureNonceForHash:hash]; + NSAssert([BTCDataFromHex(khex) isEqual:k], @"Must produce matching k nonce."); + }; + + verifyRFC6979TestVector(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); + verifyRFC6979TestVector(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); + verifyRFC6979TestVector(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); + verifyRFC6979TestVector(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); + verifyRFC6979TestVector(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); + verifyRFC6979TestVector(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); + +} + + (void) testDiffieHellman { BTCKey* alice = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(@"c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")]; diff --git a/CoreBitcoin/BTCKey.h b/CoreBitcoin/BTCKey.h index 271486ac..a0a5d7a3 100644 --- a/CoreBitcoin/BTCKey.h +++ b/CoreBitcoin/BTCKey.h @@ -70,18 +70,21 @@ // Returns a signature data for a 256-bit hash using private key. // Returns nil if signing failed or a private key is not present. -- (NSData*)signatureForHash:(NSData*)hash; +- (NSData*) signatureForHash:(NSData*)hash; // Same as above, but also appends a hash type byte to the signature. -- (NSData*)signatureForHash:(NSData*)hash hashType:(BTCSignatureHashType)hashType; -- (NSData*)signatureForHash:(NSData*)hash withHashType:(BTCSignatureHashType)hashType DEPRECATED_ATTRIBUTE; +- (NSData*) signatureForHash:(NSData*)hash hashType:(BTCSignatureHashType)hashType; +- (NSData*) signatureForHash:(NSData*)hash withHashType:(BTCSignatureHashType)hashType DEPRECATED_ATTRIBUTE; + +// [RFC6979 implementation](https://tools.ietf.org/html/rfc6979). +// Returns 32-byte `k` nonce generated deterministically from the `hash` and the private key. +// Returns a mutable data to make it clearable. +- (NSMutableData*) signatureNonceForHash:(NSData*)hash; // Clears all key data from memory making receiver invalid. - (void) clear; - - // BTCAddress Import/Export diff --git a/CoreBitcoin/BTCKey.m b/CoreBitcoin/BTCKey.m index 32c0d92a..1bd2a1a2 100644 --- a/CoreBitcoin/BTCKey.m +++ b/CoreBitcoin/BTCKey.m @@ -7,6 +7,7 @@ #import "BTCBigNumber.h" #import "BTCProtocolSerialization.h" #import "BTCErrors.h" +#include #include #include #include @@ -186,16 +187,13 @@ - (NSData*)signatureForHash:(NSData*)hash appendHashType:(BOOL)appendHashType ha const BIGNUM *privkeyBIGNUM = EC_KEY_get0_private_key(_key); BTCMutableBigNumber* privkeyBN = [[BTCMutableBigNumber alloc] initWithBIGNUM:privkeyBIGNUM]; - NSMutableData* privkeyData = [self privateKey]; - BTCBigNumber* n = [BTCCurvePoint curveOrder]; - NSMutableData* kdata = BTCHMACSHA256(privkeyData, hash); + NSMutableData* kdata = [self signatureNonceForHash:hash]; BTCMutableBigNumber* k = [[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:kdata]; [k mod:n]; // make sure k belongs to [0, n - 1] BTCDataClear(kdata); - BTCDataClear(privkeyData); BTCCurvePoint* K = [[BTCCurvePoint generator] multiply:k]; BTCBigNumber* Kx = K.x; @@ -283,6 +281,67 @@ - (NSData*)signatureForHash:(NSData*)hash appendHashType:(BOOL)appendHashType ha // return signature; } +// [RFC6979 implementation](https://tools.ietf.org/html/rfc6979#section-3.2). +// Returns 32-byte `k` nonce generated deterministically from the `hash` and the private key. +- (NSMutableData*) signatureNonceForHash:(NSData*)hash { + + NSMutableData* privkey = [self privateKey]; + BTCBigNumber* order = [BTCCurvePoint curveOrder]; + + uint8_t v[32]; + uint8_t k[32]; + uint8_t bx[2*32]; + uint8_t buf[32 + 1 + sizeof(bx)]; + uint8_t t[32]; + + // Step 3.2.a. hash = H(message). Already performed by the caller. + + // Step 3.2.b. V = 0x01 0x01 0x01 ... 0x01 (32 bytes equal 0x01) + memset(v, 1, sizeof(v)); + + // Step 3.2.c. K = 0x00 0x00 0x00 ... 0x00 (32 bytes equal 0x00) + memset(k, 0, sizeof(k)); + + // Step 3.2.d. K = HMAC-SHA256(key: K, data: V || 0x00 || int2octets(privkey) || bits2octets(hash)) + memcpy(bx, privkey.bytes, 32); + BTCMutableBigNumber* hashModOrder = [[[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:hash] mod:order]; + memcpy(bx + 32, hashModOrder.unsignedBigEndian.bytes, 32); + + memcpy(buf, v, sizeof(v)); + buf[sizeof(v)] = 0x00; + memcpy(buf + sizeof(v) + 1, bx, 64); + + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), buf, sizeof(buf), k); + + // Step 3.2.e. V = HMAC-SHA256(key: K, data: V) + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), v, sizeof(v), v); + + // Step 3.2.f. K = HMAC-SHA256(key: K, data: V || 0x01 || int2octets(privkey) || bits2octets(hash)) + memcpy(buf, v, sizeof(v)); + buf[sizeof(v)] = 0x01; + memcpy(buf + sizeof(v) + 1, bx, 64); + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), buf, sizeof(buf), k); + + // Step 3.2.g. V = HMAC-SHA256(key: K, data: V) + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), v, sizeof(v), v); + + // Step 3.2.h. + for (int i = 0; i < 10000; i++) { + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), v, sizeof(v), t); + + BTCBigNumber* bn = [[BTCBigNumber alloc] initWithUnsignedBigEndian:[NSData dataWithBytesNoCopy:t length:sizeof(t) freeWhenDone:NO]]; + if (!bn.isZero && [bn less:order]) { + return [NSMutableData dataWithBytes:&t length:sizeof(t)]; + } + // Note: the probability of not succeeding at the first try is about 2^-127. + memcpy(buf, v, sizeof(v)); + buf[sizeof(v)] = 0x00; + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), buf, sizeof(v) + 1, k); + CCHmac(kCCHmacAlgSHA256, k, sizeof(k), v, sizeof(v), v); + } + // we generated 10000 numbers, none of them is good -> fail. + return nil; +} - (NSMutableData*) publicKey { From 4fe44bca12f0635ad3883f801e7abc82e6bf521b Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:24:19 +0200 Subject: [PATCH 026/163] implemented Automatic Encrypted Backup Format --- CoreBitcoin.xcodeproj/project.pbxproj | 6 + CoreBitcoin/BTCEncryptedBackup+Tests.h | 15 ++ CoreBitcoin/BTCEncryptedBackup+Tests.m | 111 ++++++++++ CoreBitcoin/BTCEncryptedBackup.h | 31 ++- CoreBitcoin/BTCEncryptedBackup.m | 285 +++++++++++++++++++++++++ UnitTests/main.m | 4 +- 6 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 CoreBitcoin/BTCEncryptedBackup+Tests.h create mode 100644 CoreBitcoin/BTCEncryptedBackup+Tests.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ce7fcc1e..ded7f6fe 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -315,6 +315,7 @@ 20A443CD1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; 20A443CE1AC82594008B3447 /* BTCEncryptedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443C61AC82594008B3447 /* BTCEncryptedMessage.m */; }; 20A443D11AC825DA008B3447 /* BTCEncryptedMessage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */; }; + 20A443D51AC954C1008B3447 /* BTCEncryptedBackup+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20A443D41AC954C1008B3447 /* BTCEncryptedBackup+Tests.m */; }; 20B5A64018924F350035582D /* BTCTransaction+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */; }; 20B66C8717BFE4F9007128CE /* BTCErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B66C8617BFE4F9007128CE /* BTCErrors.m */; }; 20B8AB92189E7E0100008138 /* BTCCurvePoint+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20B8AB91189E7E0100008138 /* BTCCurvePoint+Tests.m */; }; @@ -543,6 +544,8 @@ 20A443CF1AC825DA008B3447 /* BTCEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCEncryptedMessage+Tests.h"; sourceTree = ""; }; 20A443D01AC825DA008B3447 /* BTCEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCEncryptedMessage+Tests.m"; sourceTree = ""; }; 20A443D21AC941F7008B3447 /* ReleaseNotes.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ReleaseNotes.md; sourceTree = ""; }; + 20A443D31AC954C1008B3447 /* BTCEncryptedBackup+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCEncryptedBackup+Tests.h"; sourceTree = ""; }; + 20A443D41AC954C1008B3447 /* BTCEncryptedBackup+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCEncryptedBackup+Tests.m"; sourceTree = ""; }; 20B5A63E18924F350035582D /* BTCTransaction+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCTransaction+Tests.h"; sourceTree = ""; }; 20B5A63F18924F350035582D /* BTCTransaction+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCTransaction+Tests.m"; sourceTree = ""; }; 20B66C8517BFE4F9007128CE /* BTCErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCErrors.h; sourceTree = ""; }; @@ -943,6 +946,8 @@ 200756D91A5D6A44009A24A9 /* BTCPriceSource+Tests.m */, 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */, 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */, + 20A443D31AC954C1008B3447 /* BTCEncryptedBackup+Tests.h */, + 20A443D41AC954C1008B3447 /* BTCEncryptedBackup+Tests.m */, ); path = CoreBitcoin; sourceTree = ""; @@ -1479,6 +1484,7 @@ 2084DD8B17B8FF76005AC9E6 /* BTCKey.m in Sources */, 2084DD8C17B8FF76005AC9E6 /* BTCProtocolSerialization.m in Sources */, 207B2608188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, + 20A443D51AC954C1008B3447 /* BTCEncryptedBackup+Tests.m in Sources */, 2084DD8D17B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.m in Sources */, 2060A2831AAA077A004531FD /* BTCMerkleTree.m in Sources */, 20D008C218D1AFA800079B79 /* BTC256+Tests.m in Sources */, diff --git a/CoreBitcoin/BTCEncryptedBackup+Tests.h b/CoreBitcoin/BTCEncryptedBackup+Tests.h new file mode 100644 index 00000000..f52e9f5a --- /dev/null +++ b/CoreBitcoin/BTCEncryptedBackup+Tests.h @@ -0,0 +1,15 @@ +// +// BTCEncryptedBackup+Tests.h +// CoreBitcoin +// +// Created by Oleg Andreev on 30.03.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCEncryptedBackup.h" + +@interface BTCEncryptedBackup (Tests) + ++ (void) runAllTests; + +@end diff --git a/CoreBitcoin/BTCEncryptedBackup+Tests.m b/CoreBitcoin/BTCEncryptedBackup+Tests.m new file mode 100644 index 00000000..65fd5527 --- /dev/null +++ b/CoreBitcoin/BTCEncryptedBackup+Tests.m @@ -0,0 +1,111 @@ +// +// BTCEncryptedBackup+Tests.m +// CoreBitcoin +// +// Created by Oleg Andreev on 30.03.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCEncryptedBackup+Tests.h" +#import "BTCData.h" +#import "BTCKey.h" +#import "BTCNetwork.h" + +@implementation BTCEncryptedBackup (Tests) + ++ (void) runAllTests { + + [self testShortBackup]; + [self testLongBackup]; +} + ++ (void) testShortBackup { + + NSTimeInterval timestamp = 1427720967; + + NSData* plaintext = [@"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" dataUsingEncoding:NSUTF8StringEncoding]; + + NSData* masterKey = BTCSHA256([@"Master Key" dataUsingEncoding:NSUTF8StringEncoding]); + NSData* backupKey = [BTCEncryptedBackup backupKeyForNetwork:[BTCNetwork mainnet] masterKey:masterKey]; + + NSAssert([backupKey isEqual:BTCDataFromHex(@"7618f25cd5faadd52d0ea3b608b0c076664f5816b81311017985ae229157057a")], @"must produce a determinstic backup key"); + + BTCEncryptedBackup* backup = [BTCEncryptedBackup encrypt:plaintext backupKey:backupKey timestamp:timestamp]; + + NSAssert(backup != nil, @"Should encrypt alright"); + + NSAssert(backup.encryptedData.length >= 1 + 4 + 16 + 1 + plaintext.length + 1 + 68, @"Should be of realistic length"); + + NSAssert([backup.encryptionKey isEqual:BTCDataFromHex(@"58369379e5100b58cd49c97171f29f3d")], @"Should use deterministic encryption key"); + NSAssert([backup.iv isEqual:BTCDataFromHex(@"bf07aaa979ae8af6eebfea5da8e83cad")], @"Should compute IV deterministically"); + NSAssert([backup.authenticationKey.privateKey isEqual:BTCDataFromHex(@"44b45878c33c974179f5363fee95f9e9d4a60c97e9c865e58b57bef3558034f4")], @"Should use deterministic authentication private key"); + NSAssert([backup.authenticationKey.publicKey isEqual:BTCDataFromHex(@"028747be6de07552c48f9db23617792d47df1accd611175f6dfe636f4098984a09")], @"Should use deterministic compressed authentication public key"); + NSAssert([backup.walletID isEqual:@"WmEp7EPk8vKMgXQQGWgh1AYhmY8Usw6kwL"], @"Should compute WalletID deterministically"); + NSAssert([backup.ciphertext isEqual:BTCDataFromHex(@"5edbaade9ba4ed528a8de36c95ece996189dedf4756fba2599f94b4f370d701366e2f0ba4e59111c0787708cf4b0b82de558b4d8bf5d90b3512f09814d605d4c14f2f85b596211f83918c31c4bef19ea")], @"Should compute ciphertext deterministically"); + NSAssert([backup.merkleRoot isEqual:BTCDataFromHex(@"9e913cd60f7df551b3baa320602bfba78489921d661362a64a03550a45add008")], @"Should compute merkle root of the ciphertext correctly"); + NSAssert([backup.signature isEqual:BTCDataFromHex(@"3045022100ddbc9b06625c2b3c9cbfb27b6ac39596bd13daf43d4ddecbb7257a0d26f5e2c402200a5bd5fd27df7ac262ac3cff9d5398742c6fd9c76c427548667bee45dcb1134c")], @"Should compute signature deterministically (RFC6979)"); + NSAssert([backup.encryptedData isEqual:BTCDataFromHex(@"01074b1955bf07aaa979ae8af6eebfea5da8e83cad505edbaade9ba4ed528a8de36c95ece996189dedf4756fba2599f94b4f370d701366e2f0ba4e59111c0787708cf4b0b82de558b4d8bf5d90b3512f09814d605d4c14f2f85b596211f83918c31c4bef19ea473045022100ddbc9b06625c2b3c9cbfb27b6ac39596bd13daf43d4ddecbb7257a0d26f5e2c402200a5bd5fd27df7ac262ac3cff9d5398742c6fd9c76c427548667bee45dcb1134c")], @"Should compute the whole encrypted backup deterministically"); + + BTCEncryptedBackup* backup2 = [BTCEncryptedBackup decrypt:backup.encryptedData backupKey:backupKey]; + + NSAssert(backup2, @"Must decrypt"); + NSAssert(backup2.version == backup.version, @"Version must be decoded correctly"); + NSAssert(backup2.timestamp == backup.timestamp, @"Timestamp must be decoded correctly"); + NSAssert([backup2.decryptedData isEqual:plaintext], @"Plaintext must be decrypted correctly"); +} + ++ (void) testLongBackup { + + // Encrypt a backup + id backupJSON = @{ + @"version": @"1", + @"network": @"main", + @"accounts": @[ + @{@"type": @"bip44", @"label": @"label for bip44 account 0", @"path": @"44'/0'/0'"}, + @{@"type": @"bip44", @"label": @"label for bip44 account 1", @"path": @"44'/0'/1'"}, + @{@"type": @"bip44", @"label": @"label for bip44 account 17", @"path": @"44'/0'/17'"}, + @{@"type": @"single", @"label": @"Vanity Address", @"wif": @"5RLmtKqh..."}, + @{@"type": @"single", @"label": @"Watch-Only", @"address": @"1Ht3CBv..."}, + @{@"type": @"trezor", @"label": @"My Trezor", @"xpub": @"xpub6FHa3pjLCk8..."}, + ], + @"transactions": @{ + @"f10c7786f120536...": @{ + @"memo": @"Hotel in Lisbon", + @"recipient": @"Expedia, Inc.", + @"payment_request": @"12008c17d661778f1249...", + @"payment_ack": @"0b2678e8a476a30e2609...", + @"fiat_amount": @"-265.10", + @"fiat_code": @"EUR", + }, + }, + @"currency": @{ + @"fiat_code": @"USD", + @"fiat_source": @"Coinbase", + @"btc_unit": @"BTC", + } + }; + NSError* error = nil; + NSData* plaintext = [NSJSONSerialization dataWithJSONObject:backupJSON options:0 error:&error]; + //NSLog(@"plaintext = %@", [[NSString alloc] initWithData:plaintext encoding:NSUTF8StringEncoding]); + NSAssert(plaintext, @"Must encode to JSON"); + + NSData* masterKey = BTCSHA256([@"Master Key" dataUsingEncoding:NSUTF8StringEncoding]); + NSData* backupKey = [BTCEncryptedBackup backupKeyForNetwork:[BTCNetwork mainnet] masterKey:masterKey]; + + NSAssert([backupKey isEqual:BTCDataFromHex(@"7618f25cd5faadd52d0ea3b608b0c076664f5816b81311017985ae229157057a")], @"must produce a determinstic backup key"); + + BTCEncryptedBackup* backup = [BTCEncryptedBackup encrypt:plaintext backupKey:backupKey timestamp:1427720967.0f]; + + NSAssert(backup != nil, @"Should encrypt alright"); + + NSAssert(backup.encryptedData.length >= 1 + 4 + 16 + 1 + plaintext.length + 1 + 68, @"Should be of realistic length"); + + BTCEncryptedBackup* backup2 = [BTCEncryptedBackup decrypt:backup.encryptedData backupKey:backupKey]; + + NSAssert(backup2, @"Must decrypt"); + NSAssert(backup2.version == backup.version, @"Version must be decoded correctly"); + NSAssert(backup2.timestamp == backup.timestamp, @"Timestamp must be decoded correctly"); + NSAssert([backup2.decryptedData isEqual:plaintext], @"Plaintext must be decrypted correctly"); +} + +@end diff --git a/CoreBitcoin/BTCEncryptedBackup.h b/CoreBitcoin/BTCEncryptedBackup.h index 6b5e9187..8110d03d 100644 --- a/CoreBitcoin/BTCEncryptedBackup.h +++ b/CoreBitcoin/BTCEncryptedBackup.h @@ -2,11 +2,40 @@ #import +typedef NS_ENUM(unsigned char, BTCEncryptedBackupVersion) { + BTCEncryptedBackupVersion1 = 0x01, +}; + @class BTCNetwork; +@class BTCKey; @interface BTCEncryptedBackup : NSObject -- (id) initWithBackupKey:(NSData*)backupKey; +// Default version is BTCEncryptedBackupVersion1. +@property(nonatomic, readonly) BTCEncryptedBackupVersion version; + +// Timestamp of the backup. If not specified, during encryption set to current time. +@property(nonatomic, readonly) NSTimeInterval timestamp; +@property(nonatomic, readonly) NSDate* date; + +@property(nonatomic, readonly) NSData* decryptedData; +@property(nonatomic, readonly) NSData* encryptedData; + +@property(nonatomic, readonly) NSString* walletID; +@property(nonatomic, readonly) BTCKey* authenticationKey; + ++ (instancetype) encrypt:(NSData*)data backupKey:(NSData*)backupKey; ++ (instancetype) encrypt:(NSData*)data backupKey:(NSData*)backupKey timestamp:(NSTimeInterval)timestamp; ++ (instancetype) decrypt:(NSData*)data backupKey:(NSData*)backupKey; + (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKey; +// For testing/audit purposes only: + +@property(nonatomic, readonly) NSData* encryptionKey; +@property(nonatomic, readonly) NSData* iv; +@property(nonatomic, readonly) NSData* merkleRoot; +@property(nonatomic, readonly) NSData* ciphertext; +@property(nonatomic, readonly) NSData* dataForSigning; +@property(nonatomic, readonly) NSData* signature; + @end diff --git a/CoreBitcoin/BTCEncryptedBackup.m b/CoreBitcoin/BTCEncryptedBackup.m index 42d58f04..ea6ea67a 100644 --- a/CoreBitcoin/BTCEncryptedBackup.m +++ b/CoreBitcoin/BTCEncryptedBackup.m @@ -2,10 +2,25 @@ #import "BTCEncryptedBackup.h" #import "BTCData.h" +#import "BTCBase58.h" +#import "BTCKey.h" #import "BTCNetwork.h" +#import "BTCMerkleTree.h" +#import "BTCProtocolSerialization.h" +#import @interface BTCEncryptedBackup () +@property(nonatomic, readwrite) BTCEncryptedBackupVersion version; +@property(nonatomic, readwrite) NSTimeInterval timestamp; +@property(nonatomic, readwrite) NSDate* date; + @property(nonatomic, readwrite) NSData* backupKey; +@property(nonatomic, readwrite) NSData* decryptedData; +@property(nonatomic, readwrite) NSData* encryptedData; + +@property(nonatomic, readwrite) NSData* iv; +@property(nonatomic, readwrite) NSData* ciphertext; +@property(nonatomic, readwrite) NSData* signature; @end @implementation BTCEncryptedBackup @@ -13,11 +28,52 @@ @implementation BTCEncryptedBackup - (id) initWithBackupKey:(NSData*)backupKey { if (!backupKey) return nil; if (self = [super init]) { + self.version = BTCEncryptedBackupVersion1; + self.date = [NSDate date]; self.backupKey = backupKey; } return self; } +- (NSTimeInterval) timestamp { + return self.date.timeIntervalSince1970; +} + +- (void) setTimestamp:(NSTimeInterval)timestamp { + if (timestamp == 0.0) { + self.date = nil; + } else { + self.date = [NSDate dateWithTimeIntervalSince1970:timestamp]; + } +} + ++ (instancetype) encrypt:(NSData*)data backupKey:(NSData*)backupKey { + return [self encrypt:data backupKey:backupKey timestamp:[NSDate date].timeIntervalSince1970]; +} + ++ (instancetype) encrypt:(NSData*)data backupKey:(NSData*)backupKey timestamp:(NSTimeInterval)timestamp { + BTCEncryptedBackup* b = [[BTCEncryptedBackup alloc] initWithBackupKey:backupKey]; + b.timestamp = timestamp; + b.decryptedData = data; + NSData* r = [b encrypt]; + if (r) { + b.encryptedData = r; + return b; + } + return nil; +} + ++ (instancetype) decrypt:(NSData*)data backupKey:(NSData*)backupKey { + BTCEncryptedBackup* b = [[BTCEncryptedBackup alloc] initWithBackupKey:backupKey]; + b.encryptedData = data; + NSData* r = [b decrypt]; + if (r) { + b.decryptedData = r; + return b; + } + return nil; +} + + (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKey { if (!network || network.isMainnet) { return BTCHMACSHA256(masterKey, [@"Automatic Backup Key Mainnet" dataUsingEncoding:NSASCIIStringEncoding]); @@ -26,4 +82,233 @@ + (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKe } } +- (NSData*) encryptionKey { + return [BTCHMACSHA256(self.backupKey, [@"Encryption Key" dataUsingEncoding:NSUTF8StringEncoding]) subdataWithRange:NSMakeRange(0, 16)]; +} + +- (NSData*) iv { + if (_iv) return _iv; + return [self ivForPlaintext:self.decryptedData ek:[self encryptionKey]]; +} + +- (NSData*) ciphertext { + if (_ciphertext) return _ciphertext; + return [self ciphertextForData:self.decryptedData iv:[self iv] ek:[self encryptionKey]]; +} + +- (NSData*) merkleRoot { + return [self merkleRootForCiphertext:[self ciphertext]]; +} + +- (NSData*) dataForSigning { + return [self dataForSigning:self.version timestamp:self.timestamp iv:[self iv] merkleRoot:[self merkleRoot]]; +} + +- (BTCKey*) authenticationKey { + BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCHMACSHA256(self.backupKey, [@"Authentication Key" dataUsingEncoding:NSUTF8StringEncoding])]; + key.publicKeyCompressed = YES; + return key; +} + +- (NSString*) walletID { + // WalletID = Base58Check(0x49 || RIPEMD-160(SHA-256(APub))) + NSMutableData* data = [NSMutableData data]; + uint8_t v = 0x49; + [data appendBytes:&v length:1]; + [data appendData:BTCHash160([self authenticationKey].publicKey)]; + return BTCBase58CheckStringWithData(data); +} + +- (NSData*) signature { + if (_signature) return _signature; + return [self signatureForData:[self dataForSigning] key:[self authenticationKey]]; +} + + +- (NSData*) encrypt { + + NSData* plaintext = self.decryptedData; + + // Result = VersionByte || Timestamp || IV || CiphertextLength || Ciphertext || SignatureLength || Signature + NSMutableData* result = [NSMutableData data]; + + uint8_t v = (uint8_t)self.version; + uint32_t ts = CFSwapInt32HostToLittle((uint32_t)self.timestamp); + BTCKey* ak = [self authenticationKey]; + NSData* ek = [self encryptionKey]; + NSData* iv = [self ivForPlaintext:plaintext ek:ek]; + NSData* ciphertext = [self ciphertextForData:plaintext iv:iv ek:ek]; + NSData* mr = [self merkleRootForCiphertext:ciphertext]; + NSData* signature = [self signatureForData:[self dataForSigning:self.version timestamp:self.timestamp iv:iv merkleRoot:mr] key:ak]; + + [result appendData:[NSData dataWithBytes:&v length:1]]; + [result appendData:[NSData dataWithBytes:&ts length:4]]; + [result appendData:iv]; + [result appendData:[BTCProtocolSerialization dataForVarString:ciphertext]]; + [result appendData:[BTCProtocolSerialization dataForVarString:signature]]; + return result; +} + +- (NSData*) decrypt { + + // Result = VersionByte || Timestamp || IV || CiphertextLength || Ciphertext || SignatureLength || Signature + + NSData* payload = self.encryptedData; + if (payload.length < (1 + 4 + 16 + 2 + 60)) { + return nil; + } + self.version = ((uint8_t*)payload.bytes)[0]; + uint32_t ts; + memcpy(&ts, [payload subdataWithRange:NSMakeRange(1, 4)].bytes, 4); + self.timestamp = CFSwapInt32LittleToHost(ts); + NSData* iv = [payload subdataWithRange:NSMakeRange(1+4, 16)]; + + NSInteger fixedOffset = 1+4+16; + NSUInteger ctlen = 0; + NSData* ciphertext = [BTCProtocolSerialization readVarStringFromData:[payload subdataWithRange:NSMakeRange(fixedOffset, payload.length - fixedOffset)] readBytes:&ctlen]; + + if (!ciphertext) { + return nil; + } + + NSData* signature = [BTCProtocolSerialization readVarStringFromData: + [payload subdataWithRange:NSMakeRange(fixedOffset + ctlen, payload.length - (fixedOffset + ctlen))]]; + + if (!signature) { + return nil; + } + + self.iv = iv; + self.ciphertext = ciphertext; + self.signature = signature; + + // 1. Verify signature. + // 2. Decrypt + // 3. Verify plaintext integrity via IV-as-MAC. + + BTCKey* ak = [self authenticationKey]; + NSData* ek = [self encryptionKey]; + NSData* mr = [self merkleRootForCiphertext:ciphertext]; + NSData* sigData = [self dataForSigning:self.version timestamp:self.timestamp iv:iv merkleRoot:mr]; + if (![ak isValidSignature:signature hash:BTCHash256(sigData)]) { + return nil; + } + + NSData* plaintext = [self plaintextFromData:ciphertext iv:iv ek:ek]; + if (!plaintext) { + return nil; + } + + NSData* mac = [self ivForPlaintext:plaintext ek:ek]; + + // IV acts as a MAC on plaintext. Here we check that ciphertext wasn't tail-mutated within Merkle Tree. + if (![mac isEqual:iv]) { + return nil; + } + + return plaintext; +} + + + +// Functional Helpers + +- (NSData*) ivForPlaintext:(NSData*)plaintext ek:(NSData*)ek { + return [BTCHMACSHA256(ek, plaintext) subdataWithRange:NSMakeRange(0, 16)]; +} + +- (NSData*) ciphertextForData:(NSData*)plaintext iv:(NSData*)iv ek:(NSData*)ek { + + NSMutableData* ct = [NSMutableData dataWithLength:plaintext.length + 16]; + size_t dataOutMoved = 0; + CCCryptorStatus cryptstatus = CCCrypt( + kCCEncrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES, // CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ + kCCOptionPKCS7Padding, // CCOptions options, /* kCCOptionPKCS7Padding, etc. */ + ek.bytes, // const void *key, + ek.length, // size_t keyLength, + iv.bytes, // const void *iv, /* optional initialization vector */ + plaintext.bytes, // const void *dataIn, /* optional per op and alg */ + plaintext.length, // size_t dataInLength, + ct.mutableBytes, // void *dataOut, /* data RETURNED here */ + ct.length, // size_t dataOutAvailable, + &dataOutMoved // size_t *dataOutMoved + ); + + if (cryptstatus != kCCSuccess) { + return nil; + } + [ct setLength:dataOutMoved]; + return ct; +} + +- (NSData*) plaintextFromData:(NSData*)ciphertext iv:(NSData*)iv ek:(NSData*)ek { + + NSMutableData* pt = [NSMutableData dataWithLength:ciphertext.length]; + size_t dataOutMoved = 0; + CCCryptorStatus cryptstatus = CCCrypt( + kCCDecrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES, // CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ + kCCOptionPKCS7Padding, // CCOptions options, /* kCCOptionPKCS7Padding, etc. */ + ek.bytes, // const void *key, + ek.length, // size_t keyLength, + iv.bytes, // const void *iv, /* optional initialization vector */ + ciphertext.bytes, // const void *dataIn, /* optional per op and alg */ + ciphertext.length, // size_t dataInLength, + pt.mutableBytes, // void *dataOut, /* data RETURNED here */ + pt.length, // size_t dataOutAvailable, + &dataOutMoved // size_t *dataOutMoved + ); + + if (cryptstatus != kCCSuccess) { + return nil; + } + [pt setLength:dataOutMoved]; + return pt; +} + + +- (NSData*) merkleRootForCiphertext:(NSData*)data { + // Ciphertext = a (1024) || b (1024) || c (1024) || d (1024) || e (904 bytes) + /* + Merkle Root + / \ + p q + / \ / \ + f g h h + / \ / \ / \ + a b c d e e + */ + NSMutableArray* dataItems = [NSMutableArray arrayWithCapacity:((data.length + 1023) / 1024)]; + for (int i = 0; i < data.length; i += 1024) { + NSData* item = [data subdataWithRange:NSMakeRange(i, MIN(1024, data.length - i))]; + [dataItems addObject:item]; + } + BTCMerkleTree* mt = [[BTCMerkleTree alloc] initWithDataItems:dataItems]; + return mt.merkleRoot; +} + +- (NSData*) dataForSigning:(BTCEncryptedBackupVersion)version timestamp:(NSTimeInterval)timestamp iv:(NSData*)iv merkleRoot:(NSData*)merkleRoot { + // VersionByte || Timestamp || IV || MerkleRoot + + NSMutableData* result = [NSMutableData data]; + + uint8_t v = (uint8_t)version; + uint32_t ts = (uint32_t)timestamp; + + [result appendData:[NSData dataWithBytes:&v length:1]]; + [result appendData:[NSData dataWithBytes:&ts length:4]]; + [result appendData:iv]; + [result appendData:merkleRoot]; + return result; +} + +- (NSData*) signatureForData:(NSData*)data key:(BTCKey*)key { + + // Signature = ECDSA(private key: AK, hash: SHA-256(SHA-256(VersionByte || Timestamp || IV || MerkleRoot)))) + NSData* hash = BTCHash256(data); + return [key signatureForHash:hash]; +} + + @end diff --git a/UnitTests/main.m b/UnitTests/main.m index 5e787906..48f7dc48 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -11,6 +11,7 @@ #import "BTCKeychain+Tests.h" #import "BTCCurvePoint+Tests.h" #import "BTCBlindSignature+Tests.h" +#import "BTCEncryptedBackup+Tests.h" #import "BTCEncryptedMessage+Tests.h" #import "BTCFancyEncryptedMessage+Tests.h" #import "BTCScript+Tests.h" @@ -34,13 +35,14 @@ int main(int argc, const char * argv[]) [BTCCurvePoint runAllTests]; [BTCKeychain runAllTests]; [BTCBlindSignature runAllTests]; + [BTCEncryptedBackup runAllTests]; [BTCEncryptedMessage runAllTests]; [BTCFancyEncryptedMessage runAllTests]; [BTCScript runAllTests]; [BTCMerkleTree runAllTests]; [BTCBlockchainInfo runAllTests]; [BTCPriceSource runAllTests]; - [BTCTransaction runAllTests]; + [BTCTransaction runAllTests]; // has some interactive features to ask for private key NSLog(@"All tests passed."); } return 0; From 4acf4b28a28001399e8aa0f0de55541e0993a81f Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:31:45 +0200 Subject: [PATCH 027/163] various links and references to new features --- CoreBitcoin/BTCEncryptedBackup.h | 3 +++ README.md | 2 ++ ReleaseNotes.md | 9 ++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CoreBitcoin/BTCEncryptedBackup.h b/CoreBitcoin/BTCEncryptedBackup.h index 8110d03d..eafab6f1 100644 --- a/CoreBitcoin/BTCEncryptedBackup.h +++ b/CoreBitcoin/BTCEncryptedBackup.h @@ -1,5 +1,8 @@ // CoreBitcoin by Oleg Andreev , WTFPL. +// Implementation of [Automatic Encrypted Wallet Backups](https://github.com/oleganza/bitcoin-papers/blob/master/AutomaticEncryptedWalletBackups.md) scheme. +// For test vectors, see unit tests (BTCEncryptedBackup+Tests.m). + #import typedef NS_ENUM(unsigned char, BTCEncryptedBackupVersion) { diff --git a/README.md b/README.md index 0810a3f5..af2d140a 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ See also [Release Notes](ReleaseNotes.md). - BIP32, BIP44 hierarchical deterministic wallets ([BTCKeychain](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKeychain.h)). - BIP39 implementation ([BTCMnemonic](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCMnemonic.h)). - BIP70 implementation ([BTCPaymentProtocol](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCPaymentProtocol.h)). +- [Automatic Encrypted Wallet Backup](https://github.com/oleganza/bitcoin-papers/blob/master/AutomaticEncryptedWalletBackups.md) scheme ([BTCPaymentProtocol](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCEncryptedBackup.h)). Currency Tools -------------- @@ -43,6 +44,7 @@ Currency Tools Advanced Features ----------------- +- Deterministic [RFC6979](https://tools.ietf.org/html/rfc6979#section-3.2)-compliant ECDSA signatures. - Script evaluation machine to actually validate individual transactions ([BTCScriptMachine](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCScriptMachine.h)). - Blind signatures implementation ([BTCBlindSignature](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBlindSignature.h)). - Math on elliptic curves: big numbers, curve points, conversion between keys, numbers and points ([BTCBigNumber](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCBigNumber.h), [BTCCurvePoint](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCCurvePoint.h)). diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 27589a84..1fa19cdf 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,11 +1,14 @@ CoreBitcoin Release Notes ========================= -CoreBitcoin 0.6.7 (pending) ---------------------------- +CoreBitcoin 0.6.7 +----------------- -* Fixed crash in BTCBitcoinURL parser on invalid amounts. +March 30, 2015. +* Implemented RFC6979 deterministic signatures (`BTCKey`). Previously signatures were also deterministic, but non-standard. +* Implemented [Automatic Encrypted Wallet Backup scheme](https://github.com/oleganza/bitcoin-papers/blob/master/AutomaticEncryptedWalletBackups.md) (`BTCEncryptedBackup`). +* Fixed crash in BTCBitcoinURL parser on invalid amounts. CoreBitcoin 0.6.6 ----------------- From 7bddd1f5fa178bb48a46212feaa97324b76178e3 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:32:01 +0200 Subject: [PATCH 028/163] 0.6.7 --- CoreBitcoin.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreBitcoin.podspec b/CoreBitcoin.podspec index b16309d0..63b912f0 100644 --- a/CoreBitcoin.podspec +++ b/CoreBitcoin.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreBitcoin" - s.version = "0.6.6" + s.version = "0.6.7" s.summary = "CoreBitcoin is an implementation of Bitcoin protocol in Objective-C." s.description = <<-DESC CoreBitcoin is a complete toolkit to work with Bitcoin data structures. From 1016309054bf3271816ab46b0eaac04e3902affb Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 17:35:05 +0200 Subject: [PATCH 029/163] typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af2d140a..b159ec96 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ See also [Release Notes](ReleaseNotes.md). - BIP32, BIP44 hierarchical deterministic wallets ([BTCKeychain](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCKeychain.h)). - BIP39 implementation ([BTCMnemonic](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCMnemonic.h)). - BIP70 implementation ([BTCPaymentProtocol](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCPaymentProtocol.h)). -- [Automatic Encrypted Wallet Backup](https://github.com/oleganza/bitcoin-papers/blob/master/AutomaticEncryptedWalletBackups.md) scheme ([BTCPaymentProtocol](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCEncryptedBackup.h)). +- [Automatic Encrypted Wallet Backup](https://github.com/oleganza/bitcoin-papers/blob/master/AutomaticEncryptedWalletBackups.md) scheme ([BTCEncryptedBackup](https://github.com/oleganza/CoreBitcoin/blob/master/CoreBitcoin/BTCEncryptedBackup.h)). Currency Tools -------------- From 09eff31fad0597c0d4022be76ecf2b497f4ac161 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 30 Mar 2015 18:08:37 +0200 Subject: [PATCH 030/163] cleaned up obsolete code --- CoreBitcoin/BTCEncryptedBackup+Tests.m | 3 + CoreBitcoin/BTCKey.m | 109 ++++++++++--------------- 2 files changed, 48 insertions(+), 64 deletions(-) diff --git a/CoreBitcoin/BTCEncryptedBackup+Tests.m b/CoreBitcoin/BTCEncryptedBackup+Tests.m index 65fd5527..981ee099 100644 --- a/CoreBitcoin/BTCEncryptedBackup+Tests.m +++ b/CoreBitcoin/BTCEncryptedBackup+Tests.m @@ -26,9 +26,12 @@ + (void) testShortBackup { NSData* plaintext = [@"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" dataUsingEncoding:NSUTF8StringEncoding]; NSData* masterKey = BTCSHA256([@"Master Key" dataUsingEncoding:NSUTF8StringEncoding]); + NSAssert([masterKey isEqual:BTCDataFromHex(@"08c17482950a872178b8030c8f8a63bc6e5f9f680dd25739e1ec7e0b544f40f9")], @"master key"); NSData* backupKey = [BTCEncryptedBackup backupKeyForNetwork:[BTCNetwork mainnet] masterKey:masterKey]; + NSData* backupKeyTestnet = [BTCEncryptedBackup backupKeyForNetwork:[BTCNetwork testnet] masterKey:masterKey]; NSAssert([backupKey isEqual:BTCDataFromHex(@"7618f25cd5faadd52d0ea3b608b0c076664f5816b81311017985ae229157057a")], @"must produce a determinstic backup key"); + NSAssert([backupKeyTestnet isEqual:BTCDataFromHex(@"caa57de4c3d9c77186175fbfdc326997162da0ce1b74022a51c600838449b2c3")], @"must produce a determinstic backup key"); BTCEncryptedBackup* backup = [BTCEncryptedBackup encrypt:plaintext backupKey:backupKey timestamp:timestamp]; diff --git a/CoreBitcoin/BTCKey.m b/CoreBitcoin/BTCKey.m index 1bd2a1a2..ddac3484 100644 --- a/CoreBitcoin/BTCKey.m +++ b/CoreBitcoin/BTCKey.m @@ -176,94 +176,75 @@ - (NSData*)signatureForHash:(NSData*)hash appendHashType:(BOOL)appendHashType ha // private key from a few observed signatures. Using the same function to derive k therefore // does not make the signature any less secure. // - + ECDSA_SIG sigValue; ECDSA_SIG *sig = NULL; - - if (1 /* deterministic signature with nonce derived from message and private key */) - { - sig = &sigValue; - - const BIGNUM *privkeyBIGNUM = EC_KEY_get0_private_key(_key); - - BTCMutableBigNumber* privkeyBN = [[BTCMutableBigNumber alloc] initWithBIGNUM:privkeyBIGNUM]; - BTCBigNumber* n = [BTCCurvePoint curveOrder]; - NSMutableData* kdata = [self signatureNonceForHash:hash]; - BTCMutableBigNumber* k = [[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:kdata]; - [k mod:n]; // make sure k belongs to [0, n - 1] - - BTCDataClear(kdata); - - BTCCurvePoint* K = [[BTCCurvePoint generator] multiply:k]; - BTCBigNumber* Kx = K.x; - - BTCBigNumber* hashBN = [[BTCBigNumber alloc] initWithUnsignedBigEndian:hash]; - - // Compute s = (k^-1)*(h + Kx*privkey) - - BTCBigNumber* signatureBN = [[[privkeyBN multiply:Kx mod:n] add:hashBN mod:n] multiply:[k inverseMod:n] mod:n]; - - //NSLog(@"ECDSA: r = %@", Kx.hexString); - //NSLog(@"ECDSA: s = %@", signatureBN.hexString); - BIGNUM r; BN_init(&r); BN_copy(&r, Kx.BIGNUM); - BIGNUM s; BN_init(&s); BN_copy(&s, signatureBN.BIGNUM); - - [privkeyBN clear]; - [k clear]; - [hashBN clear]; - [K clear]; - [Kx clear]; - [signatureBN clear]; - - sig->r = &r; - sig->s = &s; - } - else - { -// Non-deterministic ECDSA signature using OpenSSL's RNG. -// sig = ECDSA_do_sign((unsigned char*)hash.bytes, (int)hash.length, _key); -// if (sig == NULL) -// { -// return nil; -// } - } + /* deterministic signature with nonce derived from message and private key */ + sig = &sigValue; + const BIGNUM *privkeyBIGNUM = EC_KEY_get0_private_key(_key); + + BTCMutableBigNumber* privkeyBN = [[BTCMutableBigNumber alloc] initWithBIGNUM:privkeyBIGNUM]; + BTCBigNumber* n = [BTCCurvePoint curveOrder]; + + NSMutableData* kdata = [self signatureNonceForHash:hash]; + BTCMutableBigNumber* k = [[BTCMutableBigNumber alloc] initWithUnsignedBigEndian:kdata]; + [k mod:n]; // make sure k belongs to [0, n - 1] + + BTCDataClear(kdata); + + BTCCurvePoint* K = [[BTCCurvePoint generator] multiply:k]; + BTCBigNumber* Kx = K.x; + + BTCBigNumber* hashBN = [[BTCBigNumber alloc] initWithUnsignedBigEndian:hash]; + + // Compute s = (k^-1)*(h + Kx*privkey) + + BTCBigNumber* signatureBN = [[[privkeyBN multiply:Kx mod:n] add:hashBN mod:n] multiply:[k inverseMod:n] mod:n]; + + //NSLog(@"ECDSA: r = %@", Kx.hexString); + //NSLog(@"ECDSA: s = %@", signatureBN.hexString); + BIGNUM r; BN_init(&r); BN_copy(&r, Kx.BIGNUM); + BIGNUM s; BN_init(&s); BN_copy(&s, signatureBN.BIGNUM); + + [privkeyBN clear]; + [k clear]; + [hashBN clear]; + [K clear]; + [Kx clear]; + [signatureBN clear]; + + sig->r = &r; + sig->s = &s; + BN_CTX *ctx = BN_CTX_new(); BN_CTX_start(ctx); - + const EC_GROUP *group = EC_KEY_get0_group(_key); BIGNUM *order = BN_CTX_get(ctx); BIGNUM *halforder = BN_CTX_get(ctx); EC_GROUP_get_order(group, order, ctx); BN_rshift1(halforder, order); - if (BN_cmp(sig->s, halforder) > 0) - { + if (BN_cmp(sig->s, halforder) > 0) { // enforce low S values, by negating the value (modulo the order) if above order/2. BN_sub(sig->s, order, sig->s); } BN_CTX_end(ctx); BN_CTX_free(ctx); unsigned int sigSize = ECDSA_size(_key); - + NSMutableData* signature = [NSMutableData dataWithLength:sigSize + 16]; // Make sure it is big enough - + unsigned char *pos = (unsigned char *)signature.mutableBytes; sigSize = i2d_ECDSA_SIG(sig, &pos); - -// if (!deterministic) -// { -// ECDSA_SIG_free(sig); // sig was dynamically allocated by ECDSA_do_sign -// } - + [signature setLength:sigSize]; // Shrink to fit actual size - - if (appendHashType) - { + + if (appendHashType) { [signature appendBytes:&hashType length:sizeof(hashType)]; } - return signature; // This code is simpler but it produces random signatures and does not canonicalize S as done above. From e93dd71207861b5bf044415db5fa72405e7d8fbc Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 31 Mar 2015 11:29:44 +0200 Subject: [PATCH 031/163] added test vectors for complete ECDSA signature --- CoreBitcoin/BTCKey+Tests.m | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index 7e640f43..454d6c38 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -20,20 +20,33 @@ + (void) runAllTests + (void) testRFC6979 { - void(^verifyRFC6979TestVector)(NSString* keyhex, NSString* msg, NSString* khex) = ^(NSString* keyhex, NSString* msg, NSString* khex) { + void(^verifyRFC6979Nonce)(NSString* keyhex, NSString* msg, NSString* khex) = ^(NSString* keyhex, NSString* msg, NSString* khex) { NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; NSData* k = [key signatureNonceForHash:hash]; NSAssert([BTCDataFromHex(khex) isEqual:k], @"Must produce matching k nonce."); }; - verifyRFC6979TestVector(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); - verifyRFC6979TestVector(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); - verifyRFC6979TestVector(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); - verifyRFC6979TestVector(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); - verifyRFC6979TestVector(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); - verifyRFC6979TestVector(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); + void(^verifyRFC6979Signature)(NSString* keyhex, NSString* msg, NSString* sighex) = ^(NSString* keyhex, NSString* msg, NSString* sighex) { + NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); + BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; + NSData* sig = [key signatureForHash:hash]; + NSAssert([BTCDataFromHex(sighex) isEqual:sig], @"Must produce matching signature."); + }; + verifyRFC6979Nonce(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); + verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); + verifyRFC6979Nonce(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); + verifyRFC6979Nonce(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); + verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); + verifyRFC6979Nonce(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); + + verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); + verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); + verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); + verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); + verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); + verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); } + (void) testDiffieHellman { From 63b53f114892256eda51704136994b0ce5b236fb Mon Sep 17 00:00:00 2001 From: Andrew Ogden Date: Wed, 25 Mar 2015 17:26:30 -0700 Subject: [PATCH 032/163] Support for additional query parameters for BTCBitcoinURL BTCBitcoinURL query parameters dictionary handles all parameters. Convenience properties for standard properties defined in BIP 21. BTCBitcoinURL queryParameters as immutable dictionary. Adding subscript support to BTCBitcoinURL Code cleanup for pull request https://github.com/oleganza/CoreBitcoin/pull/29 Lazy assignment of standard query parameter properties in getters --- CoreBitcoin/BTCBitcoinURL.h | 6 +- CoreBitcoin/BTCBitcoinURL.m | 141 ++++++++++++++++++++++++++++-------- 2 files changed, 114 insertions(+), 33 deletions(-) diff --git a/CoreBitcoin/BTCBitcoinURL.h b/CoreBitcoin/BTCBitcoinURL.h index edddc41e..e71ec89f 100644 --- a/CoreBitcoin/BTCBitcoinURL.h +++ b/CoreBitcoin/BTCBitcoinURL.h @@ -1,7 +1,6 @@ #import #import "BTCUnitsAndLimits.h" -// TODO: support arbitrary keys and values. // TODO: support handling URL from UIApplicationDelegate. /*! @@ -31,6 +30,11 @@ */ @property(nonatomic) NSString* message; +/*! + * Query parameters. Default is nil. + */ +@property(nonatomic) NSDictionary* queryParameters; + /*! * Payment request URL (r=...). Default is nil. */ diff --git a/CoreBitcoin/BTCBitcoinURL.m b/CoreBitcoin/BTCBitcoinURL.m index 5f7d4309..dfb07de7 100644 --- a/CoreBitcoin/BTCBitcoinURL.m +++ b/CoreBitcoin/BTCBitcoinURL.m @@ -2,8 +2,18 @@ #import "BTCAddress.h" #import "BTCNumberFormatter.h" +@interface BTCBitcoinURL () +@property NSMutableDictionary* mutableQueryParameters; +@end + @implementation BTCBitcoinURL +@synthesize amount = _amount; +@synthesize label = _label; +@synthesize message = _message; +@synthesize paymentRequestURL = _paymentRequestURL; +@synthesize queryParameters = _queryParameters; + + (NSURL*) URLWithAddress:(BTCAddress*)address amount:(BTCAmount)amount label:(NSString*)label { BTCBitcoinURL* btcurl = [[self alloc] init]; btcurl.address = address; @@ -14,6 +24,7 @@ + (NSURL*) URLWithAddress:(BTCAddress*)address amount:(BTCAmount)amount label:(N - (id) init { if (self = [super init]) { + self.mutableQueryParameters = [NSMutableDictionary dictionary]; } return self; } @@ -36,21 +47,10 @@ - (id) initWithURL:(NSURL*)url { } } - if (self = [super init]) { + if (self = [self init]) { self.address = address; for (NSURLQueryItem* item in comps.queryItems) { - if ([item.name isEqual:@"amount"]) { - self.amount = [[self class] parseAmount:item.value]; - } - if ([item.name isEqual:@"label"]) { - self.label = item.value; - } - if ([item.name isEqual:@"message"]) { - self.message = item.value; - } - if ([item.name isEqual:@"r"]) { - self.paymentRequestURL = [NSURL URLWithString:item.value]; - } + [self.mutableQueryParameters setObject:item.value forKey:item.name]; } } return self; @@ -65,37 +65,114 @@ - (NSURL*) URL NSMutableString* string = [NSMutableString stringWithFormat:@"bitcoin:%@", self.address ? self.address.string : @""]; NSMutableArray* queryItems = [NSMutableArray array]; - if (self.amount > 0) { - [queryItems addObject:[NSString stringWithFormat:@"amount=%@", [BTCBitcoinURL formatAmount:self.amount]]]; + if(self.queryParameters) { + NSArray* keys = self.queryParameters.allKeys; + for (NSString* key in keys) { + NSString* encodedKey = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)key, NULL, CFSTR("&="), + kCFStringEncodingUTF8)); + NSString* encodedValue = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)[self.queryParameters objectForKey:key], NULL, CFSTR("&="), + kCFStringEncodingUTF8)); + [queryItems addObject:[NSString stringWithFormat:@"%@=%@",encodedKey,encodedValue]]; + + } + } + + if (queryItems.count > 0) { + [string appendString:@"?"]; + [string appendString:[queryItems componentsJoinedByString:@"&"]]; + } + + return [NSURL URLWithString:string]; +} + +- (id)objectForKeyedSubscript:(id )key { + return self.mutableQueryParameters[key]; +} + +- (void)setObject:(id)obj forKeyedSubscript:(id )key { + self.mutableQueryParameters[key] = obj; +} + +- (NSDictionary*) queryParameters { + return self.mutableQueryParameters; +} + +- (void) setQueryParameters:(NSDictionary *)queryParameters { + self.mutableQueryParameters = [NSMutableDictionary dictionaryWithDictionary:queryParameters]; + //Reset cached standard query parameters + _amount = 0; + _paymentRequestURL = nil; + _message = nil; + _label = nil; +} + +#pragma mark Standard query parameters + +- (BTCAmount) amount { + if(_amount == 0){ + NSString* amountString = self.mutableQueryParameters[@"amount"]; + if (amountString) _amount = [BTCBitcoinURL parseAmount:amountString]; + } + return _amount; +} + +- (void) setAmount:(BTCAmount)amount { + _amount = amount; + NSString* amountString = [BTCBitcoinURL formatAmount:amount]; + self.mutableQueryParameters[@"amount"] = amountString; +} + +- (NSURL*) paymentRequestURL { + if (!_paymentRequestURL) { + NSString* r = self.mutableQueryParameters[@"r"]; + if (r) _paymentRequestURL = [NSURL URLWithString:r]; } + return _paymentRequestURL; +} - if (self.label.length > 0) { - [queryItems addObject:[NSString stringWithFormat:@"label=%@", - CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self.label, NULL, CFSTR("&="), - kCFStringEncodingUTF8))]]; +- (void) setPaymentRequestURL:(NSURL *)paymentRequestURL { + _paymentRequestURL = paymentRequestURL; + if(paymentRequestURL != nil) { + self.mutableQueryParameters[@"r"] = paymentRequestURL.absoluteString; + } else { + [self.mutableQueryParameters removeObjectForKey:@"r"]; } +} - if (self.message.length > 0) { - [queryItems addObject:[NSString stringWithFormat:@"message=%@", - CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self.message, NULL, CFSTR("&="), - kCFStringEncodingUTF8))]]; +- (NSString*) label { + if(!_label) { + _label = self.mutableQueryParameters[@"label"]; } + return _label; +} - if (self.paymentRequestURL) { - NSString* r = self.paymentRequestURL.absoluteString; - [queryItems addObject:[NSString stringWithFormat:@"r=%@", - CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)r, NULL, CFSTR("&="), - kCFStringEncodingUTF8))]]; +- (void) setLabel:(NSString *)label { + _label = label; + if(label != nil) { + self.mutableQueryParameters[@"label"] = label; + } else { + [self.mutableQueryParameters removeObjectForKey:@"label"]; } +} - if (queryItems.count > 0) { - [string appendString:@"?"]; - [string appendString:[queryItems componentsJoinedByString:@"&"]]; +- (NSString*) message { + if(!_message) { + _message = self.mutableQueryParameters[@"message"]; } + return _message; +} - return [NSURL URLWithString:string]; +- (void) setMessage:(NSString *)message { + _message = message; + if(message != nil) { + self.mutableQueryParameters[@"message"] = message; + } else { + [self.mutableQueryParameters removeObjectForKey:@"message"]; + } } +#pragma mark + + (NSString*) formatAmount:(BTCAmount)amount { return [NSString stringWithFormat:@"%d.%08d", (int)(amount / BTCCoin), (int)(amount % BTCCoin)]; } From b73478df45b3b93743dec70ca96b9805e8700130 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 1 Apr 2015 21:53:46 +0200 Subject: [PATCH 033/163] added cross-test for RFC6979 example from btcd/btcec --- CoreBitcoin/BTCKey+Tests.m | 59 +++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index 454d6c38..d5de292a 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -20,33 +20,52 @@ + (void) runAllTests + (void) testRFC6979 { - void(^verifyRFC6979Nonce)(NSString* keyhex, NSString* msg, NSString* khex) = ^(NSString* keyhex, NSString* msg, NSString* khex) { - NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); + void(^verifyRFC6979)(NSString*, id, NSString*, NSString*) = ^(NSString* keyhex, id msg, NSString* khex, NSString* sighex) { + NSData* hash = [msg isKindOfClass:[NSString class]] ? BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]) : msg; BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; NSData* k = [key signatureNonceForHash:hash]; NSAssert([BTCDataFromHex(khex) isEqual:k], @"Must produce matching k nonce."); - }; - - void(^verifyRFC6979Signature)(NSString* keyhex, NSString* msg, NSString* sighex) = ^(NSString* keyhex, NSString* msg, NSString* sighex) { - NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); - BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; NSData* sig = [key signatureForHash:hash]; NSAssert([BTCDataFromHex(sighex) isEqual:sig], @"Must produce matching signature."); }; - verifyRFC6979Nonce(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); - verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); - verifyRFC6979Nonce(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); - verifyRFC6979Nonce(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); - verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); - verifyRFC6979Nonce(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); - - verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); - verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); - verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); - verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); - verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); - verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); + verifyRFC6979(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", + @"sample", + @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3", + @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); + verifyRFC6979(@"0000000000000000000000000000000000000000000000000000000000000001", + @"Satoshi Nakamoto", + @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15", + @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); + verifyRFC6979(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + @"Satoshi Nakamoto", + @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90", + @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); + verifyRFC6979(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", + @"Alan Turing", + @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1", + @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); + verifyRFC6979(@"0000000000000000000000000000000000000000000000000000000000000001", + @"All those moments will be lost in time, like tears in rain. Time to die...", + @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3", + @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); + verifyRFC6979(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", + @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", + @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d", + @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); + + // Test from [btcd/btcec example](https://github.com/btcsuite/btcd/blob/master/btcec/example_test.go) + verifyRFC6979(@"22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c", + BTCHash256([@"test message" dataUsingEncoding:NSUTF8StringEncoding]), + @"c5186174691d589ad5fec3d34deac8a1a2b4156fd87a27ea8961dffe5d056ae9", + @"304402201008e236fa8cd0f25df4482dddbb622e8a8b26ef0ba731719458de3ccd93805b022032f8ebe514ba5f672466eba334639282616bb3c2f0ab09998037513d1f9e3d6d"); + +// verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); +// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); +// verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); +// verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); +// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); +// verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); } + (void) testDiffieHellman { From 471f2eb8742ecf14b3144a5f54cb06341986a150 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 1 Apr 2015 21:53:46 +0200 Subject: [PATCH 034/163] added cross-test for RFC6979 example from btcd/btcec --- CoreBitcoin/BTCKey+Tests.m | 59 +++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index 454d6c38..d5de292a 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -20,33 +20,52 @@ + (void) runAllTests + (void) testRFC6979 { - void(^verifyRFC6979Nonce)(NSString* keyhex, NSString* msg, NSString* khex) = ^(NSString* keyhex, NSString* msg, NSString* khex) { - NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); + void(^verifyRFC6979)(NSString*, id, NSString*, NSString*) = ^(NSString* keyhex, id msg, NSString* khex, NSString* sighex) { + NSData* hash = [msg isKindOfClass:[NSString class]] ? BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]) : msg; BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; NSData* k = [key signatureNonceForHash:hash]; NSAssert([BTCDataFromHex(khex) isEqual:k], @"Must produce matching k nonce."); - }; - - void(^verifyRFC6979Signature)(NSString* keyhex, NSString* msg, NSString* sighex) = ^(NSString* keyhex, NSString* msg, NSString* sighex) { - NSData* hash = BTCSHA256([msg dataUsingEncoding:NSUTF8StringEncoding]); - BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCDataFromHex(keyhex)]; NSData* sig = [key signatureForHash:hash]; NSAssert([BTCDataFromHex(sighex) isEqual:sig], @"Must produce matching signature."); }; - verifyRFC6979Nonce(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3"); - verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15"); - verifyRFC6979Nonce(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90"); - verifyRFC6979Nonce(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1"); - verifyRFC6979Nonce(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3"); - verifyRFC6979Nonce(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d"); - - verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); - verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); - verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); - verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); - verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); - verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); + verifyRFC6979(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", + @"sample", + @"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3", + @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); + verifyRFC6979(@"0000000000000000000000000000000000000000000000000000000000000001", + @"Satoshi Nakamoto", + @"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15", + @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); + verifyRFC6979(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + @"Satoshi Nakamoto", + @"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90", + @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); + verifyRFC6979(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", + @"Alan Turing", + @"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1", + @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); + verifyRFC6979(@"0000000000000000000000000000000000000000000000000000000000000001", + @"All those moments will be lost in time, like tears in rain. Time to die...", + @"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3", + @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); + verifyRFC6979(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", + @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", + @"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d", + @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); + + // Test from [btcd/btcec example](https://github.com/btcsuite/btcd/blob/master/btcec/example_test.go) + verifyRFC6979(@"22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c", + BTCHash256([@"test message" dataUsingEncoding:NSUTF8StringEncoding]), + @"c5186174691d589ad5fec3d34deac8a1a2b4156fd87a27ea8961dffe5d056ae9", + @"304402201008e236fa8cd0f25df4482dddbb622e8a8b26ef0ba731719458de3ccd93805b022032f8ebe514ba5f672466eba334639282616bb3c2f0ab09998037513d1f9e3d6d"); + +// verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); +// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); +// verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); +// verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); +// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); +// verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); } + (void) testDiffieHellman { From 2de23ef995f189e7f89c56351f65e4a17c8767ae Mon Sep 17 00:00:00 2001 From: Andrew Ogden Date: Wed, 1 Apr 2015 13:50:15 -0700 Subject: [PATCH 035/163] Decalring subscripting methods in BTCBitcoinURL header --- CoreBitcoin/BTCBitcoinURL.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CoreBitcoin/BTCBitcoinURL.h b/CoreBitcoin/BTCBitcoinURL.h index e71ec89f..42fc2cf3 100644 --- a/CoreBitcoin/BTCBitcoinURL.h +++ b/CoreBitcoin/BTCBitcoinURL.h @@ -70,4 +70,17 @@ */ - (id) init; +/*! + * Object subscripting to access query parameters more conveniently + * @param key The key in the queryParameters + */ +- (id)objectForKeyedSubscript:(id )key; + +/*! + * Object subscripting to set query parameter key value pairs more conveniently + * @param obj The value to be set for key in queryParameters + * @param key The key for value in the queryParameters + */ +- (void)setObject:(id)obj forKeyedSubscript:(id )key; + @end From 4e6f8ea59092f582c1df3768bc2e8426416c3b13 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Thu, 2 Apr 2015 10:26:55 +0200 Subject: [PATCH 036/163] added tests for bitcoin URL --- CoreBitcoin.xcodeproj/project.pbxproj | 6 ++ CoreBitcoin/BTCBitcoinURL+Tests.h | 15 +++++ CoreBitcoin/BTCBitcoinURL+Tests.m | 87 +++++++++++++++++++++++++++ UnitTests/main.m | 3 + 4 files changed, 111 insertions(+) create mode 100644 CoreBitcoin/BTCBitcoinURL+Tests.h create mode 100644 CoreBitcoin/BTCBitcoinURL+Tests.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ded7f6fe..ea6113d5 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -274,6 +274,7 @@ 208E303B1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; 208E303C1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; 208E303D1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */; }; + 2091948D1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2091948C1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m */; }; 209D1E1218D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; 209D1E1318D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; 209D1E1418D48EA200293483 /* BTCNetwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 209D1E1018D48EA200293483 /* BTCNetwork.h */; }; @@ -529,6 +530,8 @@ 208642BA194C9EAF003A3A2C /* BTCBlindSignature+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCBlindSignature+Tests.m"; sourceTree = ""; }; 208E30341AC012CE0020F830 /* BTCEncryptedBackup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCEncryptedBackup.h; sourceTree = ""; }; 208E30351AC012CE0020F830 /* BTCEncryptedBackup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCEncryptedBackup.m; sourceTree = ""; }; + 2091948B1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCBitcoinURL+Tests.h"; sourceTree = ""; }; + 2091948C1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCBitcoinURL+Tests.m"; sourceTree = ""; }; 209818911839688D00541747 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 209818921839694000541747 /* CoreBitcoin.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = CoreBitcoin.podspec; sourceTree = ""; }; 209D1E1018D48EA200293483 /* BTCNetwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCNetwork.h; sourceTree = ""; }; @@ -928,6 +931,8 @@ 209D1E1B18D4F12500293483 /* BTCProcessor.m */, 207646EE1A0A8AE4000F00F2 /* BTCBitcoinURL.h */, 207646EF1A0A8AE4000F00F2 /* BTCBitcoinURL.m */, + 2091948B1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.h */, + 2091948C1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m */, 207647021A0A8C15000F00F2 /* BTCCurrencyConverter.h */, 207647031A0A8C15000F00F2 /* BTCCurrencyConverter.m */, 207646F81A0A8BB3000F00F2 /* BTCNumberFormatter.h */, @@ -1505,6 +1510,7 @@ 20C2D80719E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */, 207646E91A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, 207646F31A0A8AE4000F00F2 /* BTCBitcoinURL.m in Sources */, + 2091948D1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m in Sources */, 206304EA17CBE72200DF3F18 /* NS+BTCBase58.m in Sources */, 208642BB194C9EAF003A3A2C /* BTCBlindSignature+Tests.m in Sources */, 208E30391AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, diff --git a/CoreBitcoin/BTCBitcoinURL+Tests.h b/CoreBitcoin/BTCBitcoinURL+Tests.h new file mode 100644 index 00000000..81901865 --- /dev/null +++ b/CoreBitcoin/BTCBitcoinURL+Tests.h @@ -0,0 +1,15 @@ +// +// BTCBitcoinURL+Tests.h +// CoreBitcoin +// +// Created by Oleg Andreev on 02.04.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCBitcoinURL.h" + +@interface BTCBitcoinURL (Tests) + ++ (void) runAllTests; + +@end diff --git a/CoreBitcoin/BTCBitcoinURL+Tests.m b/CoreBitcoin/BTCBitcoinURL+Tests.m new file mode 100644 index 00000000..e7f2db4c --- /dev/null +++ b/CoreBitcoin/BTCBitcoinURL+Tests.m @@ -0,0 +1,87 @@ +// +// BTCBitcoinURL+Tests.m +// CoreBitcoin +// +// Created by Oleg Andreev on 02.04.2015. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCBitcoinURL+Tests.h" +#import "BTCAddress.h" + +@implementation BTCBitcoinURL (Tests) + ++ (void) runAllTests { + + [self testSimpleURL]; + [self testCompatiblePaymentRequest]; + [self testNakedPaymentRequest]; + [self testInvalidURL]; + [self testMalformedURL]; +} + ++ (void) testSimpleURL { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T?amount=1.23450009&label=Hello%20world"]]; + NSAssert(burl, @"Must parse"); + NSAssert(burl.isValid == YES, @"Must be valid"); + NSAssert(burl.amount == 123450009, @"Must parse amount formatted as btc"); + NSAssert([burl.address.string isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"Must parse address"); + NSAssert(burl.paymentRequestURL == nil, @"Must parse payment request"); + NSAssert([burl.label isEqualToString:@"Hello world"], @"Must parse label"); + NSAssert([burl.queryParameters[@"label"] isEqualToString:@"Hello world"], @"Must provide raw query items access"); + NSAssert([burl.queryParameters[@"amount"] isEqualToString:@"1.23450009"], @"Must provide raw query items access"); + NSAssert([burl[@"label"] isEqualToString:@"Hello world"], @"Must provide raw query items access"); + NSAssert([burl[@"amount"] isEqualToString:@"1.23450009"], @"Must provide raw query items access"); +} + ++ (void) testCompatiblePaymentRequest { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T?amount=1.23450009&r=http://example.com/order-1000123"]]; + NSAssert(burl, @"Must parse"); + NSAssert(burl.isValid == YES, @"Must be valid"); + NSAssert(burl.amount == 123450009, @"Must parse amount formatted as btc"); + NSAssert([burl.address.string isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"Must parse address"); + NSAssert([burl.paymentRequestURL.absoluteString isEqual:@"http://example.com/order-1000123"], @"Must parse payment request"); +} + ++ (void) testNakedPaymentRequest { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:?r=http://example.com/order-1000123"]]; + NSAssert(burl, @"Must parse"); + NSAssert(burl.isValid == YES, @"Must be valid"); + NSAssert(burl.amount == 0, @"Default amount is zero"); + NSAssert(burl.address == nil, @"Default address is nil"); + NSAssert([burl.paymentRequestURL.absoluteString isEqual:@"http://example.com/order-1000123"], @"Must parse payment request"); +} + ++ (void) testInvalidURL { + { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:?x=something"]]; + NSAssert(burl, @"Must parse"); + NSAssert(burl.isValid == NO, @"Must not be valid"); + NSAssert(burl.amount == 0, @"Default amount is zero"); + NSAssert(burl.address == nil, @"Default address is nil"); + NSAssert(burl.paymentRequestURL == nil, @"Must have nil payment request"); + NSAssert([burl[@"x"] isEqual: @"something"], @"Must have query item"); + } + + { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:?amount=1.2"]]; + NSAssert(burl, @"Must parse"); + NSAssert(burl.isValid == NO, @"Must not be valid"); + NSAssert(burl.amount == 120000000, @"Must parse amount"); + NSAssert(burl.address == nil, @"Default address is nil"); + NSAssert(burl.paymentRequestURL == nil, @"Must have nil payment request"); + NSAssert([burl[@"amount"] isEqual: @"1.2"], @"Must have query item"); + } +} + ++ (void) testMalformedURL { + { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"bitcoin:xxxx"]]; + NSAssert(!burl, @"Must not parse broken address"); + } + { + BTCBitcoinURL* burl = [[BTCBitcoinURL alloc] initWithURL:[NSURL URLWithString:@"http://example.com"]]; + NSAssert(!burl, @"Must not parse other schemas than bitcoin:"); + } +} +@end diff --git a/UnitTests/main.m b/UnitTests/main.m index 48f7dc48..8efe13fe 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -19,6 +19,7 @@ #import "BTCBlockchainInfo+Tests.h" #import "BTCPriceSource+Tests.h" #import "BTCMerkleTree+Tests.h" +#import "BTCBitcoinURL+Tests.h" int main(int argc, const char * argv[]) { @@ -42,6 +43,8 @@ int main(int argc, const char * argv[]) [BTCMerkleTree runAllTests]; [BTCBlockchainInfo runAllTests]; [BTCPriceSource runAllTests]; + [BTCBitcoinURL runAllTests]; + [BTCTransaction runAllTests]; // has some interactive features to ask for private key NSLog(@"All tests passed."); } From 3ba04c4f0edb0793bce72d1dbdf4d413bff64d85 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Thu, 2 Apr 2015 10:27:13 +0200 Subject: [PATCH 037/163] removed dead code --- CoreBitcoin/BTCKey+Tests.m | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index d5de292a..45a3f4c0 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -59,13 +59,6 @@ + (void) testRFC6979 { BTCHash256([@"test message" dataUsingEncoding:NSUTF8StringEncoding]), @"c5186174691d589ad5fec3d34deac8a1a2b4156fd87a27ea8961dffe5d056ae9", @"304402201008e236fa8cd0f25df4482dddbb622e8a8b26ef0ba731719458de3ccd93805b022032f8ebe514ba5f672466eba334639282616bb3c2f0ab09998037513d1f9e3d6d"); - -// verifyRFC6979Signature(@"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50", @"sample", @"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"); -// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"Satoshi Nakamoto", @"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"); -// verifyRFC6979Signature(@"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @"Satoshi Nakamoto", @"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"); -// verifyRFC6979Signature(@"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181", @"Alan Turing", @"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"); -// verifyRFC6979Signature(@"0000000000000000000000000000000000000000000000000000000000000001", @"All those moments will be lost in time, like tears in rain. Time to die...", @"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"); -// verifyRFC6979Signature(@"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2", @"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", @"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6"); } + (void) testDiffieHellman { From 4a509a603152ad14f55207c2b67ecae38ae2aa94 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 5 Apr 2015 10:28:31 +0200 Subject: [PATCH 038/163] updated README and removed TODO.txt in favor of issues on Github --- CoreBitcoin.xcodeproj/project.pbxproj | 2 - README.md | 26 +++--------- TODO.txt | 57 --------------------------- 3 files changed, 6 insertions(+), 79 deletions(-) delete mode 100644 TODO.txt diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ea6113d5..b1c3e5d6 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -455,7 +455,6 @@ 2037AB1717D3D1F900DB248C /* BTCBase58+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCBase58+Tests.m"; sourceTree = ""; }; 2039DB0117E2ED2B0051177B /* GLOSSARY.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = GLOSSARY.md; sourceTree = ""; }; 2039DB0217E2ED7F0051177B /* ScriptStringRepresentation.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = ScriptStringRepresentation.md; sourceTree = ""; }; - 204F8A801845FAF8002649CE /* TODO.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TODO.txt; sourceTree = ""; }; 204FB500194C63B500C131DE /* BTCBlindSignature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCBlindSignature.h; sourceTree = ""; }; 204FB501194C63B500C131DE /* BTCBlindSignature.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCBlindSignature.m; sourceTree = ""; }; 2054DC731950E35E007175C8 /* BTCFancyEncryptedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCFancyEncryptedMessage.h; sourceTree = ""; }; @@ -779,7 +778,6 @@ children = ( 2084DD9417B8FF7E005AC9E6 /* README.md */, 20A443D21AC941F7008B3447 /* ReleaseNotes.md */, - 204F8A801845FAF8002649CE /* TODO.txt */, 2039DB0117E2ED2B0051177B /* GLOSSARY.md */, 20203ADD19F66B6E0085C01F /* MEME_GLOSSARY.md */, 209818911839688D00541747 /* LICENSE.txt */, diff --git a/README.md b/README.md index b159ec96..912a7cff 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,14 @@ Advanced Features On the roadmap -------------- -See all [todo items here](https://github.com/oleganza/CoreBitcoin/issues). +See [all todo items](https://github.com/oleganza/CoreBitcoin/issues). - Complete support for blocks and block headers. - SPV mode and P2P communication with other nodes. - Full blockchain verification procedure and storage. - Importing BitcoinQT, Electrum and Blockchain.info wallets. - Support for [libsecp256k1](https://github.com/bitcoin/secp256k1) in addition to OpenSSL. -- Eventual support for libconsensus as it gets more mature and feature-full. +- Eventual support for libconsensus as it gets more mature and feature-complete. The goal is to implement everything useful related to Bitcoin and organize it nicely in a single powerful library. Pull requests are welcome. @@ -119,18 +119,6 @@ Include headers: There are also raw universal libraries (.a) with headers located in binaries/include, if you happen to need them for some reason. Frameworks and binary libraries have OpenSSL built-in. If you have different version of OpenSSL in your project, consider using CocoaPods or raw sources of CoreBitcoin. -Bounties --------- - -- [done] 0.1 BTC for a CocoaPod. OpenSSL should be bundled automatically (or as a dependency). [@oleganza] -- [done] 0.5 BTC for building CoreBitcoin.a with headers and support for x86_64, armv7, armv7s, armv64. OpenSSL should be bundled inside. [@oleganza] -- [done] extra 0.5 BTC for building CoreBitcoin.framework with support for x86_64, armv7, armv7s, armv64. OpenSSL should be bundled inside. It's okay to have one framework for OS X and one for iOS. [@oleganza] -- 3 BTC for ECIES encryption scheme [@yrashk] -- 0.25 BTC for P2P communication - -To add your own bounty, add a line here (or edit an existing one), make a pull request and donate to the address below. Your donation will be reserved for that bounty only. I will contact you to check if the implementation is acceptable before paying out. - - Swift ----- @@ -152,9 +140,8 @@ Twitter: [@oleganza](http://twitter.com/oleganza) To publish on CocoaPods: -``` -$ pod trunk push --verbose --use-libraries -``` + $ pod trunk push --verbose --use-libraries + Donate ------ @@ -163,12 +150,11 @@ Please send your donations here: 1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG. All funds will be used only for bounties. -You can also donate to a specific bounty. The amount will be reserved for that bounty and listed above. Contact Oleg to arrange that. +You can also donate to a specific bounty. The amount will be reserved for that bounty and listed above. Contact [Oleg](mailto:oleganza@gmail.com) to arrange that. License ------- -Released under the [WTFPL](http://www.wtfpl.net) except for OpenSSL. No contributor to CoreBitcoin will ever be able to drag you in court if you do not mention CoreBitcoin in your legalese. Crediting authors and improving library is voluntary and highly appreciated. Have a nice day. - +Released under the highly permissive [WTFPL](http://www.wtfpl.net) (except for OpenSSL). Have a nice day. diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 6903a0b9..00000000 --- a/TODO.txt +++ /dev/null @@ -1,57 +0,0 @@ -TODO: - -- add coinbaseData property to BTCTransactionInput to be able to read coinbase raw data when as a script it's invalid. -- make BTCTransactionInput store BTCOutpoint natively instead of building it on the fly. -- support for blocks and their validation -- support for network messages -- support for SPV like in Bitcoinj -- add aggregate header CoreBitcoinTests.h with tests -- QR code support (rendering and scanning) for OS X -- BitcoinQT wallet reader. -- Blockchain.info wallet reader. -- Modern unit test suite in Xcode 5 (currently asserts work good enough; if you want to improve that, do it yourself). -- Security analysis. Do we use truly random numbers? Do we sign things correctly? Do we have buffer overflows? And so on. Should add bounties for that. - -Peer-to-peer connectivity features: - -1. Support for Bitcoin network message would allow: -- SPV -- full node implementation -- experiments with compact full node implementations (partial block storage, partial UTXO) - -2. Support for multi-peer connectivity and Bluetooth: -- to push tx directly to a recipient (e.g. while in roaming or where 3G connection is poor). -- to enable 2-factor signing (e.g. from a phone and from a laptop). -- enable in-person contract signing without intermediary app or service. - - -Done: - -+ QR code support (rendering and scanning) for iOS -+ currency converter to have a common codebase not linked to any exchange -+ number formatter for various btc units -+ bitcoin URL support (parsing and composing) -+ configurable and extensible transaction builder - -+ Unit tests for EC sign/verify. -+ Unit tests for transaction parsing/serialization. - -+ deterministic ECDSA (where nonce is derived from privkey and message hash) -+ support for HD wallets - -+ Scrypt wrapper API where fixed number of rounds and memory usage can be specified (and not computed from current machine capabilities) [bounty 0.1 BTC by @oleganza] -- Done in a separate BTCScrypt repository (https://github.com/oleganza/BTCScrypt) - -+ Sample code for creating a transaction given some txouts. -+ Sample code for verifying a transaction. - -+ full transaction support. -+ Universal OpenSSL libraries. Or as a part of a build process. -+ CoreBitcoin.framework for iOS and OS X with OpenSSL inside. - -+ rename both iOS and OSX frameworks to CoreBitcoin so the #include can be the same for both platforms in a portable code (we can ship it as "iOS/CoreBitcoin.framework") -+ add aggregate header CoreBitcoin.h with all non-test and non-category methods. -+ add aggregate header CoreBitcoin+Categories.h that includes categories on standard classes. -+ add openssl dependency in cocoapods (we can specify the exact hash for security). If openssl cocoapods does not have fat iOS build, that needs to be fixed. - - From 0b0c8a5ce82ca02045b67b986a3ef32bc09516e3 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 5 Apr 2015 10:29:32 +0200 Subject: [PATCH 039/163] fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 912a7cff..824a8586 100644 --- a/README.md +++ b/README.md @@ -156,5 +156,5 @@ You can also donate to a specific bounty. The amount will be reserved for that b License ------- -Released under the highly permissive [WTFPL](http://www.wtfpl.net) (except for OpenSSL). Have a nice day. +Released under [WTFPL](http://www.wtfpl.net) (except for OpenSSL). Have a nice day. From 6ef58f66797fbb7917b6a99a708c697bd1961487 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 20 Apr 2015 15:40:59 +0200 Subject: [PATCH 040/163] fixed paths per @rsmoz suggestion in #26 --- CoreBitcoin.xcodeproj/project.pbxproj | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index b1c3e5d6..ef7c3d10 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -291,6 +291,7 @@ 209D1E2118D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; 209D1E2218D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; 209D1E2318D4F12500293483 /* BTCProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 209D1E1B18D4F12500293483 /* BTCProcessor.m */; }; + 209F27C11AE538F80092A1FB /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2084DD9617B8FFA2005AC9E6 /* Security.framework */; }; 20A443B51AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; 20A443B61AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; 20A443B71AC55F52008B3447 /* BTCPaymentProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A443B11AC55F52008B3447 /* BTCPaymentProtocol.h */; }; @@ -632,6 +633,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 209F27C11AE538F80092A1FB /* Security.framework in Frameworks */, 20C0DDB8183BC5EA00A9EED0 /* libcrypto-osx.a in Frameworks */, 20C0DDB9183BC5EA00A9EED0 /* libssl-osx.a in Frameworks */, ); @@ -1578,7 +1580,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = CoreBitcoinIOS; @@ -1605,7 +1607,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = CoreBitcoinIOS; @@ -1637,7 +1639,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); LINK_WITH_STANDARD_LIBRARIES = NO; MACH_O_TYPE = mh_object; @@ -1668,7 +1670,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); LINK_WITH_STANDARD_LIBRARIES = NO; MACH_O_TYPE = mh_object; @@ -1696,7 +1698,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_NAME = CoreBitcoinOSX; @@ -1718,7 +1720,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_NAME = CoreBitcoinOSX; @@ -1747,7 +1749,7 @@ LD_DYLIB_INSTALL_NAME = ""; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1773,7 +1775,7 @@ LD_DYLIB_INSTALL_NAME = ""; LIBRARY_SEARCH_PATHS = ( "$(inherited)", - /Volumes/work/projects/CoreBitcoin/CoreBitcoin/openssl/lib, + "$(PROJECT_DIR)/openssl/lib", ); MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_NAME = "$(TARGET_NAME)"; From 6647070067a36134669f6c4f8f46990df4b403a3 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 20 Apr 2015 15:41:06 +0200 Subject: [PATCH 041/163] headers update --- CoreBitcoin/BTCBitcoinURL+Tests.h | 2 ++ CoreBitcoin/BTCBitcoinURL.h | 2 ++ CoreBitcoin/BTCBlindSignature+Tests.h | 2 -- CoreBitcoin/BTCBlindSignature.h | 2 -- CoreBitcoin/BTCChainCom.h | 2 ++ CoreBitcoin/BTCCurrencyConverter.h | 2 ++ CoreBitcoin/BTCEncryptedBackup+Tests.h | 2 ++ CoreBitcoin/BTCEncryptedMessage+Tests.h | 2 ++ CoreBitcoin/BTCFancyEncryptedMessage+Tests.h | 2 ++ CoreBitcoin/BTCMnemonic+Tests.h | 2 ++ CoreBitcoin/BTCNumberFormatter.h | 2 ++ CoreBitcoin/BTCPriceSource+Tests.h | 2 ++ CoreBitcoin/BTCQRCode.h | 2 ++ CoreBitcoin/BTCTransactionBuilder.h | 2 ++ CoreBitcoin/CoreBitcoin.h | 25 +++++++++++++------- 15 files changed, 40 insertions(+), 13 deletions(-) diff --git a/CoreBitcoin/BTCBitcoinURL+Tests.h b/CoreBitcoin/BTCBitcoinURL+Tests.h index 81901865..0899d829 100644 --- a/CoreBitcoin/BTCBitcoinURL+Tests.h +++ b/CoreBitcoin/BTCBitcoinURL+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + // // BTCBitcoinURL+Tests.h // CoreBitcoin diff --git a/CoreBitcoin/BTCBitcoinURL.h b/CoreBitcoin/BTCBitcoinURL.h index 42fc2cf3..8fa5f51d 100644 --- a/CoreBitcoin/BTCBitcoinURL.h +++ b/CoreBitcoin/BTCBitcoinURL.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import #import "BTCUnitsAndLimits.h" diff --git a/CoreBitcoin/BTCBlindSignature+Tests.h b/CoreBitcoin/BTCBlindSignature+Tests.h index 67a695cb..fbb44760 100644 --- a/CoreBitcoin/BTCBlindSignature+Tests.h +++ b/CoreBitcoin/BTCBlindSignature+Tests.h @@ -1,6 +1,4 @@ // CoreBitcoin by Oleg Andreev , WTFPL. -// Implementation of blind signatures for Bitcoin transactions: -// http://oleganza.com/blind-ecdsa-draft-v2.pdf #import "BTCBlindSignature.h" diff --git a/CoreBitcoin/BTCBlindSignature.h b/CoreBitcoin/BTCBlindSignature.h index 8b5b6c99..530b1704 100644 --- a/CoreBitcoin/BTCBlindSignature.h +++ b/CoreBitcoin/BTCBlindSignature.h @@ -1,6 +1,4 @@ // CoreBitcoin by Oleg Andreev , WTFPL. -// Implementation of blind signatures for Bitcoin transactions: -// http://oleganza.com/blind-ecdsa-draft-v2.pdf #import diff --git a/CoreBitcoin/BTCChainCom.h b/CoreBitcoin/BTCChainCom.h index 70675a49..3a7d034e 100644 --- a/CoreBitcoin/BTCChainCom.h +++ b/CoreBitcoin/BTCChainCom.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import @class BTCAddress; diff --git a/CoreBitcoin/BTCCurrencyConverter.h b/CoreBitcoin/BTCCurrencyConverter.h index b590f66b..c24f2895 100644 --- a/CoreBitcoin/BTCCurrencyConverter.h +++ b/CoreBitcoin/BTCCurrencyConverter.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import #import "BTCUnitsAndLimits.h" diff --git a/CoreBitcoin/BTCEncryptedBackup+Tests.h b/CoreBitcoin/BTCEncryptedBackup+Tests.h index f52e9f5a..08be28d7 100644 --- a/CoreBitcoin/BTCEncryptedBackup+Tests.h +++ b/CoreBitcoin/BTCEncryptedBackup+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + // // BTCEncryptedBackup+Tests.h // CoreBitcoin diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.h b/CoreBitcoin/BTCEncryptedMessage+Tests.h index 4516fc0d..0b23a72f 100644 --- a/CoreBitcoin/BTCEncryptedMessage+Tests.h +++ b/CoreBitcoin/BTCEncryptedMessage+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + // // BTCEncryptedMessage+Tests.h // CoreBitcoin diff --git a/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h index 316fcbf3..6009adc4 100644 --- a/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h +++ b/CoreBitcoin/BTCFancyEncryptedMessage+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + // // BTCFancyEncryptedMessage+Tests.h // CoreBitcoin diff --git a/CoreBitcoin/BTCMnemonic+Tests.h b/CoreBitcoin/BTCMnemonic+Tests.h index 97ffba6b..8363c47b 100644 --- a/CoreBitcoin/BTCMnemonic+Tests.h +++ b/CoreBitcoin/BTCMnemonic+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import "BTCMnemonic.h" @interface BTCMnemonic (Tests) diff --git a/CoreBitcoin/BTCNumberFormatter.h b/CoreBitcoin/BTCNumberFormatter.h index 78f0c36f..09875f19 100644 --- a/CoreBitcoin/BTCNumberFormatter.h +++ b/CoreBitcoin/BTCNumberFormatter.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import #import "BTCUnitsAndLimits.h" diff --git a/CoreBitcoin/BTCPriceSource+Tests.h b/CoreBitcoin/BTCPriceSource+Tests.h index b968ffdf..4fba98cb 100644 --- a/CoreBitcoin/BTCPriceSource+Tests.h +++ b/CoreBitcoin/BTCPriceSource+Tests.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import "BTCPriceSource.h" @interface BTCPriceSource (Tests) diff --git a/CoreBitcoin/BTCQRCode.h b/CoreBitcoin/BTCQRCode.h index b956a7c4..5e480fb4 100644 --- a/CoreBitcoin/BTCQRCode.h +++ b/CoreBitcoin/BTCQRCode.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import #if TARGET_OS_IPHONE #import diff --git a/CoreBitcoin/BTCTransactionBuilder.h b/CoreBitcoin/BTCTransactionBuilder.h index 47d218e4..1c62621d 100644 --- a/CoreBitcoin/BTCTransactionBuilder.h +++ b/CoreBitcoin/BTCTransactionBuilder.h @@ -1,3 +1,5 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + #import #import "BTCUnitsAndLimits.h" extern NSString* const BTCTransactionBuilderErrorDomain; diff --git a/CoreBitcoin/CoreBitcoin.h b/CoreBitcoin/CoreBitcoin.h index c4ca45c3..8ab4e4c5 100644 --- a/CoreBitcoin/CoreBitcoin.h +++ b/CoreBitcoin/CoreBitcoin.h @@ -1,35 +1,42 @@ // CoreBitcoin by Oleg Andreev , WTFPL. +#import #import #import #import +#import #import #import -#import #import #import +#import +#import #import #import +#import +#import #import +#import #import #import #import +#import #import #import +#import #import +#import +#import +#import +#import +#import #import +#import #import #import #import #import +#import #import #import -#import #import -#import -#import -#import -#import -#import -#import -#import From 051105c580fc335b39c33895b308fc8c4b821f91 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Mon, 20 Apr 2015 16:41:01 +0200 Subject: [PATCH 042/163] added support for BIP32 slash-separated paths `m/x/y/z/w` --- CoreBitcoin/BTCKeychain+Tests.m | 40 ++++++++++++++++----- CoreBitcoin/BTCKeychain.h | 28 ++++++++++++++- CoreBitcoin/BTCKeychain.m | 63 ++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/CoreBitcoin/BTCKeychain+Tests.m b/CoreBitcoin/BTCKeychain+Tests.m index 0c25d4f1..35bafb49 100644 --- a/CoreBitcoin/BTCKeychain+Tests.m +++ b/CoreBitcoin/BTCKeychain+Tests.m @@ -10,10 +10,34 @@ @implementation BTCKeychain (Tests) + (void) runAllTests { + [self testPaths]; [self testStandardTestVectors]; [self testZeroPaddedPrivateKeys]; } ++ (void) testPaths +{ + BTCKeychain* keychain = [[BTCKeychain alloc] initWithExtendedKey:@"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"]; + + NSAssert([[keychain derivedKeychainWithPath:@""].extendedPublicKey isEqual:keychain.extendedPublicKey], @"must return root key"); + NSAssert([[keychain derivedKeychainWithPath:@"m"].extendedPublicKey isEqual:keychain.extendedPublicKey], @"must return root key"); + NSAssert([[keychain derivedKeychainWithPath:@"/"].extendedPublicKey isEqual:keychain.extendedPublicKey], @"must return root key"); + NSAssert([[keychain derivedKeychainWithPath:@"m//"].extendedPublicKey isEqual:keychain.extendedPublicKey], @"must return root key"); + + NSAssert([[keychain derivedKeychainWithPath:@"m/0'"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:YES].extendedPublicKey], @"must return hardened child at index 0"); + NSAssert([[keychain derivedKeychainWithPath:@"/0'"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:YES].extendedPublicKey], @"must return hardened child at index 0"); + NSAssert([[keychain derivedKeychainWithPath:@"0'"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:YES].extendedPublicKey], @"must return hardened child at index 0"); + + NSAssert([[keychain derivedKeychainWithPath:@"m/0"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:NO].extendedPublicKey], @"must return non-hardened child at index 0"); + NSAssert([[keychain derivedKeychainWithPath:@"/0"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:NO].extendedPublicKey], @"must return non-hardened child at index 0"); + NSAssert([[keychain derivedKeychainWithPath:@"0"].extendedPublicKey isEqual:[keychain derivedKeychainAtIndex:0 hardened:NO].extendedPublicKey], @"must return non-hardened child at index 0"); + + NSAssert([keychain derivedKeychainWithPath:@"m / 0 / 1"] == nil, @"must return nil if path contains spaces"); + NSAssert([keychain derivedKeychainWithPath:@"m/b/c"] == nil, @"must return nil if path contains irrelevant characters"); + NSAssert([keychain derivedKeychainWithPath:@"1/m/2"] == nil, @"must return nil if path contains irrelevant characters"); + NSAssert([keychain derivedKeychainWithPath:@"m/1.2^3"] == nil, @"must return nil if path contains irrelevant characters"); +} + + (void) testStandardTestVectors { // Test Vector 1 @@ -58,8 +82,8 @@ + (void) testStandardTestVectors NSAssert([@"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" isEqualToString:masterChain.extendedPrivateKey], @""); [self testDeserializationWithKeychain:masterChain]; - - BTCKeychain* m0prv = [masterChain derivedKeychainAtIndex:0 hardened:YES]; + + BTCKeychain* m0prv = [masterChain derivedKeychainWithPath:@"m/0'"]; NSAssert(m0prv.parentFingerprint != 0, @""); NSAssert(m0prv.depth == 1, @""); @@ -71,7 +95,7 @@ + (void) testStandardTestVectors [self testDeserializationWithKeychain:m0prv]; - BTCKeychain* m0prv1pub = [m0prv derivedKeychainAtIndex:1]; + BTCKeychain* m0prv1pub = [masterChain derivedKeychainWithPath:@"m/0'/1"]; NSAssert(m0prv1pub.parentFingerprint != 0, @""); NSAssert(m0prv1pub.depth == 2, @""); @@ -83,7 +107,7 @@ + (void) testStandardTestVectors [self testDeserializationWithKeychain:m0prv1pub]; - BTCKeychain* m0prv1pub2prv = [m0prv1pub derivedKeychainAtIndex:2 hardened:YES]; + BTCKeychain* m0prv1pub2prv = [masterChain derivedKeychainWithPath:@"m/0'/1/2'"]; NSAssert([@"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5" isEqualToString:m0prv1pub2prv.extendedPublicKey], @""); NSAssert([@"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" isEqualToString:m0prv1pub2prv.extendedPrivateKey], @""); @@ -94,8 +118,8 @@ + (void) testStandardTestVectors NSAssert([@"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" isEqualToString:m0prv1pub2prv2pub.extendedPrivateKey], @""); [self testDeserializationWithKeychain:m0prv1pub2prv2pub]; - - BTCKeychain* m0prv1pub2prv2pub1Gpub = [m0prv1pub2prv2pub derivedKeychainAtIndex:1000000000]; + + BTCKeychain* m0prv1pub2prv2pub1Gpub = [masterChain derivedKeychainWithPath:@"m/0'/1/2'/2/1000000000"]; NSAssert([@"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" isEqualToString:m0prv1pub2prv2pub1Gpub.extendedPublicKey], @""); NSAssert([@"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" isEqualToString:m0prv1pub2prv2pub1Gpub.extendedPrivateKey], @""); @@ -162,13 +186,13 @@ + (void) testStandardTestVectors [self testDeserializationWithKeychain:m0pubFFprv1]; - BTCKeychain* m0pubFFprv1pubFEprv = [m0pubFFprv1 derivedKeychainAtIndex:2147483646 hardened:YES]; + BTCKeychain* m0pubFFprv1pubFEprv = [masterChain derivedKeychainWithPath:@"m/0/2147483647'/1/2147483646'"]; NSAssert([@"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL" isEqualToString:m0pubFFprv1pubFEprv.extendedPublicKey], @""); NSAssert([@"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" isEqualToString:m0pubFFprv1pubFEprv.extendedPrivateKey], @""); [self testDeserializationWithKeychain:m0pubFFprv1pubFEprv]; - BTCKeychain* m0pubFFprv1pubFEprv2 = [m0pubFFprv1pubFEprv derivedKeychainAtIndex:2]; + BTCKeychain* m0pubFFprv1pubFEprv2 = [masterChain derivedKeychainWithPath:@"m/0/2147483647'/1/2147483646'/2"]; NSAssert([@"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt" isEqualToString:m0pubFFprv1pubFEprv2.extendedPublicKey], @""); NSAssert([@"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" isEqualToString:m0pubFFprv1pubFEprv2.extendedPrivateKey], @""); diff --git a/CoreBitcoin/BTCKeychain.h b/CoreBitcoin/BTCKeychain.h index 2e2c096f..ab468b51 100644 --- a/CoreBitcoin/BTCKeychain.h +++ b/CoreBitcoin/BTCKeychain.h @@ -110,7 +110,33 @@ static const uint32_t BTCKeychainMaxIndex = 0x7fffffff; // This feature is used in BTCBlindSignature protocol. - (BTCKeychain*) derivedKeychainAtIndex:(uint32_t)index hardened:(BOOL)hardened factor:(BTCBigNumber**)factorOut; -// Returns a derived key from this keychain. This is a convenient way to access [... derivedKeychainAtIndex:i hardened:YES/NO].rootKey +// Parses the BIP32 path and derives the chain of keychains accordingly. +// Path syntax: (m?/)?([0-9]+'?(/[0-9]+'?)*)? +// The following paths are valid: +// +// "" (root key) +// "m" (root key) +// "/" (root key) +// "m/0'" (hardened child #0 of the root key) +// "/0'" (hardened child #0 of the root key) +// "0'" (hardened child #0 of the root key) +// "m/44'/1'/2'" (BIP44 testnet account #2) +// "/44'/1'/2'" (BIP44 testnet account #2) +// "44'/1'/2'" (BIP44 testnet account #2) +// +// The following paths are invalid: +// +// "m / 0 / 1" (contains spaces) +// "m/b/c" (alphabetical characters instead of numerical indexes) +// "m/1.2^3" (contains illegal characters) +- (BTCKeychain*) derivedKeychainWithPath:(NSString*)path; + +// Returns a derived key for a given BIP32 path. +// Equivalent to `[keychain derivedKeychainWithPath:@"..."].key` +- (BTCKey*) keyWithPath:(NSString*)path; + +// Returns a derived key from this keychain. +// Equivalent to [keychain derivedKeychainAtIndex:i hardened:YES/NO].key // If the receiver contains a private key, child key will also contain a private key. // If the receiver contains only a public key, child key will only contain a public key. (Or nil will be returned if hardened = YES.) // By default, a normal (non-hardened) derivation is used. diff --git a/CoreBitcoin/BTCKeychain.m b/CoreBitcoin/BTCKeychain.m index 82f473e6..abe63c77 100644 --- a/CoreBitcoin/BTCKeychain.m +++ b/CoreBitcoin/BTCKeychain.m @@ -389,7 +389,68 @@ - (BTCKey*) keyAtIndex:(uint32_t)index } - (BTCKey*) keyAtIndex:(uint32_t)index hardened:(BOOL)hardened { - return [[self derivedKeychainAtIndex:index hardened:hardened] key]; + return [self derivedKeychainAtIndex:index hardened:hardened].key; +} + + +// Parses the BIP32 path and derives the chain of keychains accordingly. +// Path syntax: (m?/)?([0-9]+'?(/[0-9]+'?)*)? +// The following paths are valid: +// +// "" (root key) +// "m" (root key) +// "/" (root key) +// "m/0'" (hardened child #0 of the root key) +// "/0'" (hardened child #0 of the root key) +// "0'" (hardened child #0 of the root key) +// "m/44'/1'/2'" (BIP44 testnet account #2) +// "/44'/1'/2'" (BIP44 testnet account #2) +// "44'/1'/2'" (BIP44 testnet account #2) +// +// The following paths are invalid: +// +// "m / 0 / 1" (contains spaces) +// "m/b/c" (alphabetical characters instead of numerical indexes) +// "m/1.2^3" (contains illegal characters) +- (BTCKeychain*) derivedKeychainWithPath:(NSString*)path { + + if (path == nil) return nil; + + if ([path isEqualToString:@"m"] || + [path isEqualToString:@"/"] || + [path isEqualToString:@""]) { + return self; + } + + BTCKeychain* kc = self; + + if ([path rangeOfString:@"m/"].location == 0) { // strip "m/" from the beginning. + path = [path substringFromIndex:2]; + } + for (NSString* chunk in [path componentsSeparatedByString:@"/"]) { + if (chunk.length == 0) { + continue; + } + BOOL hardened = NO; + NSString* indexString = chunk; + if ([chunk rangeOfString:@"'"].location == chunk.length - 1) { + hardened = YES; + indexString = [chunk substringToIndex:chunk.length - 1]; + } + + // Make sure the chunk is just a number + NSInteger i = [indexString integerValue]; + if (i >= 0 && [@(i).stringValue isEqualToString:indexString]) { + kc = [kc derivedKeychainAtIndex:(uint32_t)i hardened:hardened]; + } else { + return nil; + } + } + return kc; +} + +- (BTCKey*) keyWithPath:(NSString*)path { + return [self derivedKeychainWithPath:path].key; } - (BTCKeychain*) publicKeychain From 9d6da5e5eed3059ff3c9cfed030a4c8e7efd4c23 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 21 Apr 2015 11:57:41 +0200 Subject: [PATCH 043/163] make sure we do not validate incompatible versions of BIP70 payment request --- CoreBitcoin/BTCPaymentProtocol.h | 2 ++ CoreBitcoin/BTCPaymentProtocol.m | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CoreBitcoin/BTCPaymentProtocol.h b/CoreBitcoin/BTCPaymentProtocol.h index 0eed64ac..74f15271 100644 --- a/CoreBitcoin/BTCPaymentProtocol.h +++ b/CoreBitcoin/BTCPaymentProtocol.h @@ -26,6 +26,8 @@ typedef NS_ENUM(NSInteger, BTCPaymentRequestStatus) { // Payment request is valid and the user can trust it. BTCPaymentRequestStatusValid = 0, // signed with a valid and known certificate. + BTCPaymentRequestStatusNotCompatible = 100, // version is not supported (currently only v1 is supported) + // These allow Payment Request to be accepted with a warning to the user. BTCPaymentRequestStatusUnsigned = 101, // PKI type is "none" BTCPaymentRequestStatusUnknown = 102, // PKI type is unknown (for forward compatibility may allow sending or warn to upgrade). diff --git a/CoreBitcoin/BTCPaymentProtocol.m b/CoreBitcoin/BTCPaymentProtocol.m index 8c5bdfff..4d04c0da 100644 --- a/CoreBitcoin/BTCPaymentProtocol.m +++ b/CoreBitcoin/BTCPaymentProtocol.m @@ -363,6 +363,12 @@ - (void) validatePaymentRequest { _isValidated = YES; _isValid = NO; + // Make sure we do not accidentally send funds to a payment request that we do not support. + if (self.version != BTCPaymentRequestVersion1) { + _status = BTCPaymentRequestStatusNotCompatible; + return; + } + if ([self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA1] || [self.pkiType isEqual:BTCPaymentRequestPKITypeX509SHA256]) { From d1ce1af904cc6517f4e1cc133f517af7c7bad6c4 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Tue, 21 Apr 2015 14:54:35 +0200 Subject: [PATCH 044/163] deprecated BTCAddress.base58string initializer and property in favor of simply "string" (also cleaned up isEqualTo:) --- CoreBitcoin/BTCAddress+Tests.m | 26 ++++++++++++------------- CoreBitcoin/BTCAddress.h | 4 ++-- CoreBitcoin/BTCBitcoinURL.m | 2 +- CoreBitcoin/BTCBlockchainInfo+Tests.m | 4 ++-- CoreBitcoin/BTCEncryptedMessage+Tests.m | 4 ++-- CoreBitcoin/BTCKey+Tests.m | 6 +++--- CoreBitcoin/BTCKey.m | 6 +++--- CoreBitcoin/BTCKeychain+Tests.m | 8 ++++---- CoreBitcoin/BTCMerkleTree+Tests.m | 6 +++--- CoreBitcoin/BTCScript+Tests.m | 16 +++++++-------- CoreBitcoin/BTCTransaction+Tests.m | 4 ++-- 11 files changed, 43 insertions(+), 43 deletions(-) diff --git a/CoreBitcoin/BTCAddress+Tests.m b/CoreBitcoin/BTCAddress+Tests.m index 3db8e01a..bf4c60d9 100644 --- a/CoreBitcoin/BTCAddress+Tests.m +++ b/CoreBitcoin/BTCAddress+Tests.m @@ -14,59 +14,59 @@ + (void) runAllTests + (void) testPublicKeyAddress { - BTCPublicKeyAddress* addr = [BTCPublicKeyAddress addressWithBase58String:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"]; + BTCPublicKeyAddress* addr = [BTCPublicKeyAddress addressWithString:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"]; NSAssert(addr, @"Address should be decoded"); NSAssert([addr isKindOfClass:[BTCPublicKeyAddress class]], @"Address should be an instance of BTCPublicKeyAddress"); NSAssert([@"c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827" isEqualToString:[addr.data hex]], @"Must decode hash160 correctly."); - NSAssert([addr isEqualTo:addr.publicAddress], @"Address should be equal to its publicAddress"); + NSAssert([addr isEqual:addr.publicAddress], @"Address should be equal to its publicAddress"); BTCPublicKeyAddress* addr2 = [BTCPublicKeyAddress addressWithData:BTCDataFromHex(@"c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827")]; NSAssert(addr2, @"Address should be created"); - NSAssert([@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T" isEqualToString:addr2.base58String], @"Must encode hash160 correctly."); + NSAssert([@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T" isEqualToString:addr2.string], @"Must encode hash160 correctly."); } + (void) testPrivateKeyAddress { - BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithBase58String:@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS"]; + BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithString:@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS"]; NSAssert(addr, @"Address should be decoded"); NSAssert([addr isKindOfClass:[BTCPrivateKeyAddress class]], @"Address should be an instance of BTCPrivateKeyAddress"); NSAssert(!addr.isPublicKeyCompressed, @"Address should be not compressed"); NSAssert([@"c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a" isEqualToString:addr.data.hex], @"Must decode secret key correctly."); - NSAssert([[[addr publicAddress] base58String] isEqualTo:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"must provide proper public address"); + NSAssert([[addr publicAddress].string isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"must provide proper public address"); BTCPrivateKeyAddress* addr2 = [BTCPrivateKeyAddress addressWithData:BTCDataFromHex(@"c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")]; NSAssert(addr2, @"Address should be created"); - NSAssert([@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS" isEqualToString:addr2.base58String], @"Must encode secret key correctly."); + NSAssert([@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS" isEqualToString:addr2.string], @"Must encode secret key correctly."); } + (void) testPrivateKeyAddressWithCompressedPoint { - BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithBase58String:@"L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu"]; + BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithString:@"L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu"]; NSAssert(addr, @"Address should be decoded"); NSAssert([addr isKindOfClass:[BTCPrivateKeyAddress class]], @"Address should be an instance of BTCPrivateKeyAddress"); NSAssert(addr.isPublicKeyCompressed, @"Address should be compressed"); NSAssert([@"c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a" isEqualToString:addr.data.hex], @"Must decode secret key correctly."); - NSAssert([[[addr publicAddress] base58String] isEqualTo:@"1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"], @"must provide proper public address"); + NSAssert([[addr publicAddress].string isEqual:@"1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8"], @"must provide proper public address"); BTCPrivateKeyAddress* addr2 = [BTCPrivateKeyAddress addressWithData:BTCDataFromHex(@"c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")]; NSAssert(addr2, @"Address should be created"); addr2.publicKeyCompressed = YES; - NSAssert([@"L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu" isEqualToString:addr2.base58String], @"Must encode secret key correctly."); + NSAssert([@"L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu" isEqualToString:addr2.string], @"Must encode secret key correctly."); addr2.publicKeyCompressed = NO; - NSAssert([@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS" isEqualToString:addr2.base58String], @"Must encode secret key correctly."); + NSAssert([@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS" isEqualToString:addr2.string], @"Must encode secret key correctly."); } + (void) testScriptHashKeyAddress { - BTCScriptHashAddress* addr = [BTCScriptHashAddress addressWithBase58String:@"3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8"]; + BTCScriptHashAddress* addr = [BTCScriptHashAddress addressWithString:@"3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8"]; NSAssert(addr, @"Address should be decoded"); NSAssert([addr isKindOfClass:[BTCScriptHashAddress class]], @"Address should be an instance of BTCScriptHashAddress"); NSAssert([@"e8c300c87986efa84c37c0519929019ef86eb5b4" isEqualToString:addr.data.hex], @"Must decode hash160 correctly."); - NSAssert([addr isEqualTo:addr.publicAddress], @"Address should be equal to its publicAddress"); + NSAssert([addr isEqual:addr.publicAddress], @"Address should be equal to its publicAddress"); BTCScriptHashAddress* addr2 = [BTCScriptHashAddress addressWithData:BTCDataFromHex(@"e8c300c87986efa84c37c0519929019ef86eb5b4")]; NSAssert(addr2, @"Address should be created"); - NSAssert([@"3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8" isEqualToString:addr2.base58String], @"Must encode hash160 correctly."); + NSAssert([@"3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8" isEqualToString:addr2.string], @"Must encode hash160 correctly."); } @end diff --git a/CoreBitcoin/BTCAddress.h b/CoreBitcoin/BTCAddress.h index 8e249986..7df18d8b 100644 --- a/CoreBitcoin/BTCAddress.h +++ b/CoreBitcoin/BTCAddress.h @@ -27,7 +27,7 @@ // Returns an instance of a specific subclass depending on version number. // Returns nil for unsupported addresses. // DEPRECATED! Use `-addressWithString:` instead. -+ (instancetype) addressWithBase58String:(NSString*)string; ++ (instancetype) addressWithBase58String:(NSString*)string DEPRECATED_ATTRIBUTE; // Returns binary contents of an address (without checksums or version number). // 20 bytes for hashes, 32 bytes for private key. @@ -38,7 +38,7 @@ // Returns representation in base58 encoding. // DEPRECATED! Use -string instead. -@property(nonatomic, readonly) NSString* base58String; +@property(nonatomic, readonly) NSString* base58String DEPRECATED_ATTRIBUTE; /*! * Returns a public version of this address. By default it's receiver itself. diff --git a/CoreBitcoin/BTCBitcoinURL.m b/CoreBitcoin/BTCBitcoinURL.m index dfb07de7..ede3ef13 100644 --- a/CoreBitcoin/BTCBitcoinURL.m +++ b/CoreBitcoin/BTCBitcoinURL.m @@ -41,7 +41,7 @@ - (id) initWithURL:(NSURL*)url { // We allow empty address, but if it's not empty, it must be a valid address. BTCAddress* address = nil; if (comps.path.length > 0) { - address = [BTCAddress addressWithBase58String:comps.path]; + address = [BTCAddress addressWithString:comps.path]; if (!address) { return nil; } diff --git a/CoreBitcoin/BTCBlockchainInfo+Tests.m b/CoreBitcoin/BTCBlockchainInfo+Tests.m index 91576510..39bd787f 100644 --- a/CoreBitcoin/BTCBlockchainInfo+Tests.m +++ b/CoreBitcoin/BTCBlockchainInfo+Tests.m @@ -18,7 +18,7 @@ + (void) testUnspentOutputs { NSError* error = nil; - NSArray* outputs = [[[BTCBlockchainInfo alloc] init] unspentOutputsWithAddresses:@[ [BTCAddress addressWithBase58String:@"1LKF45kfvHAaP7C4cF91pVb3bkAsmQ8nBr"] ] error:&error]; + NSArray* outputs = [[[BTCBlockchainInfo alloc] init] unspentOutputsWithAddresses:@[ [BTCAddress addressWithString:@"1LKF45kfvHAaP7C4cF91pVb3bkAsmQ8nBr"] ] error:&error]; NSAssert([outputs isEqual:@[]], @"should return an empty array"); NSAssert(!error, @"should have no error"); @@ -27,7 +27,7 @@ + (void) testUnspentOutputs { NSError* error = nil; - NSArray* outputs = [[[BTCBlockchainInfo alloc] init] unspentOutputsWithAddresses:@[ [BTCAddress addressWithBase58String:@"1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"] ] error:&error]; + NSArray* outputs = [[[BTCBlockchainInfo alloc] init] unspentOutputsWithAddresses:@[ [BTCAddress addressWithString:@"1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"] ] error:&error]; NSAssert(outputs.count > 0, @"should return non-empty array"); NSAssert([outputs.firstObject isKindOfClass:[BTCTransactionOutput class]], @"should contain BTCTransactionOutput objects"); diff --git a/CoreBitcoin/BTCEncryptedMessage+Tests.m b/CoreBitcoin/BTCEncryptedMessage+Tests.m index 40deb147..e95d6776 100644 --- a/CoreBitcoin/BTCEncryptedMessage+Tests.m +++ b/CoreBitcoin/BTCEncryptedMessage+Tests.m @@ -22,13 +22,13 @@ + (void) runAllTests { NSData* expectedCiphertext = BTCDataFromHex(@"0339e504d6492b082da96e11e8f039796b06cd4855c101e2492a6f10f3e056a9e712c732611c6917ab5c57a1926973bc44a1586e94a783f81d05ce72518d9b0a80e2e13c7ff7d1306583f9cc7a48def5b37fbf2d5f294f128472a6e9c78dede5f5"); NSData* ciphertext = [em encrypt:message]; - NSAssert([ciphertext isEqualTo:expectedCiphertext], @"Must encrypt correctly"); + NSAssert([ciphertext isEqual:expectedCiphertext], @"Must encrypt correctly"); // Must decrypt. NSData* plaintext = [em decrypt:expectedCiphertext]; - NSAssert([plaintext isEqualTo:message], @"Must decrypt correctly"); + NSAssert([plaintext isEqual:message], @"Must decrypt correctly"); } diff --git a/CoreBitcoin/BTCKey+Tests.m b/CoreBitcoin/BTCKey+Tests.m index 45a3f4c0..6e60a48e 100644 --- a/CoreBitcoin/BTCKey+Tests.m +++ b/CoreBitcoin/BTCKey+Tests.m @@ -165,8 +165,8 @@ + (void) testBasicSigning //NSLog(@"pubkeyAddress.base58String = %@", pubkeyAddress.base58String); - NSAssert([pubkeyAddress.base58String isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @""); - NSAssert([privkeyAddress.base58String isEqual:@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS"], @""); + NSAssert([pubkeyAddress.string isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @""); + NSAssert([privkeyAddress.string isEqual:@"5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS"], @""); NSData* signature = [key signatureForHash:messageData.SHA256]; // NSLog(@"Signature: %@ (%d bytes)", [signature hexString], (int)signature.length); @@ -231,7 +231,7 @@ + (void) testBitcoinSignedMessage BTCKey* key = [BTCKey verifySignature:signature forMessage:@"Test message"]; NSAssert([key isValidSignature:signature forMessage:@"Test message"], @"Should validate signature"); - NSAssert([key.uncompressedPublicKeyAddress.base58String isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"Should be signed with 1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"); + NSAssert([key.uncompressedPublicKeyAddress.string isEqual:@"1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"], @"Should be signed with 1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T"); } } diff --git a/CoreBitcoin/BTCKey.m b/CoreBitcoin/BTCKey.m index ddac3484..d8e9e058 100644 --- a/CoreBitcoin/BTCKey.m +++ b/CoreBitcoin/BTCKey.m @@ -74,7 +74,7 @@ - (id) initWithCurvePoint:(BTCCurvePoint*)curvePoint - (id) initWithWIF:(NSString*)wifString { - BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithBase58String:wifString]; + BTCPrivateKeyAddress* addr = [BTCPrivateKeyAddress addressWithString:wifString]; if (![addr isKindOfClass:[BTCPrivateKeyAddress class]]) { return nil; @@ -401,13 +401,13 @@ - (NSMutableData*) privateKey - (NSString*) WIF { if (!self.privateKey) return nil; - return [self privateKeyAddress].base58String; + return [self privateKeyAddress].string; } - (NSString*) WIFTestnet { if (!self.privateKey) return nil; - return [self privateKeyAddressTestnet].base58String; + return [self privateKeyAddressTestnet].string; } - (void) setPublicKey:(NSData *)publicKey diff --git a/CoreBitcoin/BTCKeychain+Tests.m b/CoreBitcoin/BTCKeychain+Tests.m index 35bafb49..8f4a0f82 100644 --- a/CoreBitcoin/BTCKeychain+Tests.m +++ b/CoreBitcoin/BTCKeychain+Tests.m @@ -68,7 +68,7 @@ + (void) testStandardTestVectors BTCKeychain* masterChain = [[BTCKeychain alloc] initWithSeed:seed]; - NSAssert([masterChain.key.compressedPublicKeyAddress.base58String isEqualToString:@"15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"], @""); + NSAssert([masterChain.key.compressedPublicKeyAddress.string isEqualToString:@"15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma"], @""); //NSLog(@"identifier: %@ fingerprint: %@", masterChain.identifier, @(masterChain.fingerprint)); NSAssert([masterChain.identifier isEqual:BTCDataFromHex(@"3442193e1bb70916e914552172cd4e2dbc9df811")], @""); @@ -233,11 +233,11 @@ + (void) testZeroPaddedPrivateKeys } // Same as BIP32.org - NSAssert([[keychain keyAtIndex:70 hardened:YES].address.base58String isEqualToString:@"1FZQfsXwAoUcn9WVwbfRb4jMMkPJEozLWH"], @""); + NSAssert([[keychain keyAtIndex:70 hardened:YES].address.string isEqualToString:@"1FZQfsXwAoUcn9WVwbfRb4jMMkPJEozLWH"], @""); NSAssert(((uint8_t*)[keychain keyAtIndex:70 hardened:YES].privateKey.bytes)[0] == 0, @"must be zero-prefixed"); - NSAssert([[keychain keyAtIndex:227 hardened:YES].address.base58String isEqualToString:@"1LRbeWJC3sLGRk7ob82djVYTNhsH2UdR4f"], @""); + NSAssert([[keychain keyAtIndex:227 hardened:YES].address.string isEqualToString:@"1LRbeWJC3sLGRk7ob82djVYTNhsH2UdR4f"], @""); NSAssert(((uint8_t*)[keychain keyAtIndex:227 hardened:YES].privateKey.bytes)[0] == 0, @"must be zero-prefixed"); - NSAssert([[keychain keyAtIndex:455 hardened:YES].address.base58String isEqualToString:@"1HSr4B5Hr3hc7vAzNHbp7SV7rsFzUhQSeF"], @""); + NSAssert([[keychain keyAtIndex:455 hardened:YES].address.string isEqualToString:@"1HSr4B5Hr3hc7vAzNHbp7SV7rsFzUhQSeF"], @""); NSAssert(((uint8_t*)[keychain keyAtIndex:455 hardened:YES].privateKey.bytes)[0] == 0, @"must be zero-prefixed"); } diff --git a/CoreBitcoin/BTCMerkleTree+Tests.m b/CoreBitcoin/BTCMerkleTree+Tests.m index a03495c9..87ec7e44 100644 --- a/CoreBitcoin/BTCMerkleTree+Tests.m +++ b/CoreBitcoin/BTCMerkleTree+Tests.m @@ -20,7 +20,7 @@ + (void) runAllTests { { NSData* a = BTCDataFromHex(@"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"); BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a]]; - NSAssert([tree.merkleRoot isEqualTo:a], @"One-hash tree should have the root == that hash"); + NSAssert([tree.merkleRoot isEqual:a], @"One-hash tree should have the root == that hash"); } { @@ -28,7 +28,7 @@ + (void) runAllTests { NSData* b = BTCDataFromHex(@"0c08173828583fc6ecd6ecdbcca7b6939c49c242ad5107e39deb7b0a5996b903"); NSData* r = BTCDataFromHex(@"7de236613dd3d9fa1d86054a84952f1e0df2f130546b394a4d4dd7b76997f607"); BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a, b]]; - NSAssert([tree.merkleRoot isEqualTo:r], @"Two-hash tree should have the root == Hash(a+b)"); + NSAssert([tree.merkleRoot isEqual:r], @"Two-hash tree should have the root == Hash(a+b)"); } { @@ -37,7 +37,7 @@ + (void) runAllTests { NSData* c = BTCDataFromHex(@"80903da4e6bbdf96e8ff6fc3966b0cfd355c7e860bdd1caa8e4722d9230e40ac"); NSData* r = BTCDataFromHex(@"5b7534123197114fa7e7459075f39d89ffab74b5c3f31fad48a025b931ff5a01"); BTCMerkleTree* tree = [[BTCMerkleTree alloc] initWithHashes:@[a, b, c]]; - NSAssert([tree.merkleRoot isEqualTo:r], @"Root(a,b,c) == Hash(Hash(a+b)+Hash(c+c))"); + NSAssert([tree.merkleRoot isEqual:r], @"Root(a,b,c) == Hash(Hash(a+b)+Hash(c+c))"); } } diff --git a/CoreBitcoin/BTCScript+Tests.m b/CoreBitcoin/BTCScript+Tests.m index c2fd51e2..703a0b07 100644 --- a/CoreBitcoin/BTCScript+Tests.m +++ b/CoreBitcoin/BTCScript+Tests.m @@ -35,7 +35,7 @@ + (void) testP2SHMultisig BTCTransaction* tx = [[BTCTransaction alloc] initWithData:BTCDataFromHex(@"0100000002e7131826715b36b47b149177b0f2f3169af74b9188d3d02433d7f3b5e6c796a701000000fdfd0000473044022032e7b327ccf5e7f19029134c50d881daa178a1233d09ac9e6e93081e8f33efaf02202e2bf8b57d1c34554f65fac9c6df4986d31b3f6a7bee6cbab9a3ed835e3f57c301483045022100a355f5cde0b7643a1cbb813df4b29ddca13ddd7ee3685e77b1972179832bbd9a0220391bb9661fdab9f38bcce2abaebde39f3b5874b65758b61e1961c64f8b74d288014c6952210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7121026a361b855808aeba02d3143b3ec884f709b24d5391c515bd4eafd69d1afae337210355e9d91d63acb15a75c1a9205fc4c0a0878778e08e0a9ca22adb0c2c33fa880153aeffffffff12780cf6595ce7d34ca2e2c104dad5a2ea8709348a280cefc2246bdbd0bf142a01000000fdfd0000483045022100a6967dcd995712007a647d5466131ebc2f5cd3f46c7b314ccf428ea4e46684c502202716cf49125a67627dc2837b747898b38e8c4f58abb13cd3c1c362f0f4094ff301473044022056fc5265f4508e1baf4d837894d5e6e3df8925c68c1f2f8ca83476b73fabd64202200ad5c9928db2d7096a3d19ac2d6fc9eab3db69cd00b9dbcb923bb2e709c5b64f014c6952210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7121026a361b855808aeba02d3143b3ec884f709b24d5391c515bd4eafd69d1afae337210355e9d91d63acb15a75c1a9205fc4c0a0878778e08e0a9ca22adb0c2c33fa880153aeffffffff03e80300000000000017a914df91b0c30b7d6ec20c50e066c07add242dcfcc1d87e80300000000000017a914df91b0c30b7d6ec20c50e066c07add242dcfcc1d87c60700000000000017a914df91b0c30b7d6ec20c50e066c07add242dcfcc1d8700000000")]; // BTCScript* redeemScript = [[BTCScript alloc] initWithData:BTCDataFromHex(@"52210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c7121026a361b855808aeba02d3143b3ec884f709b24d5391c515bd4eafd69d1afae337210355e9d91d63acb15a75c1a9205fc4c0a0878778e08e0a9ca22adb0c2c33fa880153ae")]; - BTCScript* outputScript = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithBase58String:@"2NDdMCpA9to3ayTkXJQ3DvfKuSxjyRtFG5S"]]; + BTCScript* outputScript = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithString:@"2NDdMCpA9to3ayTkXJQ3DvfKuSxjyRtFG5S"]]; // NSLog(@"p2sh = %@", outputScript.string); // NSLog(@"p2sh inner = %@", redeemScript.string); @@ -230,20 +230,20 @@ + (void) testStandardScripts NSAssert(simsigData2.length == 1 + (72 + 1) + 1 + 33, @"Simulated sigscript for p2pkh with compressed pubkey option should contain signature, hashtype and a compressed pubkey"); } - NSString* base58address = [[script standardAddress] base58String]; + NSString* base58address = script.standardAddress.string; //NSLog(@"TEST: address: %@", base58address); NSAssert([base58address isEqualToString:@"1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"], @"address should be correctly decoded"); - BTCScript* script2 = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithBase58String:@"1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"]]; + BTCScript* script2 = [[BTCScript alloc] initWithAddress:[BTCAddress addressWithString:@"1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"]]; NSAssert([script2.data isEqual:script.data], @"script created from extracted address should be the same as the original script"); NSAssert([script2.string isEqual:script.string], @"script created from extracted address should be the same as the original script"); { BTCKey* key = [[BTCKey alloc] init]; - NSString *addressB58 = key.compressedPublicKeyAddress.base58String; - NSString *privKeyB58 = key.privateKeyAddress.base58String; + NSString *addressB58 = key.compressedPublicKeyAddress.string; + NSString *privKeyB58 = key.privateKeyAddress.string; //NSLog(@"Address1: %@", addressB58); //NSLog(@"PrivKey1: %@", privKeyB58); @@ -252,13 +252,13 @@ + (void) testStandardScripts if (1) // this assert fails because it creates data = <00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000> because it's cleared when address is dealloc'd. { - NSData *privkey01 = [[BTCAddress addressWithBase58String:privKeyB58] data]; + NSData *privkey01 = [[BTCAddress addressWithString:privKeyB58] data]; NSAssert([privkey01 isEqual:key.privateKey], @"private key should be the same"); } // However, if we assign intermediate object to a variable, everything works fine. Need to investigate. - BTCPrivateKeyAddress* pkaddr = [BTCPrivateKeyAddress addressWithBase58String:privKeyB58]; + BTCPrivateKeyAddress* pkaddr = [BTCPrivateKeyAddress addressWithString:privKeyB58]; NSData *privkey = pkaddr.data; NSAssert([privkey isEqual:key.privateKey], @"private key should be the same"); @@ -270,7 +270,7 @@ + (void) testStandardScripts //NSLog(@"Address2: %@", pubkeyAddress.base58String); //NSLog(@"PrivKey2: %@", privkeyAddress.base58String); - NSString *address2 = key2.compressedPublicKeyAddress.base58String; + NSString *address2 = key2.compressedPublicKeyAddress.string; //NSLog(@"Address1 %@ Equal Address2", [addressB58 isEqualToString:address2] ? @"is": @"is NOT"); NSAssert([addressB58 isEqualToString:address2], @"addresses must be equal"); } diff --git a/CoreBitcoin/BTCTransaction+Tests.m b/CoreBitcoin/BTCTransaction+Tests.m index 3215a904..883925ed 100644 --- a/CoreBitcoin/BTCTransaction+Tests.m +++ b/CoreBitcoin/BTCTransaction+Tests.m @@ -240,7 +240,7 @@ + (void) testSpendCoins:(BTCAPI)btcAPI NSLog(@"Address: %@", key.compressedPublicKeyAddress); - if (![@"1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo" isEqualToString:key.compressedPublicKeyAddress.base58String]) + if (![@"1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo" isEqualToString:key.compressedPublicKeyAddress.string]) { NSLog(@"WARNING: incorrect private key is supplied"); return; @@ -248,7 +248,7 @@ + (void) testSpendCoins:(BTCAPI)btcAPI NSError* error = nil; BTCTransaction* transaction = [self transactionSpendingFromPrivateKey:privateKey - to:[BTCPublicKeyAddress addressWithBase58String:@"1A3tnautz38PZL15YWfxTeh8MtuMDhEPVB"] + to:[BTCPublicKeyAddress addressWithString:@"1A3tnautz38PZL15YWfxTeh8MtuMDhEPVB"] change:key.compressedPublicKeyAddress // send change to the same address amount:100000 fee:0 From ea91242fbb1524376e560ad23d817b1c3c55df3d Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Fri, 24 Apr 2015 10:21:11 +0200 Subject: [PATCH 045/163] api improvements to encrypted backup --- CoreBitcoin/BTCEncryptedBackup.h | 2 ++ CoreBitcoin/BTCEncryptedBackup.m | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CoreBitcoin/BTCEncryptedBackup.h b/CoreBitcoin/BTCEncryptedBackup.h index eafab6f1..bfaacedd 100644 --- a/CoreBitcoin/BTCEncryptedBackup.h +++ b/CoreBitcoin/BTCEncryptedBackup.h @@ -31,6 +31,8 @@ typedef NS_ENUM(unsigned char, BTCEncryptedBackupVersion) { + (instancetype) decrypt:(NSData*)data backupKey:(NSData*)backupKey; + (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKey; ++ (BTCKey*) authenticationKeyWithBackupKey:(NSData*)backupKey; ++ (NSString*) walletIDWithAuthenticationKey:(NSData*)authPubkey; // For testing/audit purposes only: diff --git a/CoreBitcoin/BTCEncryptedBackup.m b/CoreBitcoin/BTCEncryptedBackup.m index ea6ea67a..4763514f 100644 --- a/CoreBitcoin/BTCEncryptedBackup.m +++ b/CoreBitcoin/BTCEncryptedBackup.m @@ -82,6 +82,22 @@ + (NSData*) backupKeyForNetwork:(BTCNetwork*)network masterKey:(NSData*)masterKe } } ++ (BTCKey*) authenticationKeyWithBackupKey:(NSData*)backupKey { + BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCHMACSHA256(backupKey, [@"Authentication Key" dataUsingEncoding:NSUTF8StringEncoding])]; + key.publicKeyCompressed = YES; + return key; +} + ++ (NSString*) walletIDWithAuthenticationKey:(NSData*)authPubkey { + // WalletID = Base58Check(0x49 || RIPEMD-160(SHA-256(APub))) + NSMutableData* data = [NSMutableData data]; + uint8_t v = 0x49; + [data appendBytes:&v length:1]; + [data appendData:BTCHash160(authPubkey)]; + return BTCBase58CheckStringWithData(data); +} + + - (NSData*) encryptionKey { return [BTCHMACSHA256(self.backupKey, [@"Encryption Key" dataUsingEncoding:NSUTF8StringEncoding]) subdataWithRange:NSMakeRange(0, 16)]; } @@ -105,18 +121,11 @@ - (NSData*) dataForSigning { } - (BTCKey*) authenticationKey { - BTCKey* key = [[BTCKey alloc] initWithPrivateKey:BTCHMACSHA256(self.backupKey, [@"Authentication Key" dataUsingEncoding:NSUTF8StringEncoding])]; - key.publicKeyCompressed = YES; - return key; + return [[self class] authenticationKeyWithBackupKey:self.backupKey]; } - (NSString*) walletID { - // WalletID = Base58Check(0x49 || RIPEMD-160(SHA-256(APub))) - NSMutableData* data = [NSMutableData data]; - uint8_t v = 0x49; - [data appendBytes:&v length:1]; - [data appendData:BTCHash160([self authenticationKey].publicKey)]; - return BTCBase58CheckStringWithData(data); + return [[self class] walletIDWithAuthenticationKey:self.authenticationKey.publicKey]; } - (NSData*) signature { From 638c13117f3f89871811c307dd201771a7f1af82 Mon Sep 17 00:00:00 2001 From: Andrew Ogden Date: Mon, 4 May 2015 15:03:56 -0500 Subject: [PATCH 046/163] BTCCurrencyConverter average calculation fix and unit testing --- CoreBitcoin.xcodeproj/project.pbxproj | 6 ++ CoreBitcoin/BTCCurrencyConverter+Tests.h | 17 ++++++ CoreBitcoin/BTCCurrencyConverter+Tests.m | 77 ++++++++++++++++++++++++ CoreBitcoin/BTCCurrencyConverter.m | 4 +- UnitTests/main.m | 2 + 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 CoreBitcoin/BTCCurrencyConverter+Tests.h create mode 100644 CoreBitcoin/BTCCurrencyConverter+Tests.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ef7c3d10..4aeb3095 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 02DDB9B51AF7D70F00687183 /* BTCCurrencyConverter+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 02DDB9B41AF7D70F00687183 /* BTCCurrencyConverter+Tests.m */; }; 200756DA1A5D6A44009A24A9 /* BTCPriceSource+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 200756D91A5D6A44009A24A9 /* BTCPriceSource+Tests.m */; }; 200AAE3F19FA3EFA0004F908 /* BTCOutpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 200AAE3E19FA3EFA0004F908 /* BTCOutpoint.m */; }; 200AAE4019FA3F010004F908 /* BTCOutpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 200AAE3E19FA3EFA0004F908 /* BTCOutpoint.m */; }; @@ -430,6 +431,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 02DDB9B31AF7D69600687183 /* BTCCurrencyConverter+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BTCCurrencyConverter+Tests.h"; sourceTree = ""; }; + 02DDB9B41AF7D70F00687183 /* BTCCurrencyConverter+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCCurrencyConverter+Tests.m"; sourceTree = ""; }; 200756D81A5D6A44009A24A9 /* BTCPriceSource+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCPriceSource+Tests.h"; sourceTree = ""; }; 200756D91A5D6A44009A24A9 /* BTCPriceSource+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCPriceSource+Tests.m"; sourceTree = ""; }; 200AAE3D19FA3EFA0004F908 /* BTCOutpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCOutpoint.h; sourceTree = ""; }; @@ -935,6 +938,8 @@ 2091948C1ACD318C00E67CB5 /* BTCBitcoinURL+Tests.m */, 207647021A0A8C15000F00F2 /* BTCCurrencyConverter.h */, 207647031A0A8C15000F00F2 /* BTCCurrencyConverter.m */, + 02DDB9B31AF7D69600687183 /* BTCCurrencyConverter+Tests.h */, + 02DDB9B41AF7D70F00687183 /* BTCCurrencyConverter+Tests.m */, 207646F81A0A8BB3000F00F2 /* BTCNumberFormatter.h */, 207646F91A0A8BB3000F00F2 /* BTCNumberFormatter.m */, 2076470C1A0A8D16000F00F2 /* BTCQRCode.h */, @@ -1478,6 +1483,7 @@ 2084DD6117B8FF47005AC9E6 /* main.m in Sources */, 20C4860A1955A88C0061DF75 /* BTCFancyEncryptedMessage+Tests.m in Sources */, 20B8AB98189EE88300008138 /* BTCKeychain.m in Sources */, + 02DDB9B51AF7D70F00687183 /* BTCCurrencyConverter+Tests.m in Sources */, 2084DD8617B8FF76005AC9E6 /* BTCAddress.m in Sources */, 2037AB1517D3BFF900DB248C /* BTCScript+Tests.m in Sources */, 20B8AB9F189F0CEF00008138 /* BTCKeychain+Tests.m in Sources */, diff --git a/CoreBitcoin/BTCCurrencyConverter+Tests.h b/CoreBitcoin/BTCCurrencyConverter+Tests.h new file mode 100644 index 00000000..147f6506 --- /dev/null +++ b/CoreBitcoin/BTCCurrencyConverter+Tests.h @@ -0,0 +1,17 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +// +// BTCCurrencyConverter+Tests.h +// CoreBitcoin +// +// Created by Andrew Ogden on 5/4/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCCurrencyConverter.h" + +@interface BTCCurrencyConverter (Tests) + ++ (void) runAllTests; + +@end \ No newline at end of file diff --git a/CoreBitcoin/BTCCurrencyConverter+Tests.m b/CoreBitcoin/BTCCurrencyConverter+Tests.m new file mode 100644 index 00000000..48aa623d --- /dev/null +++ b/CoreBitcoin/BTCCurrencyConverter+Tests.m @@ -0,0 +1,77 @@ +// +// BTCCurrencyConverter+Tests.m +// CoreBitcoin +// +// Created by Andrew Ogden on 5/4/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +#import "BTCCurrencyConverter+Tests.h" + + +@implementation BTCCurrencyConverter (Tests) + ++ (void) runAllTests +{ + [self testRateUpdates]; + [self testAsksAndBids]; + [self testFiatConversions]; +} + ++ (void) testRateUpdates +{ + BTCCurrencyConverter* converter = [[BTCCurrencyConverter alloc] init]; + [converter setBuyRate:[NSDecimalNumber decimalNumberWithString:@"210.0"]]; + [converter setSellRate:[NSDecimalNumber decimalNumberWithString:@"200.0"]]; + + NSAssert(converter.averageRate.doubleValue == 205.0, @"Average should be from buy and sell rates"); + + [converter setAverageRate:[NSDecimalNumber decimalNumberWithString:@"300.0"]]; + + NSAssert(converter.sellRate.doubleValue == 300.0, @"Setting average should reassign sell rate"); + NSAssert(converter.buyRate.doubleValue == 300.0, @"Setting average should reassign buy rate"); +} + ++ (void) testAsksAndBids +{ + BTCCurrencyConverter* converter = [[BTCCurrencyConverter alloc] init]; + [converter setBuyRate:[NSDecimalNumber decimalNumberWithString:@"210.0"]]; + [converter setSellRate:[NSDecimalNumber decimalNumberWithString:@"200.0"]]; + + NSArray* asks = @[@[[NSNumber numberWithDouble:209.0],[NSNumber numberWithDouble:1.0]]]; + + [converter setAsks:asks]; + NSAssert(converter.asks,@"Should be valid ask array"); +// NSAssert([converter.buyRate isEqualTo:[NSDecimalNumber decimalNumberWithString:@"209.0"]], @"Buy rate should equal minimum of asks array"); + + NSArray* bids = @[@[[NSNumber numberWithDouble:201.0],[NSNumber numberWithDouble:1.0]]]; + + [converter setBids:bids]; + NSAssert(converter.bids,@"Should be valid bids array"); +// NSAssert([converter.sellRate isEqualTo:[NSDecimalNumber decimalNumberWithString:@"201.0"]], @"Sell rate should equal maximum of bids array"); +} + ++ (void) testFiatConversions +{ + BTCCurrencyConverter* converter = [[BTCCurrencyConverter alloc] init]; + [converter setBuyRate:[NSDecimalNumber decimalNumberWithString:@"205.0"]]; + [converter setSellRate:[NSDecimalNumber decimalNumberWithString:@"195.0"]]; + + converter.mode = BTCCurrencyConverterModeAverage; + BTCAmount averageBTCAmout = [converter bitcoinFromFiat:[NSDecimalNumber decimalNumberWithString:@"10.0"]]; + + NSAssert(averageBTCAmout == 5000000, @"10.0 fiat with average BTC price of 200 should buy 5 million satoshis"); + + converter.mode = BTCCurrencyConverterModeBuy; + BTCAmount buyBTCAmount = [converter bitcoinFromFiat:[NSDecimalNumber decimalNumberWithString:@"10.0"]]; + + NSAssert(buyBTCAmount == 4878049, @"10.0 fiat with buy rate of 205 should buy 4878049 satoshis"); + + converter.mode = BTCCurrencyConverterModeSell; + BTCAmount sellBTCAmount = [converter bitcoinFromFiat:[NSDecimalNumber decimalNumberWithString:@"10.0"]]; + + NSAssert(sellBTCAmount == 5128205, @"10.0 fiat with sell rate of 195 should convert to 5128205 satoshis"); +} + + +@end \ No newline at end of file diff --git a/CoreBitcoin/BTCCurrencyConverter.m b/CoreBitcoin/BTCCurrencyConverter.m index 2bdd7b9e..3016fa36 100644 --- a/CoreBitcoin/BTCCurrencyConverter.m +++ b/CoreBitcoin/BTCCurrencyConverter.m @@ -81,7 +81,7 @@ - (void) setBuyRate:(NSDecimalNumber *)buyRate { _buyRate = buyRate; _sellRate = _sellRate ?: buyRate; - _averageRate = [[_sellRate decimalNumberByAdding:_buyRate] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithMantissa:2 exponent:0 isNegative:NO]]; + _averageRate = [[_sellRate decimalNumberByAdding:_buyRate] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithMantissa:5 exponent:-1 isNegative:NO]]; _bids = nil; _asks = nil; _date = [NSDate date]; @@ -91,7 +91,7 @@ - (void) setSellRate:(NSDecimalNumber *)sellRate { _buyRate = _buyRate ?: sellRate; _sellRate = sellRate; - _averageRate = [[_sellRate decimalNumberByAdding:_buyRate] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithMantissa:2 exponent:0 isNegative:NO]]; + _averageRate = [[_sellRate decimalNumberByAdding:_buyRate] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithMantissa:5 exponent:-1 isNegative:NO]]; _bids = nil; _asks = nil; _date = [NSDate date]; diff --git a/UnitTests/main.m b/UnitTests/main.m index 8efe13fe..0d40b78b 100644 --- a/UnitTests/main.m +++ b/UnitTests/main.m @@ -20,6 +20,7 @@ #import "BTCPriceSource+Tests.h" #import "BTCMerkleTree+Tests.h" #import "BTCBitcoinURL+Tests.h" +#import "BTCCurrencyConverter+Tests.h" int main(int argc, const char * argv[]) { @@ -44,6 +45,7 @@ int main(int argc, const char * argv[]) [BTCBlockchainInfo runAllTests]; [BTCPriceSource runAllTests]; [BTCBitcoinURL runAllTests]; + [BTCCurrencyConverter runAllTests]; [BTCTransaction runAllTests]; // has some interactive features to ask for private key NSLog(@"All tests passed."); From 8eaa221f2b25b66979dd9f0cd32f7d96a56fdeb9 Mon Sep 17 00:00:00 2001 From: Michael Ford Date: Thu, 14 May 2015 14:13:02 +0800 Subject: [PATCH 047/163] Fix typo in BTCAddress.h --- CoreBitcoin/BTCAddress.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CoreBitcoin/BTCAddress.h b/CoreBitcoin/BTCAddress.h index 7df18d8b..ad9e265e 100644 --- a/CoreBitcoin/BTCAddress.h +++ b/CoreBitcoin/BTCAddress.h @@ -59,7 +59,7 @@ @end -// Standard pulic key address (19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ) +// Standard public key address (19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ) @interface BTCPublicKeyAddress : BTCAddress @end @interface BTCPublicKeyAddressTestnet : BTCPublicKeyAddress From 072d89c0de1f1a19df148999e24fe7818f034a16 Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Wed, 13 May 2015 16:58:37 -0400 Subject: [PATCH 048/163] Added test target, bridging header, sample test --- CoreBitcoin.xcodeproj/project.pbxproj | 165 ++++++++++++++++++ CoreBitcoin/SwiftBridgingHeader.h | 8 + CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift | 37 ++++ CoreBitcoinTestsOSX/Info.plist | 24 +++ 4 files changed, 234 insertions(+) create mode 100644 CoreBitcoin/SwiftBridgingHeader.h create mode 100644 CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift create mode 100644 CoreBitcoinTestsOSX/Info.plist diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 4aeb3095..ceb047ed 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -399,6 +399,8 @@ 20D09C6218BC016C00794209 /* BTCBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 20D09C5A18BC016B00794209 /* BTCBlock.m */; }; 20E154DA195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E154D9195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m */; }; 20E1E01217C73181003B6987 /* NSData+BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E1E01117C73181003B6987 /* NSData+BTCData.m */; }; + 757B241A1B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */; }; + 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; C9C3C171195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C172195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C173195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; @@ -409,6 +411,16 @@ C9C3C178195B535500D9F6FB /* BTCChainCom.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C3C170195B535500D9F6FB /* BTCChainCom.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 757B241C1B03F1BF0084A9DE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 20D2801A17B8FE00004A462B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 206B0112183547C200878B8D; + remoteInfo = CoreBitcoinOSX; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 20148ADD18355D7200E68E9C /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -598,6 +610,10 @@ 20E1E01117C73181003B6987 /* NSData+BTCData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+BTCData.m"; sourceTree = ""; }; 20E1E01317C735EE003B6987 /* NS+BTCBase58.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NS+BTCBase58.h"; sourceTree = ""; }; 20E1E01417C735EE003B6987 /* NS+BTCBase58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NS+BTCBase58.m"; sourceTree = ""; }; + 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreBitcoinTestsOSX.swift; sourceTree = ""; }; + 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; C9C3C16F195B535500D9F6FB /* BTCChainCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCChainCom.h; sourceTree = ""; }; C9C3C170195B535500D9F6FB /* BTCChainCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCChainCom.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -654,6 +670,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 757B24121B03F1BF0084A9DE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -796,6 +820,7 @@ 20148AE118355D7200E68E9C /* CoreBitcoinIOSlib */, 20148C051835638200E68E9C /* CoreBitcoinIOS */, 20148C9D183643AB00E68E9C /* CoreBitcoinOSXlib */, + 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */, 20D2802417B8FE00004A462B /* Frameworks */, 20D2802317B8FE00004A462B /* Products */, ); @@ -809,6 +834,7 @@ 20148ADF18355D7200E68E9C /* libCoreBitcoinIOS.a */, 20148C031835638200E68E9C /* CoreBitcoinIOS.framework */, 20148C9B183643AB00E68E9C /* libCoreBitcoinOSX.a */, + 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */, ); name = Products; sourceTree = ""; @@ -832,6 +858,7 @@ 20D2802717B8FE00004A462B /* CoreBitcoin */ = { isa = PBXGroup; children = ( + 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */, 20D09B9F18BA14F900794209 /* CoreBitcoin.h */, 20D09BA018BA14F900794209 /* CoreBitcoin+Categories.h */, 2084DD8117B8FF76005AC9E6 /* BTCUnitsAndLimits.h */, @@ -962,6 +989,23 @@ path = CoreBitcoin; sourceTree = ""; }; + 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { + isa = PBXGroup; + children = ( + 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */, + 757B24171B03F1BF0084A9DE /* Supporting Files */, + ); + path = CoreBitcoinTestsOSX; + sourceTree = ""; + }; + 757B24171B03F1BF0084A9DE /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 757B24181B03F1BF0084A9DE /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1232,6 +1276,24 @@ productReference = 2084DD5D17B8FF47005AC9E6 /* UnitTests */; productType = "com.apple.product-type.tool"; }; + 757B24141B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { + isa = PBXNativeTarget; + buildConfigurationList = 757B24201B03F1BF0084A9DE /* Build configuration list for PBXNativeTarget "CoreBitcoinTestsOSX" */; + buildPhases = ( + 757B24111B03F1BF0084A9DE /* Sources */, + 757B24121B03F1BF0084A9DE /* Frameworks */, + 757B24131B03F1BF0084A9DE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 757B241D1B03F1BF0084A9DE /* PBXTargetDependency */, + ); + name = CoreBitcoinTestsOSX; + productName = CoreBitcoinTestsOSX; + productReference = 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -1240,6 +1302,11 @@ attributes = { LastUpgradeCheck = 0460; ORGANIZATIONNAME = "Oleg Andreev"; + TargetAttributes = { + 757B24141B03F1BF0084A9DE = { + CreatedOnToolsVersion = 6.3.1; + }; + }; }; buildConfigurationList = 20D2801D17B8FE00004A462B /* Build configuration list for PBXProject "CoreBitcoin" */; compatibilityVersion = "Xcode 3.2"; @@ -1258,6 +1325,7 @@ 20148ADE18355D7200E68E9C /* CoreBitcoinIOSlib */, 20148C021835638200E68E9C /* CoreBitcoinIOS */, 20148C9A183643AB00E68E9C /* CoreBitcoinOSXlib */, + 757B24141B03F1BF0084A9DE /* CoreBitcoinTestsOSX */, ); }; /* End PBXProject section */ @@ -1279,6 +1347,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 757B24131B03F1BF0084A9DE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1543,8 +1618,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 757B24111B03F1BF0084A9DE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 757B241A1B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 757B241D1B03F1BF0084A9DE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 206B0112183547C200878B8D /* CoreBitcoinOSX */; + targetProxy = 757B241C1B03F1BF0084A9DE /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 20148C081835638200E68E9C /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -1845,6 +1936,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.8; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = CoreBitcoin/SwiftBridgingHeader.h; }; name = Debug; }; @@ -1877,6 +1969,71 @@ LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/openssl/lib\""; MACOSX_DEPLOYMENT_TARGET = 10.8; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = CoreBitcoin/SwiftBridgingHeader.h; + }; + name = Release; + }; + 757B241E1B03F1BF0084A9DE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = CoreBitcoinTestsOSX/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = CoreBitcoin/SwiftBridgingHeader.h; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 757B241F1B03F1BF0084A9DE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = CoreBitcoinTestsOSX/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = CoreBitcoin/SwiftBridgingHeader.h; }; name = Release; }; @@ -1937,6 +2094,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 757B24201B03F1BF0084A9DE /* Build configuration list for PBXNativeTarget "CoreBitcoinTestsOSX" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 757B241E1B03F1BF0084A9DE /* Debug */, + 757B241F1B03F1BF0084A9DE /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = 20D2801A17B8FE00004A462B /* Project object */; diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h new file mode 100644 index 00000000..e00d9aab --- /dev/null +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -0,0 +1,8 @@ +// +// SwiftBridgingHeader.h +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/13/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + diff --git a/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift b/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift new file mode 100644 index 00000000..fdee4f8e --- /dev/null +++ b/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift @@ -0,0 +1,37 @@ +// +// CoreBitcoinTestsOSX.swift +// CoreBitcoinTestsOSX +// +// Created by Robert S Mozayeni on 5/13/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class CoreBitcoinTestsOSX: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + XCTAssert(true, "Pass") + + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock() { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/CoreBitcoinTestsOSX/Info.plist b/CoreBitcoinTestsOSX/Info.plist new file mode 100644 index 00000000..db680ff4 --- /dev/null +++ b/CoreBitcoinTestsOSX/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.oleganza.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + From 6d6c334d74adcda5a14a1f140e8dbc6780d84bf1 Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Thu, 14 May 2015 10:21:52 -0400 Subject: [PATCH 049/163] Added 3 test files --- CoreBitcoin.xcodeproj/project.pbxproj | 17 +++- CoreBitcoin/SwiftBridgingHeader.h | 8 ++ CoreBitcoinTestsOSX/BTCAddressTests.swift | 78 ++++++++++++++++++ .../BTCBlockchainInfoTests.swift | 47 +++++++++++ CoreBitcoinTestsOSX/BTCPriceSourceTests.swift | 79 +++++++++++++++++++ CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift | 37 --------- 6 files changed, 225 insertions(+), 41 deletions(-) create mode 100644 CoreBitcoinTestsOSX/BTCAddressTests.swift create mode 100644 CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift create mode 100644 CoreBitcoinTestsOSX/BTCPriceSourceTests.swift delete mode 100644 CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ceb047ed..38a1c477 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -399,8 +399,10 @@ 20D09C6218BC016C00794209 /* BTCBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 20D09C5A18BC016B00794209 /* BTCBlock.m */; }; 20E154DA195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E154D9195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m */; }; 20E1E01217C73181003B6987 /* NSData+BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E1E01117C73181003B6987 /* NSData+BTCData.m */; }; - 757B241A1B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */; }; 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; + 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; + 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */; }; + 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */; }; C9C3C171195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C172195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C173195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; @@ -612,8 +614,10 @@ 20E1E01417C735EE003B6987 /* NS+BTCBase58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NS+BTCBase58.m"; sourceTree = ""; }; 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreBitcoinTestsOSX.swift; sourceTree = ""; }; 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; + 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCAddressTests.swift; sourceTree = ""; }; + 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBlockchainInfoTests.swift; sourceTree = ""; }; + 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCPriceSourceTests.swift; sourceTree = ""; }; C9C3C16F195B535500D9F6FB /* BTCChainCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCChainCom.h; sourceTree = ""; }; C9C3C170195B535500D9F6FB /* BTCChainCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCChainCom.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -992,7 +996,9 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( - 757B24191B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift */, + 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */, + 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */, + 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */, 757B24171B03F1BF0084A9DE /* Supporting Files */, ); path = CoreBitcoinTestsOSX; @@ -1622,7 +1628,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 757B241A1B03F1BF0084A9DE /* CoreBitcoinTestsOSX.swift in Sources */, + 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */, + 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */, + 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2101,6 +2109,7 @@ 757B241F1B03F1BF0084A9DE /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index e00d9aab..d8412932 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -6,3 +6,11 @@ // Copyright (c) 2015 Oleg Andreev. All rights reserved. // +#import "BTCBlockchainInfo.h" +#import "BTCAddress.h" +#import "BTCTransactionOutput.h" +#import "BTCPriceSource.h" +#import "BTCAddress.h" +#import "NSData+BTCData.h" +#import "NS+BTCBase58.h" +#import "BTCKey.h" diff --git a/CoreBitcoinTestsOSX/BTCAddressTests.swift b/CoreBitcoinTestsOSX/BTCAddressTests.swift new file mode 100644 index 00000000..e4ff93cf --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCAddressTests.swift @@ -0,0 +1,78 @@ +// +// BTCAddressTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 4/20/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCAddressTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testPublicKeyAddress() { + let addr = BTCPublicKeyAddress(string: "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T") + XCTAssertNotNil(addr, "Address should be decoded") + XCTAssert(addr!.dynamicType === BTCPublicKeyAddress.self, "Address should be an instance of BTCPublicKeyAddress") + XCTAssertEqual("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827", addr!.data.hex(), "Must decode hash160 correctly.") + XCTAssertEqual(addr!, addr!.publicAddress, "Address should be equal to its publicAddress") + + let addr2 = BTCPublicKeyAddress(data: BTCDataFromHex("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827")) + + XCTAssertNotNil(addr2, "Address should be created") + XCTAssertEqual("1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", addr2.string, "Must encode hash160 correctly.") + + } + + func testPrivateKeyAddress() { + let addr = BTCPrivateKeyAddress(string: "5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS") + XCTAssertNotNil(addr, "Address should be decoded") + XCTAssert(addr!.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") + XCTAssert(!addr.publicKeyCompressed, "Address should be not compressed") + XCTAssertEqual("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", addr.data.hex(), "must provide proper public address") + + let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")) + XCTAssertNotNil(addr2, "Address should be created") + XCTAssertEqual("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", addr2.string, "Must encode secret key correctly.") + } + + func testPrivateKeyAddressWithCompressedPoint() { + let addr = BTCPrivateKeyAddress(string: "L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu") + XCTAssertNotNil(addr, "Address should be decoded") + XCTAssert(addr!.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") + XCTAssert(addr.publicKeyCompressed, "address should be compressed") + XCTAssertEqual("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", addr.data.hex(), "Must decode secret key correctly.") + XCTAssertEqual(addr.publicAddress.string, "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8", "must provide proper public address") + + let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")) + XCTAssertNotNil(addr2, "Address should be created") + addr2.publicKeyCompressed = true + XCTAssertEqual("L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu", addr2.string, "Must encode secret key correctly.") + addr2.publicKeyCompressed = false + XCTAssertEqual("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", addr2.string, "Must encode secret key correctly.") + } + + func testScriptHashKeyAddress() { + let addr = BTCScriptHashAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8") + XCTAssertNotNil(addr, "Address should be decoded") + XCTAssert(addr!.dynamicType === BTCScriptHashAddress.self, "Address should be an instance of BTCScriptHashAddress") + XCTAssertEqual("e8c300c87986efa84c37c0519929019ef86eb5b4", addr.data.hex(), "Must decode hash160 correctly.") + XCTAssertEqual(addr, addr.publicAddress, "Address should be equal to its publicAddress") + + let addr2 = BTCScriptHashAddress(data: BTCDataFromHex("e8c300c87986efa84c37c0519929019ef86eb5b4")) + XCTAssertNotNil(addr2, "Address should be created") + XCTAssertEqual("3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", addr2.string, "Must encode hash160 correctly.") + } + +} diff --git a/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift b/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift new file mode 100644 index 00000000..b5c72824 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift @@ -0,0 +1,47 @@ +// +// BTCBlockchainInfoTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 4/20/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCBlockchainInfoTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testEmptyUnspents() { + + var error: NSError? + + let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1LKF45kfvHAaP7C4cF91pVb3bkAsmQ8nBr")], error: &error) + + XCTAssert(outputs.count == 0, "should return an empty array") + XCTAssert(error == nil, "should have no error") + + } + + func testNonEmptyUnspents() { + + var error: NSError? + + let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")], error: &error) + + XCTAssert(outputs.count > 0, "should return a non-empty array") + XCTAssert((outputs.first as? BTCTransactionOutput) != nil, "should contain BTCTransactionOutput objects") + XCTAssert(error == nil, "should have no error") + } + + +} diff --git a/CoreBitcoinTestsOSX/BTCPriceSourceTests.swift b/CoreBitcoinTestsOSX/BTCPriceSourceTests.swift new file mode 100644 index 00000000..d68436d5 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCPriceSourceTests.swift @@ -0,0 +1,79 @@ +// +// BTCPriceSourceTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 4/20/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCPriceSourceTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testCoindesk() { + let coindesk = BTCPriceSourceCoindesk() + XCTAssert(coindesk.name.lowercaseString.rangeOfString("coindesk") != nil, "should be named coindesk") + let codes = coindesk.currencyCodes as! [String] + XCTAssert(contains(codes, "USD"), "should contain USD") + XCTAssert(contains(codes, "EUR"), "should contain EUR") + + validatePrice(coindesk.loadPriceForCurrency("USD", error: nil), min: 100, max: 10000) + validatePrice(coindesk.loadPriceForCurrency("EUR", error: nil), min: 100, max: 10000) + } + + func testWinkdex() { + let winkdex = BTCPriceSourceWinkdex() + XCTAssert(winkdex.name.lowercaseString.rangeOfString("wink") != nil, "should be named properly") + + let codes = winkdex.currencyCodes as! [String] + + XCTAssert(contains(codes, "USD"), "should contain USD") + + validatePrice(winkdex.loadPriceForCurrency("USD", error: nil), min: 100, max: 10000) + } + + func testCoinbase() { + let coinbase = BTCPriceSourceCoinbase() + XCTAssert(coinbase.name.lowercaseString.rangeOfString("coinbase") != nil, "should be named properly") + let codes = coinbase.currencyCodes as! [String] + XCTAssert(contains(codes, "USD"), "should contain USD") + } + + func testPaymium() { + let paymium = BTCPriceSourcePaymium() + XCTAssert(paymium.name.lowercaseString.rangeOfString("paymium") != nil, "should be named properly") + + let codes = paymium.currencyCodes as! [String] + + XCTAssert(contains(codes, "EUR"), "should contain EUR") + validatePrice(paymium.loadPriceForCurrency("EUR", error: nil), min: 100, max: 10000) + + } + + + func validatePrice(result: BTCPriceSourceResult?, min: Double, max: Double) { + + XCTAssert(result != nil, "result should not be nil") + + let number = result!.averageRate + // println("price = \(number) \(result.currencyCode)") + + XCTAssert(result!.date != nil , "date should not be nil") + XCTAssert(number != nil, "averageRate should not be nil") + XCTAssert(number.doubleValue >= min, "Must be over minimum value") + XCTAssert(number.doubleValue <= max, "Must be under max value") + } + + +} diff --git a/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift b/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift deleted file mode 100644 index fdee4f8e..00000000 --- a/CoreBitcoinTestsOSX/CoreBitcoinTestsOSX.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// CoreBitcoinTestsOSX.swift -// CoreBitcoinTestsOSX -// -// Created by Robert S Mozayeni on 5/13/15. -// Copyright (c) 2015 Oleg Andreev. All rights reserved. -// - -import Cocoa -import XCTest - -class CoreBitcoinTestsOSX: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - XCTAssert(true, "Pass") - - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measureBlock() { - // Put the code you want to measure the time of here. - } - } - -} From 0edfee929fb1a4130f6ff20e2782a031d7b29b4b Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sat, 16 May 2015 14:33:52 +0200 Subject: [PATCH 050/163] Ignore openssl.bak --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 20548d9b..e38c28bc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ DerivedData .svn CVS CoreBitcoin.xcodeproj/project.xcworkspace/xcshareddata/ +openssl.bak From 936e7ff0027902da94292aaf418ef3930a22d519 Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Sat, 16 May 2015 18:44:00 -0400 Subject: [PATCH 051/163] Added BTCBitcoinURLTests.swift --- CoreBitcoin.xcodeproj/project.pbxproj | 4 + CoreBitcoin/SwiftBridgingHeader.h | 1 + CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift | 79 ++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 38a1c477..2a428788 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -399,6 +399,7 @@ 20D09C6218BC016C00794209 /* BTCBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = 20D09C5A18BC016B00794209 /* BTCBlock.m */; }; 20E154DA195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E154D9195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m */; }; 20E1E01217C73181003B6987 /* NSData+BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E1E01117C73181003B6987 /* NSData+BTCData.m */; }; + 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */; }; 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */; }; @@ -612,6 +613,7 @@ 20E1E01117C73181003B6987 /* NSData+BTCData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+BTCData.m"; sourceTree = ""; }; 20E1E01317C735EE003B6987 /* NS+BTCBase58.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NS+BTCBase58.h"; sourceTree = ""; }; 20E1E01417C735EE003B6987 /* NS+BTCBase58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NS+BTCBase58.m"; sourceTree = ""; }; + 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBitcoinURLTests.swift; sourceTree = ""; }; 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; @@ -996,6 +998,7 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( + 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */, 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */, 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */, 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */, @@ -1630,6 +1633,7 @@ files = ( 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */, 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */, + 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */, 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index d8412932..cac8b28a 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -14,3 +14,4 @@ #import "NSData+BTCData.h" #import "NS+BTCBase58.h" #import "BTCKey.h" +#import "BTCBitcoinURL.h" \ No newline at end of file diff --git a/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift b/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift new file mode 100644 index 00000000..efd38956 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift @@ -0,0 +1,79 @@ +// +// BTCBitcoinURLTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/16/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCBitcoinURLTests: XCTestCase { + + func testExample() { + // This is an example of a functional test case. + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T?amount=1.23450009&label=Hello%20world")) + XCTAssertNotNil(bURL, "Must parse") + XCTAssertTrue(bURL.isValid, "Must be valid") + XCTAssertEqual(bURL.amount, 123450009, "Must parse amount formatted as btc") + XCTAssertEqual(bURL.address.string, "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", "Must parse address") + XCTAssertNil(bURL.paymentRequestURL, "Must parse payment request") + XCTAssertEqual(bURL.label, "Hello world", "Must parse label") + XCTAssertEqual(bURL.queryParameters["label"] as! String, "Hello world", "Must provide raw query items access") + XCTAssertEqual(bURL.queryParameters["amount"] as! String, "1.23450009", "Must provide raw query items access") + XCTAssertEqual(bURL["label"] as! String, "Hello world", "Must provide raw query items access") + XCTAssertEqual(bURL["amount"] as! String, "1.23450009", "Must provide raw query items access") + } + + func testCompatiblePaymentRequest() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T?amount=1.23450009&r=http://example.com/order-1000123")) + XCTAssertNotNil(bURL, "Must parse") + XCTAssertTrue(bURL.isValid, "Must be valid") + XCTAssertEqual(bURL.amount, 123450009, "Must parse amount formatted as btc") + XCTAssertEqual(bURL.address.string, "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", "Must parse address") + XCTAssertEqual(bURL.paymentRequestURL.absoluteString!, "http://example.com/order-1000123", "Must parse payment request") + + } + + func testNakedPaymentRequest() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:?r=http://example.com/order-1000123")) + XCTAssertNotNil(bURL, "Must parse") + XCTAssertTrue(bURL.isValid, "Must be valid") + XCTAssertEqual(bURL.amount, 0, "Default amount is zero") + XCTAssertNil(bURL.address, "Default address is nill") + XCTAssertEqual(bURL.paymentRequestURL.absoluteString!, "http://example.com/order-1000123", "Must parse payment request") + + } + + func testInvalidURL1() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:?x=something")) + XCTAssertNotNil(bURL, "Must parse") + XCTAssertFalse(bURL.isValid, "Must not be valid") + XCTAssertEqual(bURL.amount, 0, "Default amount is zero") + XCTAssertNil(bURL.address, "Default address is nil") + XCTAssertNil(bURL.paymentRequestURL, "Must have nil payment request") + XCTAssertEqual(bURL["x"] as! String, "something", "Must have query item") + } + + func testInvalidURL2() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:?amount=1.2")) + XCTAssertNotNil(bURL, "Must parse") + XCTAssertFalse(bURL.isValid, "Must not be valid") + XCTAssertEqual(bURL.amount, 120000000, "Must parse amount") + XCTAssertNil(bURL.address, "Default address is nil") + XCTAssertNil(bURL.paymentRequestURL, "Must have nil payment request") + XCTAssertEqual(bURL["amount"] as! String, "1.2", "Must have query item") + } + + func testMalformedURL1() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "bitcoin:xxxx")) + XCTAssertNil(bURL, "Must not parse broken address") + } + + func testMalformedURL2() { + let bURL = BTCBitcoinURL(URL: NSURL(string: "http://example.com")) + XCTAssertNil(bURL, "Must not parse other schemas than bitcoin:") + } + +} From cabd59c5172f0fb5792103280003b2d6c23ddf77 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Sun, 17 May 2015 08:58:39 +0200 Subject: [PATCH 052/163] Fixed typos --- CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift b/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift index efd38956..ff2df6eb 100644 --- a/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift +++ b/CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift @@ -33,7 +33,6 @@ class BTCBitcoinURLTests: XCTestCase { XCTAssertEqual(bURL.amount, 123450009, "Must parse amount formatted as btc") XCTAssertEqual(bURL.address.string, "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", "Must parse address") XCTAssertEqual(bURL.paymentRequestURL.absoluteString!, "http://example.com/order-1000123", "Must parse payment request") - } func testNakedPaymentRequest() { @@ -41,9 +40,8 @@ class BTCBitcoinURLTests: XCTestCase { XCTAssertNotNil(bURL, "Must parse") XCTAssertTrue(bURL.isValid, "Must be valid") XCTAssertEqual(bURL.amount, 0, "Default amount is zero") - XCTAssertNil(bURL.address, "Default address is nill") + XCTAssertNil(bURL.address, "Default address is nil") XCTAssertEqual(bURL.paymentRequestURL.absoluteString!, "http://example.com/order-1000123", "Must parse payment request") - } func testInvalidURL1() { From e6ed5f177b7ba14e06641ab1879e0bf141e58be9 Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Tue, 19 May 2015 14:20:58 -0400 Subject: [PATCH 053/163] Added BTCCurrencyConverterTests.swift --- CoreBitcoin.xcodeproj/project.pbxproj | 4 ++ CoreBitcoin/SwiftBridgingHeader.h | 3 +- .../BTCCurrencyConverterTests.swift | 72 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 CoreBitcoinTestsOSX/BTCCurrencyConverterTests.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 2a428788..3820fb2c 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -400,6 +400,7 @@ 20E154DA195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E154D9195D0DB400DA40D3 /* BTCBlockchainInfo+Tests.m */; }; 20E1E01217C73181003B6987 /* NSData+BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E1E01117C73181003B6987 /* NSData+BTCData.m */; }; 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */; }; + 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */; }; 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */; }; @@ -614,6 +615,7 @@ 20E1E01317C735EE003B6987 /* NS+BTCBase58.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NS+BTCBase58.h"; sourceTree = ""; }; 20E1E01417C735EE003B6987 /* NS+BTCBase58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NS+BTCBase58.m"; sourceTree = ""; }; 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBitcoinURLTests.swift; sourceTree = ""; }; + 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCCurrencyConverterTests.swift; sourceTree = ""; }; 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; @@ -998,6 +1000,7 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( + 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */, 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */, 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */, 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */, @@ -1632,6 +1635,7 @@ buildActionMask = 2147483647; files = ( 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */, + 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */, 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */, 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */, 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */, diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index cac8b28a..81336b72 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -14,4 +14,5 @@ #import "NSData+BTCData.h" #import "NS+BTCBase58.h" #import "BTCKey.h" -#import "BTCBitcoinURL.h" \ No newline at end of file +#import "BTCBitcoinURL.h" +#import "BTCCurrencyConverter.h" \ No newline at end of file diff --git a/CoreBitcoinTestsOSX/BTCCurrencyConverterTests.swift b/CoreBitcoinTestsOSX/BTCCurrencyConverterTests.swift new file mode 100644 index 00000000..058023fb --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCCurrencyConverterTests.swift @@ -0,0 +1,72 @@ +// +// BTCCurrencyConverterTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/19/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCCurrencyConverterTests: XCTestCase { + + func testRateUpdates() { + let converter = BTCCurrencyConverter() + converter.buyRate = NSDecimalNumber(string: "210.0") + converter.sellRate = NSDecimalNumber(string: "200.0") + + XCTAssertEqual(converter.averageRate.doubleValue, 205.0, "Average should be from buy and sell rates") + + converter.averageRate = NSDecimalNumber(string: "300.0") + + XCTAssertEqual(converter.sellRate.doubleValue, 300, "Setting average should reassign sell rate") + XCTAssertEqual(converter.buyRate.doubleValue, 300, "Setting average should reassign buy rate") + + } + + func testAsksAndBids() { + let converter = BTCCurrencyConverter() + converter.buyRate = NSDecimalNumber(string: "210.0") + converter.sellRate = NSDecimalNumber(string: "200.0") + + let asks = [[NSNumber(double: 209.0), NSNumber(double: 1.0)]] + + converter.asks = asks + XCTAssertNotNil(converter.asks, "Should be valid ask array") + + let bids = [[NSNumber(double: 201.0), NSNumber(double: 1.0)]] + + converter.bids = bids + XCTAssertNotNil(converter.bids, "Should be valid bids array") + + + } + + func testFiatConversions() { + let converter = BTCCurrencyConverter() + converter.buyRate = NSDecimalNumber(string: "205.0") + converter.sellRate = NSDecimalNumber(string: "195.0") + + + + converter.mode = BTCCurrencyConverterMode.Average + let averageBTCAmount = converter.bitcoinFromFiat(NSDecimalNumber(string: "10.0")) + + XCTAssertEqual(averageBTCAmount, 5000000, "10.0 fiat with average BTC price of 200 should buy 5 million satoshis") + + + + converter.mode = BTCCurrencyConverterMode.Buy + let buyBTCAmount = converter.bitcoinFromFiat(NSDecimalNumber(string: "10.0")) + + XCTAssertEqual(buyBTCAmount, 4878049, "10.0 fiat with buy rate of 205 should buy 4878049 satoshis") + + + + converter.mode = BTCCurrencyConverterMode.Sell + let sellBTCAmount = converter.bitcoinFromFiat(NSDecimalNumber(string: "10.0")) + XCTAssertEqual(sellBTCAmount, 5128205, "10.0 fiat with sell rate of 195 should convert to 5128205 satoshis") + + } +} From 7330814927a78cea65abd125909a86ec454cfceb Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 20 May 2015 15:02:30 +0200 Subject: [PATCH 054/163] added BTCAssetAddress --- CoreBitcoin.xcodeproj/project.pbxproj | 20 ++++++++ CoreBitcoin/BTCAssetAddress.h | 8 ++++ CoreBitcoin/BTCAssetAddress.m | 58 +++++++++++++++++++++++ CoreBitcoin/CoreBitcoin.h | 1 + CoreBitcoin/SwiftBridgingHeader.h | 1 + CoreBitcoinTestsOSX/BTCAddressTests.swift | 13 ++++- 6 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 CoreBitcoin/BTCAssetAddress.h create mode 100644 CoreBitcoin/BTCAssetAddress.m diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index 3820fb2c..ef392509 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -361,6 +361,14 @@ 20C2D80B19E2F2280022CAAC /* BTCMnemonic+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C2D80319E2F2280022CAAC /* BTCMnemonic+Tests.m */; }; 20C3F16917BFDCF8009DB89C /* libSystem.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 20C3F16817BFDCF8009DB89C /* libSystem.dylib */; }; 20C4860A1955A88C0061DF75 /* BTCFancyEncryptedMessage+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */; }; + 20C7D14C1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = 20C7D14A1B0CBBC900F71493 /* BTCAssetAddress.h */; }; + 20C7D14D1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = 20C7D14A1B0CBBC900F71493 /* BTCAssetAddress.h */; }; + 20C7D14E1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = 20C7D14A1B0CBBC900F71493 /* BTCAssetAddress.h */; }; + 20C7D14F1B0CBBC900F71493 /* BTCAssetAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */; }; + 20C7D1501B0CBBC900F71493 /* BTCAssetAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */; }; + 20C7D1511B0CBBC900F71493 /* BTCAssetAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */; }; + 20C7D1521B0CBBC900F71493 /* BTCAssetAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */; }; + 20C7D1531B0CBBC900F71493 /* BTCAssetAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */; }; 20CD68DA189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20CD68DB189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20CD68DC189B18820083E1A9 /* BTCCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -593,6 +601,8 @@ 20C3F16817BFDCF8009DB89C /* libSystem.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libSystem.dylib; path = usr/lib/libSystem.dylib; sourceTree = SDKROOT; }; 20C486081955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTCFancyEncryptedMessage+Tests.h"; sourceTree = ""; }; 20C486091955A88B0061DF75 /* BTCFancyEncryptedMessage+Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BTCFancyEncryptedMessage+Tests.m"; sourceTree = ""; }; + 20C7D14A1B0CBBC900F71493 /* BTCAssetAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCAssetAddress.h; sourceTree = ""; }; + 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCAssetAddress.m; sourceTree = ""; }; 20CD68D8189B18820083E1A9 /* BTCCurvePoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCCurvePoint.h; sourceTree = ""; }; 20CD68D9189B18820083E1A9 /* BTCCurvePoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCCurvePoint.m; sourceTree = ""; }; 20D008C018D1AFA800079B79 /* BTC256+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BTC256+Tests.h"; sourceTree = ""; }; @@ -888,6 +898,8 @@ 2084DD6A17B8FF76005AC9E6 /* BTCAddress.m */, 2084DD6B17B8FF76005AC9E6 /* BTCAddress+Tests.h */, 2084DD6C17B8FF76005AC9E6 /* BTCAddress+Tests.m */, + 20C7D14A1B0CBBC900F71493 /* BTCAssetAddress.h */, + 20C7D14B1B0CBBC900F71493 /* BTCAssetAddress.m */, 2084DD6D17B8FF76005AC9E6 /* BTCBase58.h */, 2084DD6E17B8FF76005AC9E6 /* BTCBase58.m */, 2037AB1617D3D1F900DB248C /* BTCBase58+Tests.h */, @@ -1048,6 +1060,7 @@ 207646FB1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 20148C2F1835650B00E68E9C /* BTCAddress.h in Headers */, 20148C301835650B00E68E9C /* BTCAddress+Tests.h in Headers */, + 20C7D14D1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */, 20148C311835650B00E68E9C /* BTCBase58.h in Headers */, 20148C321835650B00E68E9C /* BTCBase58+Tests.h in Headers */, 207646E71A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, @@ -1107,6 +1120,7 @@ 207646FC1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 20148CD9183643FC00E68E9C /* BTCAddress.h in Headers */, 20148CDA183643FC00E68E9C /* BTCAddress+Tests.h in Headers */, + 20C7D14E1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */, 20148CDB183643FC00E68E9C /* BTCBase58.h in Headers */, 20148CDC183643FC00E68E9C /* BTCBase58+Tests.h in Headers */, 207646E81A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, @@ -1166,6 +1180,7 @@ 207646FA1A0A8BB3000F00F2 /* BTCNumberFormatter.h in Headers */, 206B013E1835484300878B8D /* BTCData.h in Headers */, 206B014B1835484300878B8D /* BTCProtocolSerialization.h in Headers */, + 20C7D14C1B0CBBC900F71493 /* BTCAssetAddress.h in Headers */, 206B01421835484300878B8D /* BTCAddress.h in Headers */, 206B014F1835484300878B8D /* BTCScript+Tests.h in Headers */, 207646E61A0A8A37000F00F2 /* BTCTransactionBuilder.h in Headers */, @@ -1392,6 +1407,7 @@ 20148B0D18355DAD00E68E9C /* NS+BTCBase58.m in Sources */, 208E303B1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148B0E18355DAD00E68E9C /* BTCBigNumber.m in Sources */, + 20C7D1511B0CBBC900F71493 /* BTCAssetAddress.m in Sources */, 204FB507194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EB1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, C9C3C176195B535500D9F6FB /* BTCChainCom.m in Sources */, @@ -1440,6 +1456,7 @@ 20148C1B183563D000E68E9C /* NS+BTCBase58.m in Sources */, 208E303C1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148C1C183563D000E68E9C /* BTCBigNumber.m in Sources */, + 20C7D1521B0CBBC900F71493 /* BTCAssetAddress.m in Sources */, 204FB508194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EC1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, C9C3C177195B535500D9F6FB /* BTCChainCom.m in Sources */, @@ -1488,6 +1505,7 @@ 20148CC6183643E700E68E9C /* NS+BTCBase58.m in Sources */, 208E303D1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 20148CC7183643E700E68E9C /* BTCBigNumber.m in Sources */, + 20C7D1531B0CBBC900F71493 /* BTCAssetAddress.m in Sources */, 204FB509194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646ED1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, C9C3C178195B535500D9F6FB /* BTCChainCom.m in Sources */, @@ -1536,6 +1554,7 @@ 206B015C1835485D00878B8D /* NS+BTCBase58.m in Sources */, 208E303A1AC012CE0020F830 /* BTCEncryptedBackup.m in Sources */, 206B01571835485D00878B8D /* BTCErrors.m in Sources */, + 20C7D1501B0CBBC900F71493 /* BTCAssetAddress.m in Sources */, 204FB506194C63B500C131DE /* BTCBlindSignature.m in Sources */, 207646EA1A0A8A37000F00F2 /* BTCTransactionBuilder.m in Sources */, C9C3C175195B535500D9F6FB /* BTCChainCom.m in Sources */, @@ -1584,6 +1603,7 @@ 207B2608188DC47800916AE6 /* BTCBlockchainInfo.m in Sources */, 20A443D51AC954C1008B3447 /* BTCEncryptedBackup+Tests.m in Sources */, 2084DD8D17B8FF76005AC9E6 /* BTCProtocolSerialization+Tests.m in Sources */, + 20C7D14F1B0CBBC900F71493 /* BTCAssetAddress.m in Sources */, 2060A2831AAA077A004531FD /* BTCMerkleTree.m in Sources */, 20D008C218D1AFA800079B79 /* BTC256+Tests.m in Sources */, 2054DC781950E35E007175C8 /* BTCFancyEncryptedMessage.m in Sources */, diff --git a/CoreBitcoin/BTCAssetAddress.h b/CoreBitcoin/BTCAssetAddress.h new file mode 100644 index 00000000..d9767847 --- /dev/null +++ b/CoreBitcoin/BTCAssetAddress.h @@ -0,0 +1,8 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCAddress.h" + +@interface BTCAssetAddress : BTCAddress +@property(nonatomic, readonly) BTCAddress* bitcoinAddress; ++ (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress; +@end diff --git a/CoreBitcoin/BTCAssetAddress.m b/CoreBitcoin/BTCAssetAddress.m new file mode 100644 index 00000000..b53dd290 --- /dev/null +++ b/CoreBitcoin/BTCAssetAddress.m @@ -0,0 +1,58 @@ +// CoreBitcoin by Oleg Andreev , WTFPL. + +#import "BTCAssetAddress.h" +#import "BTCData.h" +#import "BTCBase58.h" + +@interface BTCAssetAddress () +@property(nonatomic, readwrite) BTCAddress* bitcoinAddress; +@end + +// OpenAssets Address, e.g. akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy (corresponds to 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM) +@implementation BTCAssetAddress + +#define BTCAssetAddressNamespace 0x13 + ++ (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress +{ + if (!btcAddress) return nil; + BTCAssetAddress* addr = [[self alloc] init]; + addr.bitcoinAddress = btcAddress; + return addr; +} + ++ (instancetype) addressWithString:(NSString*)string +{ + NSMutableData* composedData = BTCDataFromBase58Check(string); + if (!composedData) return nil; + if (composedData.length < 2) return nil; + + int namespace = ((unsigned char*)composedData.bytes)[0]; + + if (namespace == BTCAssetAddressNamespace) { // same for testnet and mainnet + BTCAddress* btcAddr = [BTCAddress addressWithString:BTCBase58CheckStringWithData([composedData subdataWithRange:NSMakeRange(1, composedData.length - 1)])]; + return [self addressWithBitcoinAddress:btcAddr]; + } else { + return nil; + } +} + +- (NSMutableData*) dataForBase58Encoding +{ + NSMutableData* data = [NSMutableData dataWithLength:1]; + char* buf = data.mutableBytes; + buf[0] = BTCAssetAddressNamespace; + [data appendData:[(BTCAssetAddress* /* cast only to expose the method that is defined in BTCAddress anyway */)self.bitcoinAddress dataForBase58Encoding]]; + return data; +} + +- (unsigned char) versionByte +{ + return BTCAssetAddressNamespace; +} + +- (BOOL) isTestnet { + return self.bitcoinAddress.isTestnet; +} + +@end diff --git a/CoreBitcoin/CoreBitcoin.h b/CoreBitcoin/CoreBitcoin.h index 8ab4e4c5..904eb905 100644 --- a/CoreBitcoin/CoreBitcoin.h +++ b/CoreBitcoin/CoreBitcoin.h @@ -2,6 +2,7 @@ #import #import +#import #import #import #import diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index 81336b72..6f7bbf70 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -8,6 +8,7 @@ #import "BTCBlockchainInfo.h" #import "BTCAddress.h" +#import "BTCAssetAddress.h" #import "BTCTransactionOutput.h" #import "BTCPriceSource.h" #import "BTCAddress.h" diff --git a/CoreBitcoinTestsOSX/BTCAddressTests.swift b/CoreBitcoinTestsOSX/BTCAddressTests.swift index e4ff93cf..dcd2c0a9 100644 --- a/CoreBitcoinTestsOSX/BTCAddressTests.swift +++ b/CoreBitcoinTestsOSX/BTCAddressTests.swift @@ -74,5 +74,16 @@ class BTCAddressTests: XCTestCase { XCTAssertNotNil(addr2, "Address should be created") XCTAssertEqual("3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", addr2.string, "Must encode hash160 correctly.") } - + + + func testAssetAddress() { + let btcAddr = BTCPublicKeyAddress(string: "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM") + let assetAddr = BTCAssetAddress(bitcoinAddress:btcAddr) + XCTAssertNotNil(assetAddr, "Address should be created") + XCTAssertEqual("akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy", assetAddr.string, "Must encode to Open Assets format correctly.") + + let assetAddr2 = BTCAssetAddress(string:"akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy") + XCTAssertEqual("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", assetAddr2.bitcoinAddress.string, "Must decode underlying Bitcoin address from Open Assets address.") + } + } From 65a9be2f89db951381b579837505f4a114ba07b6 Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Thu, 21 May 2015 16:28:48 -0400 Subject: [PATCH 055/163] Added BTCCurvePointTests.swift --- CoreBitcoin.xcodeproj/project.pbxproj | 4 + CoreBitcoin/SwiftBridgingHeader.h | 4 +- CoreBitcoinTestsOSX/BTCCurvePointTests.swift | 78 ++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 CoreBitcoinTestsOSX/BTCCurvePointTests.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index ef392509..cd6ec0a7 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -413,6 +413,7 @@ 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */; }; 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */; }; + 75BBC6E51B0E721700A61690 /* BTCCurvePointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BBC6E41B0E721700A61690 /* BTCCurvePointTests.swift */; }; C9C3C171195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C172195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; C9C3C173195B535500D9F6FB /* BTCChainCom.h in Headers */ = {isa = PBXBuildFile; fileRef = C9C3C16F195B535500D9F6FB /* BTCChainCom.h */; }; @@ -632,6 +633,7 @@ 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCAddressTests.swift; sourceTree = ""; }; 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBlockchainInfoTests.swift; sourceTree = ""; }; 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCPriceSourceTests.swift; sourceTree = ""; }; + 75BBC6E41B0E721700A61690 /* BTCCurvePointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCCurvePointTests.swift; sourceTree = ""; }; C9C3C16F195B535500D9F6FB /* BTCChainCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTCChainCom.h; sourceTree = ""; }; C9C3C170195B535500D9F6FB /* BTCChainCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTCChainCom.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1012,6 +1014,7 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( + 75BBC6E41B0E721700A61690 /* BTCCurvePointTests.swift */, 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */, 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */, 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */, @@ -1654,6 +1657,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 75BBC6E51B0E721700A61690 /* BTCCurvePointTests.swift in Sources */, 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */, 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */, 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */, diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index 6f7bbf70..690a3d03 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -16,4 +16,6 @@ #import "NS+BTCBase58.h" #import "BTCKey.h" #import "BTCBitcoinURL.h" -#import "BTCCurrencyConverter.h" \ No newline at end of file +#import "BTCCurrencyConverter.h" +#import "BTCBigNumber.h" +#import "BTCCurvePoint.h" \ No newline at end of file diff --git a/CoreBitcoinTestsOSX/BTCCurvePointTests.swift b/CoreBitcoinTestsOSX/BTCCurvePointTests.swift new file mode 100644 index 00000000..ac9500f0 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCCurvePointTests.swift @@ -0,0 +1,78 @@ +// +// BTCCurvePointTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/21/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCCurvePointTests: XCTestCase { + + func testPublicKey() { + + // Should be able to create public key N = n*G via BTCKey API as well as raw EC arithmetic using BTCCurvePoint. + let privateKeyData = BTCHash256("Private Key Seed".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + + // 1. Make the pubkey using BTCKey API. + + let key = BTCKey(privateKey: privateKeyData) + + + // 2. Make the pubkey using BTCCurvePoint API. + + let bn = BTCBigNumber(unsignedBigEndian: privateKeyData) + + let generator = BTCCurvePoint.generator() + let pubKeyPoint = generator.copy().multiply(bn) + let keyFromPoint = BTCKey(curvePoint: pubKeyPoint) + + // 2.1. Test serialization + + XCTAssertEqual(pubKeyPoint, BTCCurvePoint(data: pubKeyPoint.data), "test serialization") + + // 3. Compare the two pubkeys. + + XCTAssertEqual(keyFromPoint, key, "pubkeys should be equal") + XCTAssertEqual(key.curvePoint, pubKeyPoint, "points should be equal") + + } + + func testDiffieHellman() { + // Alice: a, A=a*G. Bob: b, B=b*G. + // Test shared secret: a*B = b*A = (a*b)*G. + + let alicePrivateKeyData = BTCHash256("alice private key".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + let bobPrivateKeyData = BTCHash256("bob private key".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + +// println("Alice privkey: \(BTCHexFromData(alicePrivateKeyData))") +// println("Bob privkey: \(BTCHexFromData(bobPrivateKeyData))") + + let aliceNumber = BTCBigNumber(unsignedBigEndian: alicePrivateKeyData) + let bobNumber = BTCBigNumber(unsignedBigEndian: bobPrivateKeyData) + +// println("Alice number: \(aliceNumber.hexString)") +// println("Bob number: \(bobNumber.hexString)") + + let aliceKey = BTCKey(privateKey: alicePrivateKeyData) + let bobKey = BTCKey(privateKey: bobPrivateKeyData) + + XCTAssertEqual(aliceKey.privateKey, aliceNumber.unsignedBigEndian, "") + XCTAssertEqual(bobKey.privateKey, bobNumber.unsignedBigEndian, "") + + let aliceSharedSecret = bobKey.curvePoint.multiply(aliceNumber) + let bobSharedSecret = aliceKey.curvePoint.multiply(bobNumber) + +// println("(a*B).x = \(aliceSharedSecret.x.decimalString)") +// println("(b*A).x = \(bobSharedSecret.x.decimalString)") + + let sharedSecretNumber = aliceNumber.mutableCopy().multiply(bobNumber, mod: BTCCurvePoint.curveOrder()) + let sharedSecret = BTCCurvePoint.generator().multiply(sharedSecretNumber) + + XCTAssertEqual(aliceSharedSecret, bobSharedSecret, "Should have the same shared secret") + XCTAssertEqual(aliceSharedSecret, sharedSecret, "Multiplication of private keys should yield a private key for the shared point") + + } +} From 1d239a7fa8b337111711078f3d056b20d9d33b9f Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Thu, 21 May 2015 17:32:55 -0400 Subject: [PATCH 056/163] Added BTCBlindSignatureTests.swift --- CoreBitcoin.xcodeproj/project.pbxproj | 4 + CoreBitcoin/SwiftBridgingHeader.h | 5 +- .../BTCBlindSignatureTests.swift | 112 ++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 CoreBitcoinTestsOSX/BTCBlindSignatureTests.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index cd6ec0a7..cda09109 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -410,6 +410,7 @@ 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */; }; 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */; }; 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; + 75A1A3721B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75A1A3711B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift */; }; 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */; }; 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */; }; @@ -630,6 +631,7 @@ 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; + 75A1A3711B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBlindSignatureTests.swift; sourceTree = ""; }; 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCAddressTests.swift; sourceTree = ""; }; 75B819071B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBlockchainInfoTests.swift; sourceTree = ""; }; 75B819091B04E69A0055F8B1 /* BTCPriceSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCPriceSourceTests.swift; sourceTree = ""; }; @@ -1014,6 +1016,7 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( + 75A1A3711B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift */, 75BBC6E41B0E721700A61690 /* BTCCurvePointTests.swift */, 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */, 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */, @@ -1662,6 +1665,7 @@ 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */, 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */, 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */, + 75A1A3721B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift in Sources */, 75B819081B04E68B0055F8B1 /* BTCBlockchainInfoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index 690a3d03..84d4ba2d 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -18,4 +18,7 @@ #import "BTCBitcoinURL.h" #import "BTCCurrencyConverter.h" #import "BTCBigNumber.h" -#import "BTCCurvePoint.h" \ No newline at end of file +#import "BTCCurvePoint.h" +#import "BTCBlindSignature.h" +#import "BTCKeychain.h" +#import "BTCData.h" diff --git a/CoreBitcoinTestsOSX/BTCBlindSignatureTests.swift b/CoreBitcoinTestsOSX/BTCBlindSignatureTests.swift new file mode 100644 index 00000000..bb693ba4 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCBlindSignatureTests.swift @@ -0,0 +1,112 @@ +// +// BTCBlindSignatureTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/21/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCBlindSignatureTests: XCTestCase { + + func testCoreAlgorithm() { + + let api = BTCBlindSignature() + + let a = BTCBigNumber(unsignedBigEndian: BTCHash256("a".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + let b = BTCBigNumber(unsignedBigEndian: BTCHash256("b".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + let c = BTCBigNumber(unsignedBigEndian: BTCHash256("c".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + let d = BTCBigNumber(unsignedBigEndian: BTCHash256("d".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + let p = BTCBigNumber(unsignedBigEndian: BTCHash256("p".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + let q = BTCBigNumber(unsignedBigEndian: BTCHash256("q".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false))) + + let PQ = api.bob_P_and_Q_for_p(p, q: q) as! [BTCCurvePoint] + let P = PQ.first! + let Q = PQ.last! + + XCTAssertNotNil(P, "sanity check") + XCTAssertNotNil(Q, "sanity check") + + let KT = api.alice_K_and_T_for_a(a, b: b, c: c, d: d, p: P, q: Q) as! [BTCCurvePoint] + let K = KT.first! + let T = KT.last! + + XCTAssertNotNil(K, "sanity check") + XCTAssertNotNil(T, "sanity check") + + // In real life we'd use T in a destination script and keep K.x around for redeeming it later. + // ... + // It's time to redeem funds! Lets do it by asking Bob to sign stuff for Alice. + + let hash = BTCHash256("some transaction".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + + // Alice computes and sends to Bob. + let blindedHash = api.aliceBlindedHashForHash(BTCBigNumber(unsignedBigEndian: hash), a: a, b: b) + + XCTAssertNotNil(blindedHash, "sanity check") + + // Bob computes and sends to Alice. + let blindedSig = api.bobBlindedSignatureForHash(blindedHash, p: p, q: q) + + XCTAssertNotNil(blindedSig, "sanity check") + + // Alice unblinds and uses in the final signature. + let unblindedSignature = api.aliceUnblindedSignatureForSignature(blindedSig, c: c, d: d) + + XCTAssertNotNil(unblindedSignature, "sanity check") + + let finalSignature = api.aliceCompleteSignatureForKx(K.x, unblindedSignature: unblindedSignature) + + XCTAssertNotNil(finalSignature, "sanity check") + + let pubkey = BTCKey(curvePoint: T) + XCTAssertTrue(pubkey.isValidSignature(finalSignature, hash: hash), "should have created a valid signature after all that trouble") + + } + + + func testConvenienceAPI() { + let aliceKeychain = BTCKeychain(seed: "Alice".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + let bobKeychain = BTCKeychain(seed: "Bob".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + let bobPublicKeychain = BTCKeychain(extendedKey: bobKeychain.extendedPublicKey) + + XCTAssertNotNil(aliceKeychain, "sanity check") + XCTAssertNotNil(bobKeychain, "sanity check") + XCTAssertNotNil(bobPublicKeychain, "sanity check") + + let alice = BTCBlindSignature(clientKeychain: aliceKeychain, custodianKeychain: bobPublicKeychain) + let bob = BTCBlindSignature(custodianKeychain: bobKeychain) + + XCTAssertNotNil(alice, "sanity check") + XCTAssertNotNil(bob, "sanity check") + + for var i: uint32 = 0; i < 32; i++ { + // This will be Alice's pubkey that she can use in a destination script. + let pubkey = alice.publicKeyAtIndex(i) + XCTAssertNotNil(pubkey, "sanity check") + +// println("pubkey = \(pubkey)") + + // This will be a hash of Alice's transaction. + let hash = BTCHash256("transaction \(i)".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + +// println("hash = \(hash)") + + // Alice will send this to Bob. + let blindedHash = alice.blindedHashForHash(hash, index: i) + XCTAssertNotNil(blindedHash, "sanity check") + + // Bob computes the signature for Alice and sends it back to her. + let blindSig = bob.blindSignatureForBlindedHash(blindedHash) + XCTAssertNotNil(blindSig, "sanity check") + + // Alice receives the blind signature and computes the complete ECDSA signature ready to use in a redeeming transaction. + let finalSig = alice.unblindedSignatureForBlindSignature(blindSig, verifyHash: hash) + XCTAssertNotNil(finalSig, "sanity check") + + XCTAssertTrue(pubkey.isValidSignature(finalSig, hash: hash), "Check that the resulting signature is valid for our original hash and pubkey.") + } + } +} From ed7a67e74a2406a4275071e3a51c5fa1748eed5b Mon Sep 17 00:00:00 2001 From: Robert Mozayeni Date: Tue, 26 May 2015 13:55:25 -0400 Subject: [PATCH 057/163] Added BTCEncryptedBackupTests.swift --- CoreBitcoin.xcodeproj/project.pbxproj | 4 + CoreBitcoin/SwiftBridgingHeader.h | 2 + .../BTCEncryptedBackupTests.swift | 106 ++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 CoreBitcoinTestsOSX/BTCEncryptedBackupTests.swift diff --git a/CoreBitcoin.xcodeproj/project.pbxproj b/CoreBitcoin.xcodeproj/project.pbxproj index cda09109..428b694e 100644 --- a/CoreBitcoin.xcodeproj/project.pbxproj +++ b/CoreBitcoin.xcodeproj/project.pbxproj @@ -409,6 +409,7 @@ 20E1E01217C73181003B6987 /* NSData+BTCData.m in Sources */ = {isa = PBXBuildFile; fileRef = 20E1E01117C73181003B6987 /* NSData+BTCData.m */; }; 754564761B07F9AB008F34DC /* BTCBitcoinURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */; }; 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */; }; + 7558460E1B14E4B100E49C87 /* BTCEncryptedBackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7558460D1B14E4B100E49C87 /* BTCEncryptedBackupTests.swift */; }; 757B241B1B03F1BF0084A9DE /* CoreBitcoinOSX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 206B0113183547C200878B8D /* CoreBitcoinOSX.framework */; }; 75A1A3721B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75A1A3711B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift */; }; 75B819061B04E65A0055F8B1 /* BTCAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B819051B04E65A0055F8B1 /* BTCAddressTests.swift */; }; @@ -628,6 +629,7 @@ 20E1E01417C735EE003B6987 /* NS+BTCBase58.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NS+BTCBase58.m"; sourceTree = ""; }; 754564751B07F9AB008F34DC /* BTCBitcoinURLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCBitcoinURLTests.swift; sourceTree = ""; }; 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCCurrencyConverterTests.swift; sourceTree = ""; }; + 7558460D1B14E4B100E49C87 /* BTCEncryptedBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BTCEncryptedBackupTests.swift; sourceTree = ""; }; 757B24151B03F1BF0084A9DE /* CoreBitcoinTestsOSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreBitcoinTestsOSX.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 757B24181B03F1BF0084A9DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 757B24211B03F1E80084A9DE /* SwiftBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftBridgingHeader.h; sourceTree = ""; }; @@ -1016,6 +1018,7 @@ 757B24161B03F1BF0084A9DE /* CoreBitcoinTestsOSX */ = { isa = PBXGroup; children = ( + 7558460D1B14E4B100E49C87 /* BTCEncryptedBackupTests.swift */, 75A1A3711B0E7DB900EA8D45 /* BTCBlindSignatureTests.swift */, 75BBC6E41B0E721700A61690 /* BTCCurvePointTests.swift */, 7549DAD01B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift */, @@ -1660,6 +1663,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7558460E1B14E4B100E49C87 /* BTCEncryptedBackupTests.swift in Sources */, 75BBC6E51B0E721700A61690 /* BTCCurvePointTests.swift in Sources */, 75B8190A1B04E69A0055F8B1 /* BTCPriceSourceTests.swift in Sources */, 7549DAD11B0BB1D900F20FF0 /* BTCCurrencyConverterTests.swift in Sources */, diff --git a/CoreBitcoin/SwiftBridgingHeader.h b/CoreBitcoin/SwiftBridgingHeader.h index 84d4ba2d..1ff3b341 100644 --- a/CoreBitcoin/SwiftBridgingHeader.h +++ b/CoreBitcoin/SwiftBridgingHeader.h @@ -22,3 +22,5 @@ #import "BTCBlindSignature.h" #import "BTCKeychain.h" #import "BTCData.h" +#import "BTCEncryptedBackup.h" +#import "BTCNetwork.h" \ No newline at end of file diff --git a/CoreBitcoinTestsOSX/BTCEncryptedBackupTests.swift b/CoreBitcoinTestsOSX/BTCEncryptedBackupTests.swift new file mode 100644 index 00000000..4869d3e4 --- /dev/null +++ b/CoreBitcoinTestsOSX/BTCEncryptedBackupTests.swift @@ -0,0 +1,106 @@ +// +// BTCEncryptedBackupTests.swift +// CoreBitcoin +// +// Created by Robert S Mozayeni on 5/26/15. +// Copyright (c) 2015 Oleg Andreev. All rights reserved. +// + +import Cocoa +import XCTest + +class BTCEncryptedBackupTests: XCTestCase { + + func testShortBackup() { + let timestamp: NSTimeInterval = 1427720967 + + let plaintext = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + let masterKey = BTCSHA256("Master Key".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + XCTAssertEqual(masterKey, BTCDataFromHex("08c17482950a872178b8030c8f8a63bc6e5f9f680dd25739e1ec7e0b544f40f9"), "master key") + let backupKey = BTCEncryptedBackup.backupKeyForNetwork(BTCNetwork.mainnet(), masterKey: masterKey) + let backupKeyTestnet = BTCEncryptedBackup.backupKeyForNetwork(BTCNetwork.testnet(), masterKey: masterKey) + + XCTAssertEqual(backupKey, BTCDataFromHex("7618f25cd5faadd52d0ea3b608b0c076664f5816b81311017985ae229157057a"), "must produce a determinstic backup key") + XCTAssertEqual(backupKeyTestnet, BTCDataFromHex("caa57de4c3d9c77186175fbfdc326997162da0ce1b74022a51c600838449b2c3"), "must produce a determinstic backup key") + + let backup = BTCEncryptedBackup.encrypt(plaintext, backupKey: backupKey, timestamp: timestamp) + + XCTAssertNotNil(backup, "Should encrypt alright") + + XCTAssertGreaterThanOrEqual(backup.encryptedData.length, 1 + 4 + 16 + 1 + plaintext!.length + 1 + 68, "Should be of realistic length") + + XCTAssertEqual(backup.encryptionKey, BTCDataFromHex("58369379e5100b58cd49c97171f29f3d"), "Should use deterministic encryption key") + XCTAssertEqual(backup.iv, BTCDataFromHex("bf07aaa979ae8af6eebfea5da8e83cad"), "Should compute IV deterministically") + XCTAssertEqual(backup.authenticationKey.privateKey, BTCDataFromHex("44b45878c33c974179f5363fee95f9e9d4a60c97e9c865e58b57bef3558034f4"), "Should use deterministic authentication private key") + XCTAssertEqual(backup.authenticationKey.publicKey, BTCDataFromHex("028747be6de07552c48f9db23617792d47df1accd611175f6dfe636f4098984a09"), "Should use deterministic compressed authentication public key") + XCTAssertEqual(backup.walletID, "WmEp7EPk8vKMgXQQGWgh1AYhmY8Usw6kwL", "Should compute WalletID deterministically") + XCTAssertEqual(backup.ciphertext, BTCDataFromHex("5edbaade9ba4ed528a8de36c95ece996189dedf4756fba2599f94b4f370d701366e2f0ba4e59111c0787708cf4b0b82de558b4d8bf5d90b3512f09814d605d4c14f2f85b596211f83918c31c4bef19ea"), "Should compute ciphertext deterministically") + XCTAssertEqual(backup.merkleRoot, BTCDataFromHex("9e913cd60f7df551b3baa320602bfba78489921d661362a64a03550a45add008"), "Should compute merkle root of the ciphertext correctly") + XCTAssertEqual(backup.signature, BTCDataFromHex("3045022100ddbc9b06625c2b3c9cbfb27b6ac39596bd13daf43d4ddecbb7257a0d26f5e2c402200a5bd5fd27df7ac262ac3cff9d5398742c6fd9c76c427548667bee45dcb1134c"), "Should compute signature deterministically (RFC6979)") + XCTAssertEqual(backup.encryptedData, BTCDataFromHex("01074b1955bf07aaa979ae8af6eebfea5da8e83cad505edbaade9ba4ed528a8de36c95ece996189dedf4756fba2599f94b4f370d701366e2f0ba4e59111c0787708cf4b0b82de558b4d8bf5d90b3512f09814d605d4c14f2f85b596211f83918c31c4bef19ea473045022100ddbc9b06625c2b3c9cbfb27b6ac39596bd13daf43d4ddecbb7257a0d26f5e2c402200a5bd5fd27df7ac262ac3cff9d5398742c6fd9c76c427548667bee45dcb1134c"), "Should compute the whole encrypted backup deterministically") + + let backup2 = BTCEncryptedBackup.decrypt(backup.encryptedData, backupKey: backupKey) + + XCTAssertNotNil(backup2, "Must decrypt") + XCTAssertEqual(backup2.version, backup.version, "Version must be decoded correctly") + XCTAssertEqual(backup2.timestamp, backup.timestamp, "Timestamp must be decoded correctly") + XCTAssertEqual(backup2.decryptedData, plaintext!, "Plaintext must be decrypted correctly") + } + + func testLongBackup() { + let backupJSON: NSDictionary = [ + "version": "1", + "network": "main", + "accounts": [ + ["type": "bip44", "label": "label for bip44 account 0", "path": "44'/0'/0'"], + ["type": "bip44", "label": "label for bip44 account 1", "path": "44'/0'/1'"], + ["type": "bip44", "label": "label for bip44 account 17", "path": "44'/0'/17'"], + ["type": "single", "label": "Vanity Address", "wif": "5RLmtKqh..."], + ["type": "single", "label": "Watch-Only", "address": "1Ht3CBv..."], + ["type": "trezor", "label": "My Trezor", "xpub": "xpub6FHa3pjLCk8..."], + ], + "transactions": [ + "f10c7786f120536...": [ + "memo": "Hotel in Lisbon", + "recipient": "Expedia, Inc.", + "payment_request": "12008c17d661778f1249...", + "payment_ack": "0b2678e8a476a30e2609...", + "fiat_amount": "-265.10", + "fiat_code": "EUR", + ], + ], + "currency": [ + "fiat_code": "USD", + "fiat_source": "Coinbase", + "btc_unit": "BTC", + ] + ] + + var error: NSError? = nil + let plaintext = NSJSONSerialization.dataWithJSONObject(backupJSON, options: NSJSONWritingOptions(0), error: &error) + let toPrint = NSString(data: plaintext!, encoding: NSUTF8StringEncoding) + println("plaintext = \(toPrint)") + XCTAssertNotNil(plaintext, "Must encode to JSON") + + let masterKey = BTCSHA256("Master Key".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)) + let backupKey = BTCEncryptedBackup.backupKeyForNetwork(BTCNetwork.mainnet(), masterKey: masterKey) + + XCTAssertEqual(backupKey, BTCDataFromHex("7618f25cd5faadd52d0ea3b608b0c076664f5816b81311017985ae229157057a"), "must produce a determinstic backup key") + + let backup = BTCEncryptedBackup.encrypt(plaintext, backupKey: backupKey, timestamp: 1427720967.0) + + XCTAssertNotNil(backup, "Should encrypt alright") + + XCTAssertGreaterThanOrEqual(backup.encryptedData.length, 1 + 4 + 16 + 1 + plaintext!.length + 1 + 68, "Should be of realistic length") + + let backup2 = BTCEncryptedBackup.decrypt(backup.encryptedData, backupKey: backupKey) + + XCTAssertNotNil(backup2, "Must decrypt") + XCTAssertEqual(backup2.version, backup.version, "Version must be decoded correctly") + XCTAssertEqual(backup2.timestamp, backup.timestamp, "Timestamp must be decoded correctly") + XCTAssertEqual(backup2.decryptedData, plaintext!, "Plaintext must be decrypted correctly") + + } + +} From a3209c62f746b9ab1281acabafad1331a41fd9f8 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 27 May 2015 10:35:12 +0200 Subject: [PATCH 058/163] Added null annotations to BTCAddress, improved handling of invalid addresses --- CoreBitcoin/BTCAddress.h | 32 ++++++------- CoreBitcoin/BTCAddress.m | 48 ++++++++++++++----- CoreBitcoin/BTCAssetAddress.h | 4 +- CoreBitcoinTestsOSX/BTCAddressTests.swift | 48 +++++++++---------- .../BTCBlockchainInfoTests.swift | 4 +- 5 files changed, 79 insertions(+), 57 deletions(-) diff --git a/CoreBitcoin/BTCAddress.h b/CoreBitcoin/BTCAddress.h index ad9e265e..df47f5e1 100644 --- a/CoreBitcoin/BTCAddress.h +++ b/CoreBitcoin/BTCAddress.h @@ -19,32 +19,23 @@ // Returns an instance of a specific subclass depending on version number. // Returns nil for unsupported addresses. -+ (instancetype) addressWithString:(NSString*)string; ++ (nullable instancetype) addressWithString:(nullable NSString*)string; // Initializes address with raw data. Should only be used in subclasses, base class will raise exception. -+ (instancetype) addressWithData:(NSData*)data; - -// Returns an instance of a specific subclass depending on version number. -// Returns nil for unsupported addresses. -// DEPRECATED! Use `-addressWithString:` instead. -+ (instancetype) addressWithBase58String:(NSString*)string DEPRECATED_ATTRIBUTE; ++ (nullable instancetype) addressWithData:(nullable NSData*)data; // Returns binary contents of an address (without checksums or version number). // 20 bytes for hashes, 32 bytes for private key. -@property(nonatomic, readonly) NSData* data; +@property(nonatomic, readonly, nonnull) NSData* data; // Returns representation in base58 encoding. -@property(nonatomic, readonly) NSString* string; - -// Returns representation in base58 encoding. -// DEPRECATED! Use -string instead. -@property(nonatomic, readonly) NSString* base58String DEPRECATED_ATTRIBUTE; +@property(nonatomic, readonly, nonnull) NSString* string; /*! * Returns a public version of this address. By default it's receiver itself. * PrivateKeyAddress returns appropriate PublicKeyAddress. */ -@property(nonatomic, readonly) BTCAddress* publicAddress; +@property(nonatomic, readonly, nonnull) BTCAddress* publicAddress; /*! * Returns YES if this address is intended for testnet. @@ -56,6 +47,15 @@ */ - (void) clear; + +// Returns an instance of a specific subclass depending on version number. +// Returns nil for unsupported addresses. +// DEPRECATED! Use `-addressWithString:` instead. ++ (nullable instancetype) addressWithBase58String:(nullable NSString*)string DEPRECATED_ATTRIBUTE; +// Returns representation in base58 encoding. +// DEPRECATED! Use -string instead. +@property(nonatomic, readonly, nonnull) NSString* base58String DEPRECATED_ATTRIBUTE; + @end @@ -73,10 +73,10 @@ @property(nonatomic, getter=isPublicKeyCompressed) BOOL publicKeyCompressed; // Returns BTCKey containing a key pair. Its public key is compressed as specified by the address. -@property(nonatomic, readonly) BTCKey* key; +@property(nonatomic, readonly, nonnull) BTCKey* key; // Creates address from raw private key data. If the public key must be compressed, pass YES to publicKeyCompressed:. -+ (instancetype) addressWithData:(NSData*)data publicKeyCompressed:(BOOL)compressedPubkey; ++ (nullable instancetype) addressWithData:(nullable NSData*)data publicKeyCompressed:(BOOL)compressedPubkey; @end @interface BTCPrivateKeyAddressTestnet : BTCPrivateKeyAddress @end diff --git a/CoreBitcoin/BTCAddress.m b/CoreBitcoin/BTCAddress.m index edffd6ef..a46ae3b3 100644 --- a/CoreBitcoin/BTCAddress.m +++ b/CoreBitcoin/BTCAddress.m @@ -4,6 +4,7 @@ #import "BTCData.h" #import "BTCBase58.h" #import "BTCKey.h" +#import enum { @@ -23,42 +24,36 @@ @implementation BTCAddress { char* _cstring; } -- (void) dealloc -{ +- (void) dealloc { // The data may be retained by someone and should not be cleared like that. // [self clear]; if (_cstring) free(_cstring); _data = nil; } -+ (instancetype) addressWithString:(NSString*)string -{ ++ (instancetype) addressWithString:(NSString*)string { return [self addressWithBase58CString:[string cStringUsingEncoding:NSASCIIStringEncoding]]; } -+ (instancetype) addressWithBase58String:(NSString*)string // DEPRECATED -{ ++ (instancetype) addressWithBase58String:(NSString*)string { // DEPRECATED return [self addressWithString:string]; } // Initializes address with raw data. Should only be used in subclasses, base class will raise exception. -+ (instancetype) addressWithData:(NSData*)data -{ ++ (instancetype) addressWithData:(NSData*)data { @throw [NSException exceptionWithName:@"BTCAddress Exception" reason:@"Cannot init base class with raw data. Please use specialized subclass." userInfo:nil]; return nil; } // prototype to make clang happy. -+ (instancetype) addressWithComposedData:(NSData*)data cstring:(const char*)cstring -{ ++ (instancetype) addressWithComposedData:(NSData*)data cstring:(const char*)cstring { return nil; } // Returns an instance of a specific subclass depending on version number. // Returns nil for unsupported addresses. -+ (id) addressWithBase58CString:(const char*)cstring -{ ++ (id) addressWithBase58CString:(const char*)cstring { NSMutableData* composedData = BTCDataFromBase58CheckCString(cstring); if (!composedData) return nil; if (composedData.length < 2) return nil; @@ -95,7 +90,13 @@ + (id) addressWithBase58CString:(const char*)cstring // Unknown version. NSLog(@"BTCAddress: unknown address version: %d", version); } - + + // Verify that address is compatible with the class being invoked. + // So if someone asked to parse P2PKH address with P2SH string, they will get nil instead of P2SH instance. + if (![address isKindOfClass:self]) { + return nil; + } + // Securely erase decoded address data BTCDataClear(composedData); @@ -176,6 +177,27 @@ - (BOOL) isEqual:(BTCAddress*)other return [self.string isEqualToString:other.string]; } + +// Known Addresses + + ++ (NSMutableDictionary*) registeredAddressClasses { + static dispatch_once_t onceToken; + static NSMutableDictionary* registeredAddressClasses; + dispatch_once(&onceToken, ^{ + registeredAddressClasses = [NSMutableDictionary dictionary]; + }); + return registeredAddressClasses; +} + +// Registers a price source with a given name. ++ (void) registerAddressClass:(Class)addressClass { + if (!addressClass) return; + NSString* name = [NSString stringWithCString:class_getName(addressClass) encoding:NSUTF8StringEncoding]; + [self registeredAddressClasses][name] = addressClass; +} + + @end diff --git a/CoreBitcoin/BTCAssetAddress.h b/CoreBitcoin/BTCAssetAddress.h index d9767847..175d534b 100644 --- a/CoreBitcoin/BTCAssetAddress.h +++ b/CoreBitcoin/BTCAssetAddress.h @@ -3,6 +3,6 @@ #import "BTCAddress.h" @interface BTCAssetAddress : BTCAddress -@property(nonatomic, readonly) BTCAddress* bitcoinAddress; -+ (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress; +@property(nonatomic, readonly, nonnull) BTCAddress* bitcoinAddress; ++ (nonnull instancetype) addressWithBitcoinAddress:(nonnull BTCAddress*)btcAddress; @end diff --git a/CoreBitcoinTestsOSX/BTCAddressTests.swift b/CoreBitcoinTestsOSX/BTCAddressTests.swift index dcd2c0a9..f66fb8e3 100644 --- a/CoreBitcoinTestsOSX/BTCAddressTests.swift +++ b/CoreBitcoinTestsOSX/BTCAddressTests.swift @@ -22,41 +22,36 @@ class BTCAddressTests: XCTestCase { } func testPublicKeyAddress() { - let addr = BTCPublicKeyAddress(string: "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T") - XCTAssertNotNil(addr, "Address should be decoded") - XCTAssert(addr!.dynamicType === BTCPublicKeyAddress.self, "Address should be an instance of BTCPublicKeyAddress") - XCTAssertEqual("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827", addr!.data.hex(), "Must decode hash160 correctly.") - XCTAssertEqual(addr!, addr!.publicAddress, "Address should be equal to its publicAddress") + let addr = BTCPublicKeyAddress(string: "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T")! + XCTAssert(addr.dynamicType === BTCPublicKeyAddress.self, "Address should be an instance of BTCPublicKeyAddress") + XCTAssertEqual("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827", addr.data.hex(), "Must decode hash160 correctly.") + XCTAssertEqual(addr, addr.publicAddress, "Address should be equal to its publicAddress") - let addr2 = BTCPublicKeyAddress(data: BTCDataFromHex("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827")) + let addr2 = BTCPublicKeyAddress(data: BTCDataFromHex("c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827"))! XCTAssertNotNil(addr2, "Address should be created") XCTAssertEqual("1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T", addr2.string, "Must encode hash160 correctly.") - } func testPrivateKeyAddress() { - let addr = BTCPrivateKeyAddress(string: "5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS") - XCTAssertNotNil(addr, "Address should be decoded") - XCTAssert(addr!.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") + let addr = BTCPrivateKeyAddress(string: "5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS")! + XCTAssert(addr.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") XCTAssert(!addr.publicKeyCompressed, "Address should be not compressed") XCTAssertEqual("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", addr.data.hex(), "must provide proper public address") - let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")) + let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a"))! XCTAssertNotNil(addr2, "Address should be created") XCTAssertEqual("5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS", addr2.string, "Must encode secret key correctly.") } func testPrivateKeyAddressWithCompressedPoint() { - let addr = BTCPrivateKeyAddress(string: "L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu") - XCTAssertNotNil(addr, "Address should be decoded") - XCTAssert(addr!.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") + let addr = BTCPrivateKeyAddress(string: "L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu")! + XCTAssert(addr.dynamicType === BTCPrivateKeyAddress.self, "Address should be an instance of BTCPrivateKeyAddress") XCTAssert(addr.publicKeyCompressed, "address should be compressed") XCTAssertEqual("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a", addr.data.hex(), "Must decode secret key correctly.") XCTAssertEqual(addr.publicAddress.string, "1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8", "must provide proper public address") - let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a")) - XCTAssertNotNil(addr2, "Address should be created") + let addr2 = BTCPrivateKeyAddress(data: BTCDataFromHex("c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a"))! addr2.publicKeyCompressed = true XCTAssertEqual("L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu", addr2.string, "Must encode secret key correctly.") addr2.publicKeyCompressed = false @@ -64,26 +59,31 @@ class BTCAddressTests: XCTestCase { } func testScriptHashKeyAddress() { - let addr = BTCScriptHashAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8") - XCTAssertNotNil(addr, "Address should be decoded") - XCTAssert(addr!.dynamicType === BTCScriptHashAddress.self, "Address should be an instance of BTCScriptHashAddress") + let addr = BTCScriptHashAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8")! + XCTAssert(addr.dynamicType === BTCScriptHashAddress.self, "Address should be an instance of BTCScriptHashAddress") XCTAssertEqual("e8c300c87986efa84c37c0519929019ef86eb5b4", addr.data.hex(), "Must decode hash160 correctly.") XCTAssertEqual(addr, addr.publicAddress, "Address should be equal to its publicAddress") - let addr2 = BTCScriptHashAddress(data: BTCDataFromHex("e8c300c87986efa84c37c0519929019ef86eb5b4")) + let addr2 = BTCScriptHashAddress(data: BTCDataFromHex("e8c300c87986efa84c37c0519929019ef86eb5b4"))! XCTAssertNotNil(addr2, "Address should be created") XCTAssertEqual("3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", addr2.string, "Must encode hash160 correctly.") } - func testAssetAddress() { - let btcAddr = BTCPublicKeyAddress(string: "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM") + let btcAddr = BTCPublicKeyAddress(string: "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")! let assetAddr = BTCAssetAddress(bitcoinAddress:btcAddr) - XCTAssertNotNil(assetAddr, "Address should be created") XCTAssertEqual("akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy", assetAddr.string, "Must encode to Open Assets format correctly.") - let assetAddr2 = BTCAssetAddress(string:"akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy") + let assetAddr2 = BTCAssetAddress(string:"akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy")! XCTAssertEqual("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", assetAddr2.bitcoinAddress.string, "Must decode underlying Bitcoin address from Open Assets address.") } + func testParseErrors() { + XCTAssertNil(BTCAddress(string: "X6UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"), "Must fail to parse incorrect address") + XCTAssertNil(BTCPublicKeyAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8"), "Must fail to parse valid address of non-matching type") + XCTAssertNil(BTCPrivateKeyAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8"), "Must fail to parse valid address of non-matching type") + XCTAssertNil(BTCScriptHashAddress(string: "L3p8oAcQTtuokSCRHQ7i4MhjWc9zornvpJLfmg62sYpLRJF9woSu"), "Must fail to parse valid address of non-matching type") + XCTAssertNil(BTCAssetAddress(string: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8"), "Must fail to parse valid address of non-matching type") + } + } diff --git a/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift b/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift index b5c72824..34894720 100644 --- a/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift +++ b/CoreBitcoinTestsOSX/BTCBlockchainInfoTests.swift @@ -25,7 +25,7 @@ class BTCBlockchainInfoTests: XCTestCase { var error: NSError? - let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1LKF45kfvHAaP7C4cF91pVb3bkAsmQ8nBr")], error: &error) + let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1LKF45kfvHAaP7C4cF91pVb3bkAsmQ8nBr")!], error: &error) XCTAssert(outputs.count == 0, "should return an empty array") XCTAssert(error == nil, "should have no error") @@ -36,7 +36,7 @@ class BTCBlockchainInfoTests: XCTestCase { var error: NSError? - let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")], error: &error) + let outputs = BTCBlockchainInfo().unspentOutputsWithAddresses([BTCAddress(string: "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")!], error: &error) XCTAssert(outputs.count > 0, "should return a non-empty array") XCTAssert((outputs.first as? BTCTransactionOutput) != nil, "should contain BTCTransactionOutput objects") From 0427812b33a8509b1b73e8c10b49320f6d68e683 Mon Sep 17 00:00:00 2001 From: Oleg Andreev Date: Wed, 27 May 2015 15:43:00 +0200 Subject: [PATCH 059/163] Added Open Assets support in URLs (using both bitcoin: and openassets: schemes) --- CoreBitcoin/BTCAddress.h | 28 +++- CoreBitcoin/BTCAddress.m | 145 +++++++++---------- CoreBitcoin/BTCAssetAddress.m | 20 ++- CoreBitcoin/BTCBitcoinURL.h | 37 +++-- CoreBitcoin/BTCBitcoinURL.m | 105 ++++++++++++-- CoreBitcoinTestsOSX/BTCBitcoinURLTests.swift | 61 ++++++-- 6 files changed, 263 insertions(+), 133 deletions(-) diff --git a/CoreBitcoin/BTCAddress.h b/CoreBitcoin/BTCAddress.h index df47f5e1..1d533bd9 100644 --- a/CoreBitcoin/BTCAddress.h +++ b/CoreBitcoin/BTCAddress.h @@ -17,22 +17,36 @@ @class BTCKey; @interface BTCAddress : NSObject -// Returns an instance of a specific subclass depending on version number. -// Returns nil for unsupported addresses. +/*! + * Allows subclasses to be instantiated by a call to a superclass: BTCAddress(string: "...") + */ ++ (void) registerAddressClass:(nonnull Class)addressClass version:(uint8_t)version; + +/*! + * Returns an instance of a specific subclass depending on version number. + * Returns nil for unsupported addresses. + */ + (nullable instancetype) addressWithString:(nullable NSString*)string; -// Initializes address with raw data. Should only be used in subclasses, base class will raise exception. +/*! + * Initializes address with raw data. Should only be used in subclasses, base class will raise exception. + * Returns an instance of a specific subclass depending on version number. + */ + (nullable instancetype) addressWithData:(nullable NSData*)data; -// Returns binary contents of an address (without checksums or version number). -// 20 bytes for hashes, 32 bytes for private key. +/*! + * Returns binary contents of an address (without checksums or version number). + * 20 bytes for hashes, 32 bytes for private key. + */ @property(nonatomic, readonly, nonnull) NSData* data; -// Returns representation in base58 encoding. +/*! + * Returns representation in base58 encoding. + */ @property(nonatomic, readonly, nonnull) NSString* string; /*! - * Returns a public version of this address. By default it's receiver itself. + * Returns a public version of this address. By default it is a receiver itself. * PrivateKeyAddress returns appropriate PublicKeyAddress. */ @property(nonatomic, readonly, nonnull) BTCAddress* publicAddress; diff --git a/CoreBitcoin/BTCAddress.m b/CoreBitcoin/BTCAddress.m index a46ae3b3..21265f83 100644 --- a/CoreBitcoin/BTCAddress.m +++ b/CoreBitcoin/BTCAddress.m @@ -58,36 +58,12 @@ + (id) addressWithBase58CString:(const char*)cstring { if (!composedData) return nil; if (composedData.length < 2) return nil; - int version = ((unsigned char*)composedData.bytes)[0]; + uint8_t version = ((unsigned char*)composedData.bytes)[0]; - BTCAddress* address = nil; - if (version == BTCPublicKeyAddressVersion) - { - address = [BTCPublicKeyAddress addressWithComposedData:composedData cstring:cstring]; - } - else if (version == BTCPrivateKeyAddressVersion) - { - address = [BTCPrivateKeyAddress addressWithComposedData:composedData cstring:cstring]; - } - else if (version == BTCScriptHashAddressVersion) - { - address = [BTCScriptHashAddress addressWithComposedData:composedData cstring:cstring]; - } - else if (version == BTCPublicKeyAddressVersionTestnet) - { - address = [BTCPublicKeyAddressTestnet addressWithComposedData:composedData cstring:cstring]; - } - else if (version == BTCPrivateKeyAddressVersionTestnet) - { - address = [BTCPrivateKeyAddressTestnet addressWithComposedData:composedData cstring:cstring]; - } - else if (version == BTCScriptHashAddressVersionTestnet) - { - address = [BTCScriptHashAddressTestnet addressWithComposedData:composedData cstring:cstring]; - } - else - { - // Unknown version. + NSDictionary* classes = [self registeredAddressClasses]; + Class cls = classes[@(version)]; + BTCAddress* address = [cls addressWithComposedData:composedData cstring:cstring]; + if (!address) { NSLog(@"BTCAddress: unknown address version: %d", version); } @@ -121,13 +97,11 @@ - (void) setBase58CString:(const char*)cstring } // for subclasses -- (NSMutableData*) dataForBase58Encoding -{ +- (NSMutableData*) dataForBase58Encoding { return nil; } -- (const char*) base58CString -{ +- (const char*) base58CString { if (!_cstring) { NSMutableData* data = [self dataForBase58Encoding]; @@ -138,41 +112,43 @@ - (const char*) base58CString } // Returns representation in base58 encoding. -- (NSString*) string -{ +- (NSString*) string { const char* cstr = [self base58CString]; if (!cstr) return nil; return [NSString stringWithCString:cstr encoding:NSASCIIStringEncoding]; } -- (NSString*) base58String -{ +- (NSString*) base58String { // deprecated return [self string]; } -- (BTCAddress*) publicAddress -{ +- (BTCAddress*) publicAddress { return self; } -- (BOOL) isTestnet -{ +- (BOOL) isTestnet { return NO; } -- (void) clear -{ +- (uint8_t) versionByte { + return [[self class] BTCVersionPrefix]; +} + ++ (uint8_t) BTCVersionPrefix { + [NSException raise:@"BTCAddress BTCVersionPrefix must be accessed via subclasses" format:@""]; + return 0xff; +} + +- (void) clear { BTCSecureClearCString(_cstring); BTCDataClear(_data); } -- (NSString*) description -{ +- (NSString*) description { return [NSString stringWithFormat:@"<%@: %@>", [self class], self.string]; } -- (BOOL) isEqual:(BTCAddress*)other -{ +- (BOOL) isEqual:(BTCAddress*)other { if (![other isKindOfClass:[BTCAddress class]]) return NO; return [self.string isEqualToString:other.string]; } @@ -191,10 +167,9 @@ + (NSMutableDictionary*) registeredAddressClasses { } // Registers a price source with a given name. -+ (void) registerAddressClass:(Class)addressClass { ++ (void) registerAddressClass:(Class)addressClass version:(uint8_t)version { if (!addressClass) return; - NSString* name = [NSString stringWithCString:class_getName(addressClass) encoding:NSUTF8StringEncoding]; - [self registeredAddressClasses][name] = addressClass; + [self registeredAddressClasses][@(version)] = addressClass; } @@ -203,6 +178,14 @@ + (void) registerAddressClass:(Class)addressClass { @implementation BTCPublicKeyAddress ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { + return BTCPublicKeyAddressVersion; +} + #define BTCPublicKeyAddressLength 20 + (instancetype) addressWithData:(NSData*)data @@ -240,17 +223,15 @@ - (NSMutableData*) dataForBase58Encoding return data; } -- (unsigned char) versionByte -{ - return BTCPublicKeyAddressVersion; -} - @end @implementation BTCPublicKeyAddressTestnet -- (unsigned char) versionByte -{ ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { return BTCPublicKeyAddressVersionTestnet; } @@ -271,6 +252,14 @@ @implementation BTCPrivateKeyAddress { BOOL _publicKeyCompressed; } ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { + return BTCPrivateKeyAddressVersion; +} + #define BTCPrivateKeyAddressLength 32 + (instancetype) addressWithData:(NSData*)data @@ -354,27 +343,23 @@ - (NSMutableData*) dataForBase58Encoding return data; } -- (unsigned char) versionByte -{ - return BTCPrivateKeyAddressVersion; -} - @end @implementation BTCPrivateKeyAddressTestnet -- (unsigned char) versionByte -{ ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { return BTCPrivateKeyAddressVersionTestnet; } -- (BTCAddress*) publicAddress -{ +- (BTCAddress*) publicAddress { return [BTCPublicKeyAddressTestnet addressWithData:BTCHash160(self.key.publicKey)]; } -- (BOOL) isTestnet -{ +- (BOOL) isTestnet { return YES; } @@ -390,6 +375,15 @@ - (BOOL) isTestnet // P2SH address (e.g. 3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8) @implementation BTCScriptHashAddress ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { + return BTCScriptHashAddressVersion; +} + + #define BTCScriptHashAddressLength 20 + (instancetype) addressWithData:(NSData*)data @@ -427,22 +421,19 @@ - (NSMutableData*) dataForBase58Encoding return data; } -- (unsigned char) versionByte -{ - return BTCScriptHashAddressVersion; -} - @end @implementation BTCScriptHashAddressTestnet -- (unsigned char) versionByte -{ ++ (void) load { + [BTCAddress registerAddressClass:self version:[self BTCVersionPrefix]]; +} + ++ (uint8_t) BTCVersionPrefix { return BTCScriptHashAddressVersionTestnet; } -- (BOOL) isTestnet -{ +- (BOOL) isTestnet { return YES; } diff --git a/CoreBitcoin/BTCAssetAddress.m b/CoreBitcoin/BTCAssetAddress.m index b53dd290..b14ed62e 100644 --- a/CoreBitcoin/BTCAssetAddress.m +++ b/CoreBitcoin/BTCAssetAddress.m @@ -13,17 +13,23 @@ @implementation BTCAssetAddress #define BTCAssetAddressNamespace 0x13 -+ (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress -{ ++ (void) load { + [BTCAddress registerAddressClass:self version:BTCAssetAddressNamespace]; +} + ++ (instancetype) addressWithBitcoinAddress:(BTCAddress*)btcAddress { if (!btcAddress) return nil; BTCAssetAddress* addr = [[self alloc] init]; addr.bitcoinAddress = btcAddress; return addr; } -+ (instancetype) addressWithString:(NSString*)string -{ ++ (instancetype) addressWithString:(NSString*)string { NSMutableData* composedData = BTCDataFromBase58Check(string); + return [self addressWithComposedData:composedData cstring:[string cStringUsingEncoding:NSUTF8StringEncoding]]; +} + ++ (instancetype) addressWithComposedData:(NSData*)composedData cstring:(const char*)cstring { if (!composedData) return nil; if (composedData.length < 2) return nil; @@ -37,8 +43,7 @@ + (instancetype) addressWithString:(NSString*)string } } -- (NSMutableData*) dataForBase58Encoding -{ +- (NSMutableData*) dataForBase58Encoding { NSMutableData* data = [NSMutableData dataWithLength:1]; char* buf = data.mutableBytes; buf[0] = BTCAssetAddressNamespace; @@ -46,8 +51,7 @@ - (NSMutableData*) dataForBase58Encoding return data; } -- (unsigned char) versionByte -{ +- (unsigned char) versionByte { return BTCAssetAddressNamespace; } diff --git a/CoreBitcoin/BTCBitcoinURL.h b/CoreBitcoin/BTCBitcoinURL.h index 8fa5f51d..e736be25 100644 --- a/CoreBitcoin/BTCBitcoinURL.h +++ b/CoreBitcoin/BTCBitcoinURL.h @@ -15,74 +15,89 @@ /*! * Encoded address. */ -@property(nonatomic) BTCAddress* address; +@property(nonatomic, nullable) BTCAddress* address; /*! * Amount in satoshis. Default is 0. */ @property(nonatomic) BTCAmount amount; +/*! + * Asset ID for Open Assets URL. + */ +@property(nonatomic, nullable) NSString* assetID; + /*! * Label. Default is nil. */ -@property(nonatomic) NSString* label; +@property(nonatomic, nullable) NSString* label; /*! * Message. Default is nil. */ -@property(nonatomic) NSString* message; +@property(nonatomic, nullable) NSString* message; /*! * Query parameters. Default is nil. */ -@property(nonatomic) NSDictionary* queryParameters; +@property(nonatomic, nonnull) NSDictionary* queryParameters; /*! * Payment request URL (r=...). Default is nil. */ -@property(nonatomic) NSURL* paymentRequestURL; +@property(nonatomic, nullable) NSURL* paymentRequestURL; /*! * Complete URL built from the individual properties. */ -@property(nonatomic, readonly) NSURL* URL; +@property(nonatomic, readonly, nonnull) NSURL* URL; /*! * Returns YES if it has a valid address or paymentRequestURL. */ @property(nonatomic, readonly) BOOL isValid; +/*! + * Returns YES if it is a pure bitcoin URL. That does not specify asset_id and not uses openassets: scheme. + */ +@property(nonatomic, readonly) BOOL isValidBitcoinURL; + +/*! + * Returns YES if it is an Open Assets URL. + */ +@property(nonatomic, readonly) BOOL isValidOpenAssetsURL; + /*! * Makes a URL in form "bitcoin:
?amount=1.2345&label=