From f9b341f7c55ad67311d0ee66a797b847246515aa Mon Sep 17 00:00:00 2001 From: Arnaud Brousseau <rno@turnkey.io> Date: Thu, 22 Feb 2024 15:40:22 -0600 Subject: [PATCH] Add message signing example --- .ruby-version | 1 + README.md | 1 + examples/signing/.env.example | 6 +++ examples/signing/Gemfile | 4 ++ examples/signing/Gemfile.lock | 29 ++++++++++++ examples/signing/README.md | 22 +++++++++ examples/signing/signing.rb | 85 +++++++++++++++++++++++++++++++++++ 7 files changed, 148 insertions(+) create mode 100644 .ruby-version create mode 100644 examples/signing/.env.example create mode 100644 examples/signing/Gemfile create mode 100644 examples/signing/Gemfile.lock create mode 100644 examples/signing/README.md create mode 100644 examples/signing/signing.rb diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..9f55b2c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.0 diff --git a/README.md b/README.md index 514016b..60e920f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ See the [examples](./examples/) folder if you're looking for a complete sample p ## Examples * [Whoami](./examples/whoami/): example showcasing API request signing in its simplest form +* [Signing](./examples/whoami/): example showcasing wallet creation and ETH message signing ## Using Turnkey in your Rails projects diff --git a/examples/signing/.env.example b/examples/signing/.env.example new file mode 100644 index 0000000..34b4a2b --- /dev/null +++ b/examples/signing/.env.example @@ -0,0 +1,6 @@ +# If you do not have an organization, create one at https://app.turnkey.com/dashboard/auth/initial +TURNKEY_ORGANIZATION_ID=<your-organization-id> +# API credentials. They need to be attached to a user in your organization. +# These credentials must be a valid hex-encoded public/private key pair (P256) +TURNKEY_API_PUBLIC_KEY=<your-public-key> +TURNKEY_API_PRIVATE_KEY=<your-private-key> diff --git a/examples/signing/Gemfile b/examples/signing/Gemfile new file mode 100644 index 0000000..7f3948d --- /dev/null +++ b/examples/signing/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'dotenv' +gem 'turnkey_client', path: '../../turnkey_client/' diff --git a/examples/signing/Gemfile.lock b/examples/signing/Gemfile.lock new file mode 100644 index 0000000..dc089b4 --- /dev/null +++ b/examples/signing/Gemfile.lock @@ -0,0 +1,29 @@ +PATH + remote: ../../turnkey_client + specs: + turnkey_client (0.0.1) + json (~> 2.1, >= 2.1.0) + openssl (~> 3.2, >= 3.0.0) + typhoeus (~> 1.0, >= 1.0.1) + +GEM + remote: https://rubygems.org/ + specs: + dotenv (3.0.2) + ethon (0.16.0) + ffi (>= 1.15.0) + ffi (1.16.3) + json (2.7.1) + openssl (3.2.0) + typhoeus (1.4.1) + ethon (>= 0.9.0) + +PLATFORMS + arm64-darwin-23 + +DEPENDENCIES + dotenv + turnkey_client! + +BUNDLED WITH + 2.2.3 diff --git a/examples/signing/README.md b/examples/signing/README.md new file mode 100644 index 0000000..a8f497b --- /dev/null +++ b/examples/signing/README.md @@ -0,0 +1,22 @@ +# Who am I? + +This examples shows how to use `turnkey_client` to create a new wallet and sign a message with it. + +To run this example: +* `cp .env.example .env` +* Follow the instructions in the `.env` file to fill in values for `TURNKEY_ORGANIZATION_ID`, `TURNKEY_API_PUBLIC_KEY`, and `TURNKEY_API_PRIVATE_KEY` +* Install the dependencies: `bundle install` +* Then run the `signing.rb` script: `bundle exec ruby signing.rb` + +You should be able to verify the signature produced via Etherscan. Here's an example run: + +``` +created new wallet successfully +successful signature +- address: 0xf0635c94F7bEEcF9abC77F512Bf33BB66Fe9997f +- message: "hello from Turnkey, in Ruby!" +- signature hash: 0x9b55914814658b4cb592ed6782f2e639b28b89d122240c34f7ea5d7afd48c5861271b008ae62d15131cdd95fbc6a8498ea8379e20ad8fa0956dfd207c6def1d21b +You can verify for yourself at https://etherscan.io/verifiedSignatures > "Verify Signature" +``` + +Proof of validity: https://etherscan.io/verifySig/36331 diff --git a/examples/signing/signing.rb b/examples/signing/signing.rb new file mode 100644 index 0000000..e651276 --- /dev/null +++ b/examples/signing/signing.rb @@ -0,0 +1,85 @@ +require 'turnkey_client' +require 'dotenv' +require 'json' +require 'date' + +# Load local .env file +Dotenv.load + +raise 'Please set TURNKEY_ORGANIZATION_ID in your .env file' if ENV['TURNKEY_ORGANIZATION_ID'].nil? + +# Make a whoami request +begin + client = TurnkeyClient.configure do |c| + c.api_public_key = ENV['TURNKEY_API_PUBLIC_KEY'] + c.api_private_key = ENV['TURNKEY_API_PRIVATE_KEY'] + end + + # https://docs.turnkey.com/api#tag/Wallets/operation/CreateWallet + now_in_ms = DateTime.now.strftime('%Q') + + # We can also use `TurnkeyClient::V1CreateWalletRequest` to create the request object here + create_wallet_response = TurnkeyClient::WalletsApi.new(client).create_wallet( + { + type: 'ACTIVITY_TYPE_CREATE_WALLET', + organizationId: ENV['TURNKEY_ORGANIZATION_ID'], + timestampMs: DateTime.now.strftime('%Q'), + parameters: { + walletName: "Wallet @#{now_in_ms}", + mnemonicLength: 12, + accounts: [ + { + curve: 'CURVE_SECP256K1', + pathFormat: 'PATH_FORMAT_BIP32', + # Default ETH account + path: "m/44'/60'/0'/0/0", + addressFormat: 'ADDRESS_FORMAT_ETHEREUM' + } + ] + } + } + ) + + created_wallet = create_wallet_response&.activity&.dig(:result, :createWalletResult) + raise "Something went wrong: no wallet in activity response: #{create_wallet_response.to_hash}" if created_wallet.nil? + + puts 'created new wallet successfully' + + eth_address = created_wallet[:addresses].first + + # Now sign! + # https://docs.turnkey.com/api#tag/Signing/operation/SignRawPayload + message = 'hello from Turnkey, in Ruby!' + eth_message = "\u0019Ethereum Signed Message:\n#{message.size}#{message}" + + signature_response = TurnkeyClient::SigningApi.new(client).sign_raw_payload( + { + type: 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2', + organizationId: ENV['TURNKEY_ORGANIZATION_ID'], + timestampMs: DateTime.now.strftime('%Q'), + parameters: { + signWith: eth_address, + payload: eth_message, + encoding: 'PAYLOAD_ENCODING_TEXT_UTF8', + hashFunction: 'HASH_FUNCTION_KECCAK256' + } + } + ) + + signature = signature_response&.activity&.dig(:result, :signRawPayloadResult) + raise "Something went wrong: no signature in activity response: #{signature_response.to_hash}" if signature.nil? + + puts 'successful signature' + + eth_v = if signature[:v] == '00' + '1b' # 27 in hex + else + '1c' # 28 in hex + end + puts "- address: #{eth_address}" + puts "- message: \"#{message}\"" + puts "- signature hash: 0x#{signature[:r]}#{signature[:s]}#{eth_v}" + puts 'You can verify for yourself at https://etherscan.io/verifiedSignatures > "Verify Signature"' +rescue TurnkeyClient::ApiError => e + puts "Exception when calling Whoami endpoint: #{e}" +end