Skip to content

Commit

Permalink
Implement RLP encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandro-isaza authored and Michael Scoff committed Jan 8, 2018
1 parent 282ded8 commit f134b96
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 10 deletions.
26 changes: 16 additions & 10 deletions Trust.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@
615F10591FCBEF7C008A45AF /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615F10581FCBEF7C008A45AF /* OnboardingPage.swift */; };
615F105D1FCBF55E008A45AF /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615F105C1FCBF55E008A45AF /* OnboardingCollectionViewController.swift */; };
615F10621FCD3F0F008A45AF /* OnboardingPageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615F10611FCD3F0F008A45AF /* OnboardingPageStyle.swift */; };
61DCE17B2001A6BE0053939F /* RLP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCE17A2001A6BE0053939F /* RLP.swift */; };
61DCE17D2001A7A20053939F /* RLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCE17C2001A7A20053939F /* RLPTests.swift */; };
61F8AA931FCA4A0F00488C6E /* BigInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F8AA921FCA4A0F00488C6E /* BigInt.swift */; };
61F8AA951FCA4C9300488C6E /* BigIntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F8AA941FCA4C9300488C6E /* BigIntTests.swift */; };
61FC5ECF1FCFBAE500CCB12A /* EtherNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61FC5ECE1FCFBAE500CCB12A /* EtherNumberFormatter.swift */; };
Expand Down Expand Up @@ -613,6 +615,8 @@
615F10581FCBEF7C008A45AF /* OnboardingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = "<group>"; };
615F105C1FCBF55E008A45AF /* OnboardingCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingCollectionViewController.swift; sourceTree = "<group>"; };
615F10611FCD3F0F008A45AF /* OnboardingPageStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageStyle.swift; sourceTree = "<group>"; };
61DCE17A2001A6BE0053939F /* RLP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RLP.swift; sourceTree = "<group>"; };
61DCE17C2001A7A20053939F /* RLPTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RLPTests.swift; sourceTree = "<group>"; };
61F8AA921FCA4A0F00488C6E /* BigInt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigInt.swift; sourceTree = "<group>"; };
61F8AA941FCA4C9300488C6E /* BigIntTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigIntTests.swift; sourceTree = "<group>"; };
61FC5ECE1FCFBAE500CCB12A /* EtherNumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtherNumberFormatter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -863,6 +867,7 @@
children = (
291E8FBE1F7DEA85003F0ECF /* EtherKeystoreTests.swift */,
2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */,
61DCE17C2001A7A20053939F /* RLPTests.swift */,
);
path = EtherClient;
sourceTree = "<group>";
Expand Down Expand Up @@ -902,19 +907,20 @@
291F52A01F6B6DBC00B369AB /* EtherClient */ = {
isa = PBXGroup;
children = (
2959961D1FAE756400DB66A8 /* TrustClient */,
2963B6B41F9A7E13003063C1 /* CoinMarket */,
29282B561F7636600067F88D /* Ethplorer */,
291F52A31F6B760A00B369AB /* Requests */,
291F52A11F6B6DCF00B369AB /* EtherClient.swift */,
291F52A41F6B762300B369AB /* EtherServiceRequest.swift */,
2959961D1FAE756400DB66A8 /* TrustClient */,
291F52B61F6B870400B369AB /* CastError.swift */,
2961BD061FB146EB00C4B840 /* ChainState.swift */,
291F52A11F6B6DCF00B369AB /* EtherClient.swift */,
291F52B81F6B880F00B369AB /* EtherKeystore.swift */,
291ED08C1F6F5F0A00E7E93A /* KeyStoreError.swift */,
29FF12FD1F75EA3F00AFD326 /* Keystore.swift */,
291F52A41F6B762300B369AB /* EtherServiceRequest.swift */,
29F114ED1FA65DEF00114A29 /* ImportType.swift */,
2961BD061FB146EB00C4B840 /* ChainState.swift */,
29FF12FD1F75EA3F00AFD326 /* Keystore.swift */,
291ED08C1F6F5F0A00E7E93A /* KeyStoreError.swift */,
2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */,
61DCE17A2001A6BE0053939F /* RLP.swift */,
);
path = EtherClient;
sourceTree = "<group>";
Expand Down Expand Up @@ -2380,6 +2386,7 @@
294EC1D81FD7FBAB0065EB20 /* BiometryAuthenticationType.swift in Sources */,
29E6E0721FEA200D0079265A /* ConfirmPaymentDetailsViewModel.swift in Sources */,
2931121B1FC560E500966EEA /* ExchangeTokenTableViewCell.swift in Sources */,
61DCE17B2001A6BE0053939F /* RLP.swift in Sources */,
293112261FC8154E00966EEA /* ContractExchangeTrade.swift in Sources */,
29E6E0701FEA12910079265A /* TransactionConfigurator.swift in Sources */,
299B5E3B1FD141B70051361C /* BackupViewModel.swift in Sources */,
Expand Down Expand Up @@ -2513,6 +2520,7 @@
2912CD101F6A830700C6CBE3 /* TrustTests.swift in Sources */,
29FF130D1F7626E800AFD326 /* FakeNavigationController.swift in Sources */,
293112311FC971B600966EEA /* ExchangeTokensCoordinatorTests.swift in Sources */,
61DCE17D2001A7A20053939F /* RLPTests.swift in Sources */,
61F8AA951FCA4C9300488C6E /* BigIntTests.swift in Sources */,
299573A41FA27A15006F17FD /* TestKeyStore.swift in Sources */,
CCA4FE3A1FD42B4100749AE4 /* FakeJailbreakChecker.swift in Sources */,
Expand Down Expand Up @@ -2640,7 +2648,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -2695,7 +2703,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
Expand All @@ -2719,7 +2727,6 @@
DEVELOPMENT_TEAM = 9873B38DWV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Trust/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.sixdays.trust;
Expand All @@ -2744,7 +2751,6 @@
DEVELOPMENT_TEAM = 9873B38DWV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Trust/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.sixdays.trust;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
167 changes: 167 additions & 0 deletions Trust/EtherClient/RLP.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright SIX DAY LLC. All rights reserved.

import Foundation
import BigInt

/// Implementation of Ethereum's RLP encoding.
///
/// - SeeAlso: https://github.com/ethereum/wiki/wiki/RLP
public struct RLP {
/// Encodes an element as RLP data.
///
/// - Returns: Encoded data or `nil` if the element type is not supported.
public static func encode(_ element: Any) -> Data? {
switch element {
case let string as String:
return encodeString(string)
case let list as [Any]:
return encodeList(list)
case let number as Int:
return encodeInt(number)
case let bigint as BigInt:
return encodeBigInt(bigint)
case let biguint as BigUInt:
return encodeBigUInt(biguint)
case let data as Data:
return encodeData(data)
default:
return nil
}
}

static func encodeString(_ string: String) -> Data? {
guard let data = string.data(using: .utf8) else {
return nil
}
return encodeData(data)
}

static func encodeInt(_ number: Int) -> Data? {
guard number >= 0 else {
return nil // RLP cannot encode negative numbers
}
let uint = UInt(bitPattern: number)
return encodeUInt(uint)
}

static func encodeUInt(_ number: UInt) -> Data? {
let biguint = BigUInt(number)
return encode(biguint)
}

static func encodeBigInt(_ number: BigInt) -> Data? {
guard number.sign == .plus else {
return nil // RLP cannot encode negative BigInts
}
return encodeBigUInt(number.magnitude)
}

static func encodeBigUInt(_ number: BigUInt) -> Data? {
let encoded = number.serialize()
if encoded.isEmpty {
return Data(bytes: [0x80])
}
return encodeData(encoded)
}

static func encodeData(_ data: Data) -> Data {
if data.count == 1 && data[0] <= 0x7f {
// Fits in single byte, no header
return data
}

var encoded = encodeHeader(size: UInt64(data.count), smallTag: 0x80, largeTag: 0xb7)
encoded.append(data)
return encoded
}

static func encodeList(_ elements: [Any]) -> Data? {
var encodedData = Data()
for el in elements {
guard let encoded = encode(el) else {
return nil
}
encodedData.append(encoded)
}

var encoded = encodeHeader(size: UInt64(encodedData.count), smallTag: 0xc0, largeTag: 0xf7)
encoded.append(encodedData)
return encoded
}

static func encodeHeader(size: UInt64, smallTag: UInt8, largeTag: UInt8) -> Data {
if size < 56 {
return Data([smallTag + UInt8(size)])
}

let sizeData = putint(size)
var encoded = Data()
encoded.append(largeTag + UInt8(sizeData.count))
encoded.append(contentsOf: sizeData)
return encoded
}

/// Returns the representation of an integer using the least number of bytes needed.
static func putint(_ i: UInt64) -> Data {
switch i {
case 0 ..< (1 << 8):
return Data([UInt8(i)])
case 0 ..< (1 << 16):
return Data([
UInt8(i >> 8),
UInt8(truncatingIfNeeded: i),
])
case 0 ..< (1 << 24):
return Data([
UInt8(i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
case 0 ..< (1 << 32):
return Data([
UInt8(i >> 24),
UInt8(truncatingIfNeeded: i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
case 0 ..< (1 << 40):
return Data([
UInt8(i >> 32),
UInt8(truncatingIfNeeded: i >> 24),
UInt8(truncatingIfNeeded: i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
case 0 ..< (1 << 48):
return Data([
UInt8(i >> 40),
UInt8(truncatingIfNeeded: i >> 32),
UInt8(truncatingIfNeeded: i >> 24),
UInt8(truncatingIfNeeded: i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
case 0 ..< (1 << 56):
return Data([
UInt8(i >> 48),
UInt8(truncatingIfNeeded: i >> 40),
UInt8(truncatingIfNeeded: i >> 32),
UInt8(truncatingIfNeeded: i >> 24),
UInt8(truncatingIfNeeded: i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
default:
return Data([
UInt8(i >> 56),
UInt8(truncatingIfNeeded: i >> 48),
UInt8(truncatingIfNeeded: i >> 40),
UInt8(truncatingIfNeeded: i >> 32),
UInt8(truncatingIfNeeded: i >> 24),
UInt8(truncatingIfNeeded: i >> 16),
UInt8(truncatingIfNeeded: i >> 8),
UInt8(truncatingIfNeeded: i),
])
}
}
}
59 changes: 59 additions & 0 deletions TrustTests/EtherClient/RLPTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright SIX DAY LLC. All rights reserved.

import BigInt
@testable import Trust
import XCTest

class RLPTests: XCTestCase {
func testStrings() {
XCTAssertEqual(RLP.encode("")!.hex, "80")
XCTAssertEqual(RLP.encode("dog")!.hex, "83646f67")
}

func testIntegers() {
XCTAssertEqual(RLP.encode(0)!.hex, "80")
XCTAssertEqual(RLP.encode(127)!.hex, "7f")
XCTAssertEqual(RLP.encode(128)!.hex, "8180")
XCTAssertEqual(RLP.encode(256)!.hex, "820100")
XCTAssertEqual(RLP.encode(1024)!.hex, "820400")
XCTAssertEqual(RLP.encode(0xffffff)!.hex, "83ffffff")
XCTAssertEqual(RLP.encode(0xffffffff)!.hex, "84ffffffff")
XCTAssertEqual(RLP.encode(0xffffffffffffff)!.hex, "87ffffffffffffff")
}

func testBigInts() {
XCTAssertEqual(RLP.encode(BigInt(0))!.hex, "80")
XCTAssertEqual(RLP.encode(BigInt(1))!.hex, "01")
XCTAssertEqual(RLP.encode(BigInt(127))!.hex, "7f")
XCTAssertEqual(RLP.encode(BigInt(128))!.hex, "8180")
XCTAssertEqual(RLP.encode(BigInt(256))!.hex, "820100")
XCTAssertEqual(RLP.encode(BigInt(1024))!.hex, "820400")
XCTAssertEqual(RLP.encode(BigInt(0xffffff))!.hex, "83ffffff")
XCTAssertEqual(RLP.encode(BigInt(0xffffffff))!.hex, "84ffffffff")
XCTAssertEqual(RLP.encode(BigInt(0xffffffffffffff))!.hex, "87ffffffffffffff")
XCTAssertEqual(
RLP.encode(BigInt("102030405060708090a0b0c0d0e0f2", radix: 16)!)!.hex,
"8f102030405060708090a0b0c0d0e0f2"
)
XCTAssertEqual(
RLP.encode(BigInt("0100020003000400050006000700080009000a000b000c000d000e01", radix: 16)!)!.hex,
"9c0100020003000400050006000700080009000a000b000c000d000e01"
)
XCTAssertEqual(
RLP.encode(BigInt("010000000000000000000000000000000000000000000000000000000000000000", radix: 16)!)!.hex,
"a1010000000000000000000000000000000000000000000000000000000000000000"
)
XCTAssertNil(RLP.encode(BigInt("-1")!))
}

func testLists() {
XCTAssertEqual(RLP.encode([])!.hex, "c0")
XCTAssertEqual(RLP.encode([1, 2, 3])!.hex, "c3010203")
XCTAssertEqual(RLP.encode(["cat", "dog"])!.hex, "c88363617483646f67")
XCTAssertEqual(RLP.encode([ [], [[]], [ [], [[]] ] ])!.hex, "c7c0c1c0c3c0c1c0")
XCTAssertEqual(RLP.encode([1, 0xffffff, [4, 5, 5], "abc"])!.hex, "cd0183ffffffc304050583616263")
let encoded = RLP.encode(Array<Int>(repeating: 0, count: 1024))!
print(encoded.hex)
XCTAssert(encoded.hex.hasPrefix("f90400"))
}
}

0 comments on commit f134b96

Please sign in to comment.