forked from trustwallet/trust-wallet-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
282ded8
commit f134b96
Showing
3 changed files
with
242 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
]) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | ||
} | ||
} |