package com.alphawallet.app; import org.junit.Test; import com.alphawallet.app.repository.EthereumNetworkRepository; import com.alphawallet.token.entity.MagicLinkData; import com.alphawallet.token.entity.SalesOrderMalformed; import com.alphawallet.token.tools.ParseMagicLink; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; import org.web3j.crypto.Sign; import org.web3j.utils.Convert; import java.math.BigDecimal; import java.math.BigInteger; import static org.junit.Assert.assertEquals; /** * Created by James on 9/02/2019. * Stormbird in Singapore */ public class UniversalLinkTypeTest { private static ParseMagicLink parser = new ParseMagicLink(new CryptoFunctions(), null); /** * these values give the key format, ie * 4 bytes - price in MicroEth (Szabo) * 4 bytes - expiry (Unix time stamp - unsigned, gives higher range) * 20 bytes - contract addr * variable length indicies, multiple of single byte with MSB specifying extension to * */ final BigDecimal DROP_VALUE = Convert.toWei("0.01", Convert.Unit.ETHER); //Currency drop value in Wei final long NONCE = 13; final String CONTRACT_ADDR = "0x4e4a970a03d0b24877244ac0b233575c201d3f44"; //roll a new key ECKeyPair testKey = ECKeyPair.create("Test Key".getBytes()); private long expireTomorrow; @Test public void GenerateCurrencyLink() throws SalesOrderMalformed { //generate link BigInteger szaboAmount = com.alphawallet.token.tools.Convert.fromWei(DROP_VALUE, com.alphawallet.token.tools.Convert.Unit.SZABO).abs().toBigInteger(); expireTomorrow = System.currentTimeMillis() + 1000 * 60 * 60 * 24; expireTomorrow = expireTomorrow / 1000; //convert to seconds //This generates the 'message' part of the transaction //--- this is bitwise identical to message the 'formMessage' internal function in the Ethereum contract generates. byte[] tradeBytes = parser.getCurrencyBytes(CONTRACT_ADDR, szaboAmount, expireTomorrow, NONCE); //sign the tradeBytes with the testKey generated above byte[] signature = getSignature(tradeBytes); //add the currency link designation on the front byte[] linkMessage = ParseMagicLink.generateCurrencyLink(tradeBytes); //now complete the link by adding the signature on the end String universalLink = parser.completeUniversalLink(1,linkMessage, signature); System.out.println(universalLink); //now ensure we can extract all the information correctly CheckCurrencyLink(universalLink); } //Check the link is recoverable according to the spec //Spec is: //0xXX -> Link spec first byte (0x04 for currency drop) //0x XXXXXXXXXXXXXXXX -> 8 bytes message 'XDAIDROP' //0x XXXXXXXX -> 4 bytes nonce //0x XXXXXXXX -> 4 bytes link value in szabo //0x XXXXXXXX -> 4 bytes expiry //0x XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -> 20 bytes contract address //0x XXX ... -> 65 byte signature private void CheckCurrencyLink(String universalLink) { try { BigInteger szaboAmount = com.alphawallet.token.tools.Convert.fromWei(DROP_VALUE, com.alphawallet.token.tools.Convert.Unit.SZABO).abs().toBigInteger(); MagicLinkData data = parser.parseUniversalLink(universalLink); String recoveredPrefix = new String(data.prefix); assertEquals(recoveredPrefix, "XDAIDROP"); assertEquals(data.nonce.longValue(), NONCE); assertEquals(data.amount, szaboAmount); assertEquals(data.expiry, expireTomorrow); //check signature String ownerAddress = "0x" + ecRecoverAddress(); // get testKey address String recoveredOriginatorAddress = parser.getOwnerKey(data); assertEquals(ownerAddress, recoveredOriginatorAddress); } catch (SalesOrderMalformed salesOrderMalformed) { salesOrderMalformed.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private byte[] getSignature(byte[] message) throws SalesOrderMalformed { Sign.SignatureData sigData = Sign.signMessage(message, testKey); byte[] sig = new byte[65]; try { System.arraycopy(sigData.getR(), 0, sig, 0, 32); System.arraycopy(sigData.getS(), 0, sig, 32, 32); System.arraycopy(sigData.getV(), 0, sig, 64, 1); } catch (IndexOutOfBoundsException e) { throw new SalesOrderMalformed("Signature shorter than expected 256"); } return sig; } private String ecRecoverAddress(byte[] data, Sign.SignatureData signature) //get the hex string address from the sig and data { String address = ""; try { BigInteger recoveredKey = Sign.signedMessageToKey(data, signature); //get embedded address address = Keys.getAddress(recoveredKey); } catch (Exception e) { e.printStackTrace(); } return address; } private String ecRecoverAddress() throws Exception { String testSigMsg = "obtain public key"; Sign.SignatureData testSig = Sign.signMessage(testSigMsg.getBytes(), testKey); BigInteger recoveredKey = Sign.signedMessageToKey(testSigMsg.getBytes(), testSig); return Keys.getAddress(recoveredKey); } }