This post is part of the Infineon Blockchain Starter Kit road test.
Blockchain - outside of the bitcoin context - is new to me.
Follow along with me on this path to learn the technology....
Let's dive further into the example Android App "coinfinity". This is the real thing: a transaction. Send crypto currency to another account. |
Process in Simple Steps
This is the biggest step in the series. Authorising the movement of currency with an Infineon smart card.
It's a 4 step process:
- sender initiates the transaction, by tapping the card and choose the Send button (this is not critial. Scanning a QR code or typing an address works too).
- someone types how much Ethereum will be transfered.
- receiver makes himself known, by tapping the card (this is not critial. Scanning a QR code or typing an address works too).
- sender approves by tapping the card. The card will calculate the hash for the transaction.
You can see that there's only one critical step: approving the transaction.
There are many ways to indicate the sender and receiver of a money transfer. In the end both have to resolve into a blockchain (in this case Ethereum) address.
The easiest way in this app is to tap both the sender and receiver cards to get the addresses.
In the case that the receiver is not physically available, a QR image of his address (can be made with the app) or the raw address itself are also ok.
The critical part is that the sender uses the card that's connected to his account when hashing the transaction.
The hashing does not happen in the app or on the blockchain service. It happens on the card.
With the private key that is stored on that card only. There's no (known) way to extract that key.
In practice, the NFC reader sends the transaction data to the card, and the card sends back a hash that represents that data.
That data is then used as the hash when posting to the blockchain.
Validity of the hash can be checked against your public key.
But altering (tampering!) with it would need your undisclosed private key. And then that would be detected because blockchains are to be distributed.
Process in the Coinfinity Android App - Narrative
This can be done on any Android phone. It does not have to be the one of the sender or the receiver. But it can be.
Sender
The app does exactly what's listed above.
The Who's The Sender part is discussed before. That's done by tapping a card to your phone.
Then (assuming you have dough on your Infineon card) , you select the SEND ETH button.
Here you can set the receiver of your money.
Receiver
If it's a transaction between to people in a single place (maybe the sender gives raw material to the receiver), the receiver can tap his card to that same telephone.
That sets him as the receiver.
For transactions that aren't physical, you can use the raw Ethereum address of the receiver, or a QR code.
In a warehouse, you would have a wall full of laminated (non-reflective!) QR codes of receivers, that cab be scanned by the phone's cam.
Scanning the right code then sets the receiver.
You can also type the 20 byte (40 hex character) address. But who wants to do that?
Once both parties are known, the amount to be transferred can be set.
In the Coinfinity app, you do this by typing an amount. In a logisitcs application, this could be done by scanning the goods-to-be-delivered ...
Firming by the Sender
Then the real stuff: approving the transfer.
As in the real world, it's the owner of the money that makes the transaction real. In our scenario, that's the sender.
There is no way around: the sender has to do that by presenting the card to the telephone.
Then APIs are used to ask the card to hash the data with the private key stored on that card. (important: the hash of the currently last transaction has to be part of that data. It's part of the resulting hash. This is actually the core of the blockchain principle).
The only secret here is that key. The transaction data is known. The resulting hash can be seen by all involved.
The magic sauce happens on the card. Turning that data into a non-reversable hash.
There's no known way (hi, NSA) to turn the hash into understandable data. But there's a way to validate that the hash is made by the owner of a public key that's shared.
Then that transaction, including the hash is sent to the blockchain. If it's from a known address, it's sent for validation to the peer chains.
A validated transaction will then be synchronised over all chains.
Only the Firming can't be Automated*
All of the above steps can be pre-filled by an ERP system or other ways of integration.
There's no business or flow need to have the sender or receiver make themselves known via the cards. That's all public data for any involved party.
Tapping the card makes it easy in practice, but there are many other valid scenarios.
Only for the signature, the Infineon Security 2Go card has to physically be present onto the NFC reader. And long enough to hash the data.
* unless you tape the card to a reader. But then you can protect it with a pin code too. In that case they need your card and pin.
The phone is not part of the security, because the transaction can be done on any phone that has the app. That's not a weak point. It's just how it works.
Process in the Coinfinity Android App - Code
We start where we left in the previous post, at the point where the sender's card is detected and the address known.
The receiver address is read in the same way (if you chose to do that by tapping your buddy's card to retrieve it).
Then you fill in the amount and tap the phone. Here is where we continue the story ...
*** I used 0.01 in the debug session below
Here we go. All info from the App screen is collected, then handed over to this function:
response = EthereumUtils.sendTransaction(gasPrice.toBigInteger(), gasLimit.toBigInteger(), ethAddress, recipientAddressTxt.getText().toString(), value.toBigInteger(), isoDep, pubKeyString, "", UiUtils.getFullNodeUrl(this), chainId, pref.getInt(KEY_INDEX_OF_CARD, 1), pinTxt.getText().toString().getBytes(StandardCharsets.UTF_8)); Log.d(TAG, String.format("sending ETH transaction finished with Hash: %s", response.first.getTransactionHash()));
This one is called. It's the big one that does all the work. All the functions mentioned below this block are called from here.
If I list them, it's because they play a key part.
public static Pair<EthSendTransaction, GenerateSignatureResponseApdu> sendTransaction( BigInteger gasPrice, BigInteger gasLimit, String from, String to, BigInteger value, IsoDep isoTag, String publicKey, String data, String url, byte chainId, int keyIndex, byte[] pin) throws Exception { Web3j web3 = Web3jFactory.build(new HttpService(url)); RawTransaction rawTransaction = RawTransaction.createTransaction( getNextNonce(web3, from), gasPrice, gasLimit, to, value, data); byte[] encodedTransaction = encode(rawTransaction, chainId); final byte[] hashedTransaction = Hash.sha3(encodedTransaction); final GenerateSignatureResponseApdu signedTransaction = NfcUtils.generateSignature(IsoTagWrapper.of(isoTag), keyIndex, hashedTransaction, pin); Log.d(TAG, String.format("signed transaction: %s", ByteUtils.bytesToHex(signedTransaction.getSignature()))); byte[] r = Bytes.trimLeadingZeroes(extractR(signedTransaction.getSignature())); byte[] s = Bytes.trimLeadingZeroes(extractS(signedTransaction.getSignature())); Log.d(TAG, String.format("r: %s", ByteUtils.bytesToHex(r))); Log.d(TAG, String.format("s: %s", ByteUtils.bytesToHex(s))); s = getCanonicalisedS(r, s); Log.d(TAG, String.format("s canonicalised: %s", ByteUtils.bytesToHex(s))); byte v = getV(publicKey, hashedTransaction, r, s); Log.d(TAG, String.format("v: %s", v)); Sign.SignatureData signatureData = new Sign.SignatureData(v, r, s); signatureData = TransactionEncoder.createEip155SignatureData(signatureData, chainId); String hexValue = Numeric.toHexString(TransactionEncoder.encode(rawTransaction, signatureData)); EthSendTransaction ethSendTransaction = web3.ethSendRawTransaction(hexValue).send(); if (ethSendTransaction != null && ethSendTransaction.getError() != null) { Log.e(TAG, String.format("TransactionError: %s", ethSendTransaction.getError().getMessage())); } return new Pair<>(ethSendTransaction, signedTransaction); }
Here we drill down to the APDU (Application Protocol Data Unit) level (called from the previous block.
At this time, our data is hashed
public static GenerateSignatureResponseApdu generateSignature(NfcTranceiver card, int keyIndex, byte[] dataToSign, byte[] pin) throws IOException, NfcCardException { selectApplication(card); if (pin != null && pin.length > 0) { if (!verifyPin(card, pin)) { return new GenerateSignatureResponseApdu(new byte[]{}); } } GenerateSignatureApdu apdu = new GenerateSignatureApdu(keyIndex, dataToSign); return (GenerateSignatureResponseApdu) tranceive(card, apdu, "GENERATE SIGNATURE"); }
Here the card is asked, via the NFC reader, to do the private key magic:
private static ResponseApdu tranceive(NfcTranceiver card, BaseCommandApdu commandApdu, String commandName) throws IOException, NfcCardException { Log.d(TAG, String.format("CMD: %s - APDU SENT: >>> %s", commandName, commandApdu.toHexString())); ResponseApdu responseApdu; if (commandApdu instanceof GetKeyInfoApdu) { responseApdu = new GetKeyInfoResponseApdu(card.transceive(commandApdu.toBytes())); } else if (commandApdu instanceof GenerateSignatureApdu) { responseApdu = new GenerateSignatureResponseApdu(card.transceive(commandApdu.toBytes())); } else { responseApdu = new ResponseApdu(card.transceive(commandApdu.toBytes())); } Log.d(TAG, String.format("CMD: %s - APDU RCVD: <<< %s", commandName, responseApdu.toHexString())); // check if Status OK if (!responseApdu.isSuccess()) { ExceptionHandler.handleErrorCodes(commandApdu, responseApdu.getSW1SW2()); } // return on success return responseApdu; }
It uses the same communication utilities to talk to the card as we've seen in the previous posts.
public byte[] transceive(byte[] commandApdu) throws IOException { if (!isoDep.isConnected()) { isoDep.connect(); } return isoDep.transceive(commandApdu); } }
Once all this preparation is done, the actual sending to the blockchain is not different than the blockchain calls in the previous posts (yes, we're back into known domain) .
The web3j lib is used to send the encoded, signed , hash-calculated block to the chain.
Evidence: