Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync / Signing Protocol Enhancement #1

Closed
3 tasks
dschoeni opened this issue Oct 25, 2018 · 21 comments
Closed
3 tasks

Sync / Signing Protocol Enhancement #1

dschoeni opened this issue Oct 25, 2018 · 21 comments

Comments

@dschoeni
Copy link
Contributor

dschoeni commented Oct 25, 2018

Airgap currently uses a very basic protocol to sync wallets, send requests for signing transactions and broadcast signed transactions.

We propose a new protocol that does replace the existing one with the following design goals:

  • Allow Apps such as AirGap Vault to properly display all necessary information when signing a transaction
  • Make sure to not transmit redundant information, send only what is necessary
  • Sign exactly the data that is sent in the request, also for easy verifiability whether the request was tampered with on the way

The following data is necessary for a user to verify his transaction properly:

  • From-Address (where does this transaction get sent from)
  • To-Address (where does this transaction go)
  • Amount (what value is transmitted in this transaction)
  • Fee (what will this transaction cost me)
  • Protocol (what type of coin/protocol does this transaction rely upon)

Coins/Protocols might add data that is necessary for proper signing such as data in case of Ethereum, or Inputs/Outputs in case of Bitcoin.

General Thoughts

This protocol solves a couple of similar use cases such as EIP 681, although not completely the same. We aim to use the protocol to facilitate communicate between cold-storage wallets / devices and internet-connected devices using a one-way method such as cameras (=> QR Codes). This adds another design goal, size-efficiency. EIP 681 or similar protocols in Bitcoin, rely on the receiver having access to the internet in order to fetch necessary properties (fpr example nonce or UTXO).

The protocol consists of two layers:

  • The basic approach on how serialization and encoding is handled
  • The content of individual payloads, which can differ given its purpose

Basic Approach

Serialization - RLP

We chose RLP as our serialization method due to two reasons:

  • RLP is enjoying more adaption in crypto, with Aeternity adapting RLP as well after Ethereum
  • It is able to serialize and deserialize data in a predictable way
  • Easy to understand and implement
  • Encoded Data as Binary, size-efficient

Encoding in URL - Base64

We use base64 to encode the data when embedded in a URL-Scheme to further shorten the amount of characters necessary in comparison to HEX-Data (Base16).

Example of Protocol Payloads

The type of request that is encoded in the protocol is always determined by the scheme / url that the request is sent to. The data itself does not include information about "what" it is, similar to how an ethereum TX does not do that. As we always have to include some kind of URL-Scheme regardless, this information would be redundant anyways.

1. Syncing a Cryptocurrency Wallet

Version: [number]
Protocol Identifier: [string]
Public Key: [string]
Derivation Path: [string]
isExtendedPublicKey: [1 || 0]

In case of syncing an ETH wallet, this could look sth like this:

[
 [1], // version
 ['eth'], // protocol identifier
 ['03c2c5da503a199294e2354425f9571d060a3a5971b4c61fcdccaf035d0fb18e6d'], // public-key
 ['m/44'/60'/0'/0'], // derivation path
 [0], // whether is an extended publicKey or not
]
[[1],['eth'],['03c2c5da503a199294e2354425f9571d060a3a5971b4c61fcdccaf035d0fb18e6d'],['m/44'/60'/0'/0'],[0]]

Which base64-encoded and embedded in a scheme equals:

airgap-wallet://sync?data=+F/BAcSDZXRo+ES4QjAzYzJjNWRhNTAzYTE5OTI5NGUyMzU0NDI1Zjk1NzFkMDYwYTNhNTk3MWI0YzYxZmNkY2NhZjAzNWQwZmIxOGU2ZM+ObS80NCcvNjAnLzAnLzDBgA==

2. Transaction Signing Request

Version: [number]
Protocol Identifier: [string]
From: [[string]...]
To: [[string]...]
Amount: [BigNumber.toString]
Fee: [BigNumber.toString]
Public Key: [string]
Callback: [string]
Unsigned Transaction: [any]

Given the following Ethereum TX:

{
   'nonce': '0x00',
   'gasPrice': '0x04a817c800',
   'gasLimit': '0x5208',
   'to': '0xf5E54317822EBA2568236EFa7b08065eF15C5d42',
   'value': '0x0de0b6b3a7640000',
   'data': '0x',
   'chainId': 1
}

made from the address 0x7461531f581A662C5dF140FD6eA1317641fFcad2, encoded in RLP this would give us sth like this:

[
 [1], // version
 ['eth'], // protocol identifier
 ['0x7461531f581A662C5dF140FD6eA1317641fFcad2'], // from
 [], // to, in this case we can extract it from the payload
 [], // amount, also possible to extract from the payload
 [], // fee, also possible to extract from the payload
 ['03c2c5da503a199294e2354425f9571d060a3a5971b4c61fcdccaf035d0fb18e6d'], // public-key
 [], // callback scheme
 [
  ['0x00'], //nonce
  ['0x04a817c800'], // gasPrice
  ['0x5208'], // gasLimit
  ['0xf5E54317822EBA2568236EFa7b08065eF15C5d42'], // to
  ['0x0de0b6b3a7640000'], // value
  ['0x'], // data
  [1] // chainId
 ]
]
[[1],['eth'],['0x7461531f581A662C5dF140FD6eA1317641fFcad2'],[],[],[],['03c2c5da503a199294e2354425f9571d060a3a5971b4c61fcdccaf035d0fb18e6d'],[],[['0x00'],['0x04a817c800'],['0x5208'],['0xf5E54317822EBA2568236EFa7b08065eF15C5d42'],['0x0de0b6b3a7640000'],['0x'],[1]]]

Which base64-encoded and embedded in a scheme equals:

airgap-vault://sign?data=+JnBAcSDZXRo1ZR0YVMfWBpmLF3xQP1uoTF2Qf/K0sDAwPhEuEIwM2MyYzVkYTUwM2ExOTkyOTRlMjM1NDQyNWY5NTcxZDA2MGEzYTU5NzFiNGM2MWZjZGNjYWYwMzVkMGZiMThlNmTA8cEAxoUEqBfIAMOCUgjVlPXlQxeCLrolaCNu+nsIBl7xXF1CyYgN4Lazp2QAAMGAwQE=

Open Questions

  • What do we accept as callback scheme?
  • Callback Scheme should probably be whitelisted due to security concerns, what happens if a non-whitelisted scheme is supplied?
  • Does the callback scheme only support custom URL schemes or universal links?
@davidyuk
Copy link

Can I find somewhere a description of the current protocol?

@dschoeni
Copy link
Contributor Author

dschoeni commented Oct 26, 2018

We don't have a formalized description of it, unfortunately. In short, the content we sync is very similar, but we just create a JSON and base64 encode it, so something like this:

'airgap-vault://sign?data=' + btoa(JSON.stringify({
        'protocolIdentifier': 'eth',
        'publicKey': '03c2c5da503a199294e2354425f9571d060a3a5971b4c61fcdccaf035d0fb18e6d',
        'payload': {
          'from': '0x7461531f581A662C5dF140FD6eA1317641fFcad2',
          'nonce': '0x00',
          'gasPrice': '0x04a817c800',
          'gasLimit': '0x5208',
          'to': '0xf5E54317822EBA2568236EFa7b08065eF15C5d42',
          'value': '0x0de0b6b3a7640000',
          'data': '0x',
          'chainId': 1
        }
      }))

This results in a much larger payload due to the inclusion of keys and brought us some problems when deserializing the stuff.

One of our goals is to include the unsigned transactions in a format "as pure as possible", so only the necessary fields of such a TX in unsigned form (or in the case of aeternity, the unsigned TX is already encoded which makes it even easier)

@knarz
Copy link

knarz commented Oct 29, 2018

Sign exactly the data that is sent in the request, also for easy verifiability whether the request was tampered with on the way

How would that be ensured with the described protocol here, if I were to change a function call for an Ethereum contract? For example: I call a token contract at address x and want to transfer tokens to a. Now I change the contract data to send to b instead.

I assume that would be up to the consumer of the signing request to parse calldata etc?

@dschoeni
Copy link
Contributor Author

Our intended way to solve this is to hash the content that gets signed and display it in form of an identicon, on both devices. Users can visually verify this way, that the data generated on device A is the same as the data signed on device B.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

How RLP encoded request is going to be passed by QR codes? In the Base app, I tried to encode binary (raw) data into QR codes but I was not able to find a Cordova package that able to scan them properly.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

size-efficiency is a goal for this protocol, but call data in ethereum in some cases are not size-efficient because function arguments should be aligned by 256-bit EVM words. I think it is reasonable to compress payload before passing it by QR codes.

For example, let's compress payload of this approveAndCall transaction by lzma package:

const lzma = require('lzma');

const txDataHex = 'cae9ca510000000000000000000000004ecd812b010d9db16b0fb7143a79786b65b89b09000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000034616b5f7631325066397657634e357453754e325365464c33526d5955447075377a6565557353754159777947444d57394e583342000000000000000000000000'
const txDataBuffer = Buffer.from(txDataHex, 'hex')
const txDataCompressedBuffer = Buffer.from(lzma.compress(txDataBuffer, 9))

console.log(txDataBuffer.length, txDataCompressedBuffer.length)
$ node main.js 
260 130

So, in this case, the compressed call data is two times shorter than uncompressed.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

Amount of data that can be put into single QR code is limited, is this protocol supposed to define a way to split transaction details into several QR codes?

I have seen some implementation of data streaming by QR codes in ledger-live-common repository, also they have problems with the encoding of binary data into QR codes.

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 5, 2018

@davidyuk
Thanks for the inputs!

We convert the binary data to base64 and write that string into the QR, which works fine with all common QR plugins (see last few lines of the initial post). I think currently there is no vastly better way.

We share the concerns about the payload size in QRs. Generally, I think using compression can only solve the problem until a certain degree, where we need to adapt a splitting mechanism anyway, as the resolution of the QR code will simply make it very hard or unable to scan =/

We have added a wrapper object around the proposed protocol above which we could use in the future to split the payload and scan multiple QRs in order to stitch it back together.

I'll update everything shortly, we should have an implemention draft ready shortly as well :)

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

So, data in QR codes will be also prefixed with airgap-wallet://<verb>?? It was unclear to me.

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 5, 2018

Yes, though the prefix depends in the end on the app you would like to speak to. The unsigned TX will allow you to include a callback url, which determines the prefix. The part will be included in the serialized data as well.

  const deserializedTxSigningRequest: DeserializedSyncProtocol = {
    version: 1,
    protocol: 'eth',
    type: EncodedType.UNSIGNED_TRANSACTION,
    payload: {
      publicKey: protocol.wallet.publicKey,
      callback: 'airgap-wallet://?d=',
      transaction: unsignedTx
    }
  }

This is how it currently looks in one of our test cases, so you can simply use

const serializedTx = await syncProtocol.serialize(deserializedTxSigningRequest)
const deserializedTx = await syncProtocol.deserialize(serializedTx)

Afterwards to serialize/deserialize it. Names etc. are not yet final of course.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

Do you have any agreement on returning a result to callback url? Can we use something like this?

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

base64 should use url-safe charset or special chars (+, /, =) should be encoded for url? https://en.wikipedia.org/wiki/Base64#URL_applications

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 5, 2018

The callback URL can either be a standard link where the serialized content will be appended, lets say https://aeternity.com/broadcast-tx?tx=, or a custom scheme to all apps on phones (airgap-wallet://broadcast?=, depending on who initiated the signing.

The url-safety aspect of base64 is a great input. Do you think it would be better to go with the base64url or simply provide the URL-Encoded base64 string?

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

As I see in the example, actually they are not encoded anyhow. Won't it lead to problems with parsing in some cases?

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 5, 2018

I guess it depends on how you parse the URL (as always), but we take a look at it as its a very valid concern.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

Do you think it would be better to go with the base64url or simply provide the URL-Encoded base64 string?

It depends on the popularity of base64url. I have no opinion on this.

@davidyuk
Copy link

davidyuk commented Nov 5, 2018

using compression can only solve the problem until a certain degree

The same with encoding in RLP.

In general, this protocol looks similar to EIP-67 that was superseded by EIP-681 as representing payment transactions in a high-level fashion. Representation of call data proposed in EIP-681 is clearer for users and more developer friendly. Also, this quality was valuable in BIP-0021.

Usage of compressing algorithms can lead to similar or even smaller payload sizes as when payload encoded into RLP for generation of QR codes. Also, arguments passing in EIP-681 is more size-efficient than argument alignment in EVM call data.

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 5, 2018

We have looked quite a lot at these EIP/BIP, and while they do solve some of the issues, they lack certain information necessary for signing requests on offline devices (ins/outs in bitcoin, nonce in eth). We think a wallet application can still react to schemes such as BIP-0021, collect the necessary information to build the transaction and then use the Sync-Scheme outlined here to send a signing request to a device capable of doing that. So they rather enhance each other than replace, as the use-case is not exactly identical.

Also, most of those schemes are coin-specific while we try to keep our scheme completely coin-agnostic, allowing for arbitrary data to be included in the future (such as new transaction types, new types of coins in general).

@davidyuk
Copy link

davidyuk commented Nov 6, 2018

Is it possible to track nonce on the side of offline signer? If the account is generated by AirGap then nonce should be 0 and it can be increased for every signed transaction.
It will make the recovery process more complicated, but I think it is important to be compatible with existing standards and implementations for easier integration into the existing ecosystem.

to keep our scheme completely coin-agnostic, allowing for arbitrary data to be included in the future

Anyway, support of new coins will require changes in your app at the same time you can implement support of corresponding URL schemas. An ability to define a handler for a specific protocol is enough to support different coins in the future.

@dschoeni
Copy link
Contributor Author

dschoeni commented Nov 6, 2018

Is it possible to track nonce on the side of offline signer?

Not really - If you imagine a scenario where a user decides to import an existing mnemonic, we would need to ask for the nonce every time when creating a new ETH/AE Wallet, as we are unable to guess it. Also, scenarios like overriding transactions with higher fees become more complicated as the vault isn't simply a signing device anymore, but holds state apart from the keys.

Anyway, support of new coins will require changes in your app at the same time you can implement support of corresponding URL schemas. An ability to define a handler for a specific protocol is enough to support different coins in the future.

The idea is that by simply updating the coin-lib in both apps, we are able to incorporate new coins easily. The apps check the supportedProtocols in the library and the whole functionality is generic, there is no coin-specific implementation in the apps currently.

As stated, the problem for coins using UTXO instead of an account based model persists - The current standard simply rely on the assumption that the receiving device has an internet connection / connection to a node to complete the missing information. This is not the case for us.

I can see however, that our apps will be able to react to those schemes as well in order to be compatible, so our Wallet might be able to receive such a BIP-0021 request, create the Unsigned TX from it and then use our scheme to sign the TX on the Vault.

@AndreasGassmann
Copy link
Member

The enhancements discussed in this issue have been implemented some time ago and are documented here: https://airgap-it.github.io/airgap-coin-lib/#/serialization?id=getting-started

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants