Skip to content
This repository has been archived by the owner on May 28, 2019. It is now read-only.

Add transaction signing #7

Merged
merged 3 commits into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Sources/RLP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ public struct RLP {
return encodeInt(number)
case let number as Int64:
return encodeInt64(number)
case let number as UInt64:
return encodeUInt64(number)
case let bigint as BigInt:
return encodeBigInt(bigint)
case let biguint as BigUInt:
return encodeBigUInt(biguint)
case let transaction as Transaction:
return encodeTransaction(transaction)
case let data as Data:
return encodeData(data)
default:
Expand Down Expand Up @@ -86,6 +90,20 @@ public struct RLP {
return encodeData(encoded)
}

static func encodeTransaction(_ transaction: Transaction) -> Data? {
return encodeList([
transaction.nonce,
transaction.gasPrice,
transaction.gasLimit,
transaction.to.data,
transaction.amount,
transaction.payload ?? Data(),
transaction.v,
transaction.r,
transaction.s,
])
}

static func encodeData(_ data: Data) -> Data {
if data.count == 1 && data[0] <= 0x7f {
// Fits in single byte, no header
Expand Down
41 changes: 34 additions & 7 deletions Sources/Transaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,42 @@ import BigInt

/// Ethereum transaction.
public struct Transaction {
public var accountNonce: UInt64
public var price: BigInt
public var gasLimit: BigInt
public var recipient: Address?
public var nonce: UInt64
public var gasPrice: BigInt
public var gasLimit: UInt64
public var to: Address
public var amount: BigInt
public var payload: Data?

// Signature values
public var v: BigInt
public var r: BigInt
public var s: BigInt
public var v = BigInt()
public var r = BigInt()
public var s = BigInt()

/// Creates a `Transaction`.
public init(gasPrice: BigInt, gasLimit: UInt64, to: Address) {
nonce = 0
self.gasPrice = gasPrice
self.gasLimit = gasLimit
self.to = to
amount = BigInt()
}

/// Signs this transaction by filling in the `v`, `r`, and `s` values.
///
/// - Parameters:
/// - chainID: chain identifier, defaults to `1`
/// - hashSigner: function to use for signing the hash
public mutating func sign(chainID: Int = 1, hashSigner: (Data) throws -> Data) rethrows {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I would ditch HomesteadSigner at all. everyone use EIP155 for this stuff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is based on geth. I don't think it's harmful to have.

let signer: Signer
if chainID == 0 {
signer = HomesteadSigner()
} else {
signer = EIP155Signer(chainID: BigInt(chainID))
}

let hash = signer.hash(transaction: self)
let signature = try hashSigner(hash)
(r, s, v) = signer.values(transaction: self, signature: signature)
}
}
71 changes: 71 additions & 0 deletions Sources/TransactionSigning.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright © 2017-2018 Trust.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

import BigInt

protocol Signer {
func hash(transaction: Transaction) -> Data
func values(transaction: Transaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt)
}

struct EIP155Signer: Signer {
let chainID: BigInt

init(chainID: BigInt) {
self.chainID = chainID
}

func hash(transaction: Transaction) -> Data {
return rlpHash([
transaction.nonce,
transaction.gasPrice,
transaction.gasLimit,
transaction.to.data,
transaction.amount,
transaction.payload ?? Data(),
chainID, 0, 0,
] as [Any])!
}

func values(transaction: Transaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
let (r, s, v) = HomesteadSigner().values(transaction: transaction, signature: signature)
let newV: BigInt
if chainID != 0 {
newV = BigInt(signature[64]) + 35 + chainID + chainID
} else {
newV = v
}
return (r, s, newV)
}
}

struct HomesteadSigner: Signer {
func hash(transaction: Transaction) -> Data {
return rlpHash([
transaction.nonce,
transaction.gasPrice,
transaction.gasLimit,
transaction.to.data,
transaction.amount,
transaction.payload ?? Data(),
])!
}

func values(transaction: Transaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
precondition(signature.count == 65, "Wrong size for signature")
let r = BigInt(sign: .plus, magnitude: BigUInt(Data(signature[..<32])))
let s = BigInt(sign: .plus, magnitude: BigUInt(Data(signature[32..<64])))
let v = BigInt(sign: .plus, magnitude: BigUInt(Data(bytes: [signature[64] + 27])))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember there was something where we wanted to make 27 customizable? can't remember

return (r, s, v)
}
}

func rlpHash(_ element: Any) -> Data? {
guard let data = RLP.encode(element) else {
return nil
}
return EthereumCrypto.hash(data)
}
50 changes: 50 additions & 0 deletions Tests/TransactionSigningTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright © 2017-2018 Trust.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

import BigInt
import TrustCore
import XCTest

class TransactionSigningTests: XCTestCase {
func testEIP155SignHash() {
let address = Address(string: "0x3535353535353535353535353535353535353535")!
var transaction = Transaction(gasPrice: 20000000000, gasLimit: 21000, to: address)
transaction.nonce = 9
transaction.amount = BigInt("1000000000000000000")

transaction.sign { hash in
XCTAssertEqual(hash.hexString, "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
return Data(repeating: 0, count: 65)
}
}

func testHomesteadSignHash() {
let address = Address(string: "0x3535353535353535353535353535353535353535")!
var transaction = Transaction(gasPrice: 20000000000, gasLimit: 21000, to: address)
transaction.nonce = 9
transaction.amount = BigInt("1000000000000000000")

transaction.sign(chainID: 0) { hash in
XCTAssertEqual(hash.hexString, "f9e36c28c8cb35adba138005c02ab7aa7fbcd891f3139cb2eeed052a51cd2713")
return Data(repeating: 0, count: 65)
}
}

func testSignTransaction() {
var transaction = Transaction(gasPrice: 20000000000, gasLimit: 21000, to: Address(string: "0x3535353535353535353535353535353535353535")!)
transaction.nonce = 9
transaction.amount = BigInt("1000000000000000000")

transaction.sign { hash in
XCTAssertEqual(hash.hexString, "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
return Data(hexString: "28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa63627667cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d8300")!
}

XCTAssertEqual(transaction.v, BigInt(37))
XCTAssertEqual(transaction.r, BigInt("18515461264373351373200002665853028612451056578545711640558177340181847433846"))
XCTAssertEqual(transaction.s, BigInt("46948507304638947509940763649030358759909902576025900602547168820602576006531"))
}
}
8 changes: 8 additions & 0 deletions TrustCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
6149A6CC201E496A00A2F35C /* DataHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6149A6CB201E496900A2F35C /* DataHex.swift */; };
6149A6CE201E499B00A2F35C /* DataHexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6149A6CD201E499B00A2F35C /* DataHexTests.swift */; };
6149A6D0201E4B6B00A2F35C /* AddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6149A6CF201E4B6B00A2F35C /* AddressTests.swift */; };
61555863208ED00A00FB241E /* TransactionSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61555862208ED00A00FB241E /* TransactionSigning.swift */; };
61555865208ED04F00FB241E /* TransactionSigningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61555864208ED04E00FB241E /* TransactionSigningTests.swift */; };
61645D142058A74E00411565 /* EthereumCryptoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61645D132058A74E00411565 /* EthereumCryptoTests.swift */; };
61645D162058BCD500411565 /* EthereumCrypto.m in Sources */ = {isa = PBXBuildFile; fileRef = 61645D152058BCD500411565 /* EthereumCrypto.m */; };
61645D182058BF2E00411565 /* EthereumCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 61645D172058BD6800411565 /* EthereumCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -54,6 +56,8 @@
6149A6CB201E496900A2F35C /* DataHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataHex.swift; sourceTree = "<group>"; };
6149A6CD201E499B00A2F35C /* DataHexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataHexTests.swift; sourceTree = "<group>"; };
6149A6CF201E4B6B00A2F35C /* AddressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressTests.swift; sourceTree = "<group>"; };
61555862208ED00A00FB241E /* TransactionSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSigning.swift; sourceTree = "<group>"; };
61555864208ED04E00FB241E /* TransactionSigningTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSigningTests.swift; sourceTree = "<group>"; };
61645D0D2058A06700411565 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
61645D132058A74E00411565 /* EthereumCryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumCryptoTests.swift; sourceTree = "<group>"; };
61645D152058BCD500411565 /* EthereumCrypto.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EthereumCrypto.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -131,6 +135,7 @@
6149A6CB201E496900A2F35C /* DataHex.swift */,
61645D192058C7B300411565 /* RLP.swift */,
6149A6C9201E472000A2F35C /* Transaction.swift */,
61555862208ED00A00FB241E /* TransactionSigning.swift */,
61675571207AD82900018DC8 /* Solidity */,
);
path = Sources;
Expand All @@ -153,6 +158,7 @@
61645D132058A74E00411565 /* EthereumCryptoTests.swift */,
61675572207ADB9B00018DC8 /* FunctionTests.swift */,
61645D1B2058C86B00411565 /* RLPTests.swift */,
61555864208ED04E00FB241E /* TransactionSigningTests.swift */,
6167558B207C7A1F00018DC8 /* Solidity */,
);
path = Tests;
Expand Down Expand Up @@ -428,6 +434,7 @@
61645D162058BCD500411565 /* EthereumCrypto.m in Sources */,
6149A6CA201E472000A2F35C /* Transaction.swift in Sources */,
6167556E207ABE6700018DC8 /* ABIValue.swift in Sources */,
61555863208ED00A00FB241E /* TransactionSigning.swift in Sources */,
6149A6C8201E455D00A2F35C /* Address.swift in Sources */,
6149A6CC201E496A00A2F35C /* DataHex.swift in Sources */,
61645D1A2058C7B300411565 /* RLP.swift in Sources */,
Expand All @@ -442,6 +449,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
61555865208ED04F00FB241E /* TransactionSigningTests.swift in Sources */,
6149A6D0201E4B6B00A2F35C /* AddressTests.swift in Sources */,
61675573207ADB9B00018DC8 /* FunctionTests.swift in Sources */,
61645D142058A74E00411565 /* EthereumCryptoTests.swift in Sources */,
Expand Down