diff --git a/ethers/wallet.nim b/ethers/wallet.nim index 900f63f..d22b835 100644 --- a/ethers/wallet.nim +++ b/ethers/wallet.nim @@ -1,11 +1,9 @@ import eth/keys -import eth/rlp -import eth/common -import eth/common/transaction as ct import ./provider -import ./transaction as tx +import ./transaction import ./signer import ./wallet/error +import ./wallet/signing export keys export WalletError @@ -17,8 +15,6 @@ proc getRng: ref HmacDrbgContext = rng = newRng() rng -type SignableTransaction = common.Transaction - type Wallet* = ref object of Signer privateKey*: PrivateKey publicKey*: PublicKey @@ -58,61 +54,13 @@ method provider*(wallet: Wallet): Provider = method getAddress(wallet: Wallet): Future[Address] {.async.} = return wallet.address -proc signTransaction(tr: var SignableTransaction, pk: PrivateKey) = - # Temporary V value, used to signal to the hashing function the - # chain id that we'd like to use for an EIP-155 signature - tr.V = int64(uint64(tr.chainId)) * 2 + 35 - - let h = tr.txHashNoSignature - let s = sign(pk, SkMessage(h.data)) - - let r = toRaw(s) - let v = r[64] - - tr.R = fromBytesBE(UInt256, r.toOpenArray(0, 31)) - tr.S = fromBytesBE(UInt256, r.toOpenArray(32, 63)) - - case tr.txType: - of TxLegacy: - tr.V = int64(v) + int64(uint64(tr.chainId))*2 + 35 - of TxEip1559: - tr.V = int64(v) - else: - raiseWalletError "Transaction type not supported" - -proc signTransaction*(wallet: Wallet, tx: tx.Transaction): Future[seq[byte]] {.async.} = - if sender =? tx.sender and sender != wallet.address: +proc signTransaction*(wallet: Wallet, + transaction: Transaction): Future[seq[byte]] {.async.} = + if sender =? transaction.sender and sender != wallet.address: raiseWalletError "from address mismatch" - without nonce =? tx.nonce and chainId =? tx.chainId and gasLimit =? tx.gasLimit: - raiseWalletError "Transaction is not properly populated" + return wallet.privateKey.sign(transaction) - var s: SignableTransaction - - if maxFee =? tx.maxFee and maxPriorityFee =? tx.maxPriorityFee: - s.txType = TxEip1559 - s.maxFee = GasInt(maxFee.truncate(uint64)) - s.maxPriorityFee = GasInt(maxPriorityFee.truncate(uint64)) - elif gasPrice =? tx.gasPrice: - s.txType = TxLegacy - s.gasPrice = GasInt(gasPrice.truncate(uint64)) - else: - raiseWalletError "Transaction is not properly populated" - - s.chainId = ChainId(chainId.truncate(uint64)) - s.gasLimit = GasInt(gasLimit.truncate(uint64)) - s.value = tx.value - s.nonce = nonce.truncate(uint64) - s.to = some EthAddress(tx.to) - s.payload = tx.data - signTransaction(s, wallet.privateKey) - - return rlp.encode(s) - -method sendTransaction*(wallet: Wallet, tx: tx.Transaction): Future[TransactionResponse] {.async.} = - let rawTX = await signTransaction(wallet, tx) - return await provider(wallet).sendTransaction(rawTX) - -#TODO add functionality to sign messages - -#TODO add functionality to create wallets from Mnemoniks or Keystores +method sendTransaction*(wallet: Wallet, transaction: Transaction): Future[TransactionResponse] {.async.} = + let signed = await signTransaction(wallet, transaction) + return await provider(wallet).sendTransaction(signed) diff --git a/ethers/wallet/signing.nim b/ethers/wallet/signing.nim new file mode 100644 index 0000000..b56e406 --- /dev/null +++ b/ethers/wallet/signing.nim @@ -0,0 +1,64 @@ +import pkg/eth/keys +import pkg/eth/rlp +import pkg/eth/common/transaction as eth +import ../basics +import ../transaction as ethers +import ./error + +type + Transaction = ethers.Transaction + SignableTransaction = eth.Transaction + +func toSignableTransaction(transaction: Transaction): SignableTransaction = + var signable: SignableTransaction + + without nonce =? transaction.nonce: + raiseWalletError "missing nonce" + + without chainId =? transaction.chainId: + raiseWalletError "missing chain id" + + without gasLimit =? transaction.gasLimit: + raiseWalletError "missing gas limit" + + signable.nonce = nonce.truncate(uint64) + signable.chainId = ChainId(chainId.truncate(uint64)) + signable.gasLimit = GasInt(gasLimit.truncate(uint64)) + signable.to = some EthAddress(transaction.to) + signable.value = transaction.value + signable.payload = transaction.data + + if maxFee =? transaction.maxFee and + maxPriorityFee =? transaction.maxPriorityFee: + signable.txType = TxEip1559 + signable.maxFee = GasInt(maxFee.truncate(uint64)) + signable.maxPriorityFee = GasInt(maxPriorityFee.truncate(uint64)) + elif gasPrice =? transaction.gasPrice: + signable.txType = TxLegacy + signable.gasPrice = GasInt(gasPrice.truncate(uint64)) + else: + raiseWalletError "missing gas price" + + signable + +func sign(key: PrivateKey, transaction: SignableTransaction): seq[byte] = + var transaction = transaction + + # Temporary V value, used to signal to the hashing function + # that we'd like to use an EIP-155 signature + transaction.V = int64(uint64(transaction.chainId)) * 2 + 35 + + let hash = transaction.txHashNoSignature().data + let signature = key.sign(SkMessage(hash)).toRaw() + + transaction.R = UInt256.fromBytesBE(signature[0..<32]) + transaction.S = UInt256.fromBytesBE(signature[32..<64]) + transaction.V = int64(signature[64]) + + if transaction.txType == TxLegacy: + transaction.V += int64(uint64(transaction.chainId)) * 2 + 35 + + rlp.encode(transaction) + +func sign*(key: PrivateKey, transaction: Transaction): seq[byte] = + key.sign(transaction.toSignableTransaction())