Skip to content

Commit

Permalink
Merge pull request #15 from ConsenSys/generateSwap
Browse files Browse the repository at this point in the history
Implement generate swap for Bitcoin and Ethereum
  • Loading branch information
i3wgnit committed Aug 6, 2018
2 parents 9a903c9 + 159e65b commit 75afd36
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 17 deletions.
37 changes: 37 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,43 @@ export default class ChainAbstractionLayer {
const secret = crypto.hash160(signedMessage)
return secret
}

/**
* Generate swap transaction data
*/
generateSwap (recipientAddress, refundAddress, secretHash, expiration) {
this._checkMethod('generateSwap')

This comment has been minimized.

Copy link
@harshjv

harshjv Aug 9, 2018

Member

and this?


if (!isString(recipientAddress)) {
throw new Error('Recipient address should be a string')
}

if (!isString(refundAddress)) {
throw new Error('Refund address should be a string')
}

if (!isString(secretHash)) {
throw new Error('Secret hash should be a string')
}

if (!(/^[A-Fa-f0-9]+$/.test(recipientAddress))) {
throw new Error('Recipient address should be a valid hex string')
}

if (!(/^[A-Fa-f0-9]+$/.test(refundAddress))) {
throw new Error('Refund address should be a valid hex string')
}

if (!(/^[A-Fa-f0-9]+$/.test(secretHash))) {
throw new Error('Secret hash should be a valid hex string')
}

if (!isNumber(expiration)) {
throw new Error('Invalid expiration time')
}

return this.provider.generateSwap(recipientAddress, refundAddress, secretHash, expiration)

This comment has been minimized.

Copy link
@harshjv

harshjv Aug 9, 2018

Member

Is this even valid @i3wgnit ?

This comment has been minimized.

Copy link
@i3wgnit

i3wgnit Aug 9, 2018

Author Contributor

not since the new providers oops

}
}

ChainAbstractionLayer.Provider = Provider
Expand Down
38 changes: 38 additions & 0 deletions src/providers/bitcoin/BitcoinSwapProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Provider from '../../Provider'
import crypto from './BitcoinCrypto'
const { addressToPubKeyHash } = crypto

export default class BitcoinSwapProvider extends Provider {
generateSwap (recipientAddress, refundAddress, secretHash, expiration) {
let expirationHex = expiration.toString(16)
if (expirationHex.length % 2 === 1) {
expirationHex = '0' + expirationHex
}
expirationHex = expirationHex.match(/.{2}/g).reverse()
expirationHex.length = Math.min(expirationHex.length, 5)
expirationHex[expirationHex.length - 1] = '00'

const recipientPubKeyHash = addressToPubKeyHash(recipientAddress)
const refundPubKeyHash = addressToPubKeyHash(refundAddress)
const expirationPushDataOpcode = expirationHex.length.toString(16).padStart(2, '0')
const expirationHexEncoded = expirationHex.join('')

return [
'76', 'a9', // OP_DUP OP_HASH160
'72', // OP_2SWAP
'63', // OP_IF
'a8', // OP_SHA256
'20', secretHash, // OP_PUSHDATA20 {secretHash}
'88', // OP_EQUALVERIFY
'14', recipientPubKeyHash, // OP_PUSHDATA20 {recipientPubKeyHash}
'67', // OP_ELSE
expirationPushDataOpcode, // OP_PUSHDATA{expirationHexLength}
expirationHexEncoded, // {expirationHexEncoded}
'b1', // OP_CHECKLOCKTIMEVERIFY
'6d', // OP_2DROP
'14', refundPubKeyHash, // OP_PUSHDATA20 {refundPubKeyHash}
'68', // OP_ENDIF
'88', 'ac' // OP_EQUALVERIFY OP_CHECKSIG
].join('')
}
}
11 changes: 11 additions & 0 deletions src/providers/bitcoin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import BitcoinRPCProvider from './BitcoinRPCProvider'
import BitcoinLedgerProvider from './BitcoinLedgerProvider'
import BitcoinCrypto from './BitcoinCrypto'
import BitcoinSwapProvider from './BitcoinSwapProvider'

export default {
BitcoinRPCProvider,
BitcoinLedgerProvider,
crypto: BitcoinCrypto,
BitcoinSwapProvider
}
20 changes: 20 additions & 0 deletions src/providers/ethereum/EthereumMetaMaskProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,24 @@ export default class EthereumMetaMaskProvider extends Provider {
txHash = ensureEthFormat(txHash)
return this._toMM('eth_getTransactionByHash', txHash)
}

generateSwap (recipientAddress, refundAddress, secretHash, expiration) {
const dataSizeBase = 112
const redeemDestinationBase = 66
const refundDestinationBase = 89
const expirationHex = expiration.toString(16)
const expirationEncoded = expirationHex.length % 2 ? '0' + expirationHex : expirationHex // Pad with 0
const expirationSize = expirationEncoded.length / 2
const redeemDestinationEncoded = (redeemDestinationBase + expirationSize).toString(16)
const refundDestinationEncoded = (refundDestinationBase + expirationSize).toString(16)
const expirationPushOpcode = (0x5f + expirationSize).toString(16)
const dataSizeEncoded = (dataSizeBase + expirationSize).toString(16)
const recipientAddressEncoded = recipientAddress.replace('0x', '') // Remove 0x if exists
const refundAddressEncoded = refundAddress.replace('0x', '') // Remove 0x if exists
const secretHashEncoded = secretHash.replace('0x', '') // Remove 0x if exists
return `60${dataSizeEncoded}80600b6000396000f36020806000803760218160008060026048f17f\
${secretHashEncoded}602151141660${redeemDestinationEncoded}57\
${expirationPushOpcode}${expirationEncoded}421160${refundDestinationEncoded}\
57005b73${recipientAddressEncoded}ff5b73${refundAddressEncoded}ff`
}
}
77 changes: 77 additions & 0 deletions src/providers/ethereum/EthereumSwapProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Provider from '../../Provider'

export default class EthereumSwapProvider extends Provider {
generateSwap (recipientAddress, refundAddress, secretHash, expiration) {
const dataSizeBase = 112
const redeemDestinationBase = 66
const refundDestinationBase = 89
const expirationHex = expiration.toString(16)
const expirationEncoded = expirationHex.length % 2 ? '0' + expirationHex : expirationHex // Pad with 0
const expirationSize = expirationEncoded.length / 2
const expirationPushOpcode = (0x60 - 1 + expirationSize).toString(16)
const redeemDestinationEncoded = (redeemDestinationBase + expirationSize).toString(16)
const refundDestinationEncoded = (refundDestinationBase + expirationSize).toString(16)
const dataSizeEncoded = (dataSizeBase + expirationSize).toString(16)
const recipientAddressEncoded = recipientAddress.replace('0x', '') // Remove 0x if exists
const refundAddressEncoded = refundAddress.replace('0x', '') // Remove 0x if exists
const secretHashEncoded = secretHash.replace('0x', '') // Remove 0x if exists

return [
// Constructor
'60', dataSizeEncoded, // PUSH1 {dataSizeEncoded}
'80', // DUP1
'60', '0b', // PUSH1 0b
'60', '00', // PUSH1 00
'39', // CODECOPY
'60', '00', // PUSH1 00
'f3', // RETURN

// Contract
'60', '20', // PUSH1 20

// Get secret
'80', // DUP1
'60', '00', // PUSH1 00
'80', // DUP1
'37', // CALLDATACOPY

// SHA256
'60', '21', // PUSH1 21
'81', // DUP2
'60', '00', // PUSH1 00
'80', // DUP1
'60', '02', // PUSH1 02
'60', '48', // PUSH1 48
'f1', // CALL

// Validate with secretHash
'7f', secretHashEncoded, // PUSH32 {secretHashEncoded}
'60', '21', // PUSH1 21
'51', // MLOAD
'14', // EQ
'16', // AND (to make sure CALL succeeded)
// Redeem if secret is valid
'60', redeemDestinationEncoded, // PUSH1 {redeemDestinationEncoded}
'57', // JUMPI

// Check time lock
expirationPushOpcode, // PUSH{expirationSize}
expirationEncoded, // {expirationEncoded}
'42', // TIMESTAMP
'11', // GT
// Refund if timelock passed
'60', refundDestinationEncoded, // PUSH1 {refundDestinationEncoded}
'57',

'00', // STOP

'5b', // JUMPDEST
'73', recipientAddressEncoded, // PUSH20 {recipientAddressEncoded}
'ff', // SUICIDE

'5b', // JUMPDEST
'73', refundAddressEncoded, // PUSH20 {refundAddressEncoded}
'ff' // SUICIDE
].join('')
}
}
11 changes: 11 additions & 0 deletions src/providers/ethereum/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import EthereumRPCProvider from './EthereumRPCProvider'
import EthereumLedgerProvider from './EthereumLedgerProvider'
import EthereumMetaMaskProvider from './EthereumMetaMaskProvider'
import EthereumSwapProvider from './EthereumSwapProvider'

export default {
EthereumRPCProvider,
EthereumLedgerProvider,
EthereumMetaMaskProvider,
EthereumSwapProvider
}
21 changes: 4 additions & 17 deletions src/providers/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import BitcoinRPCProvider from './bitcoin/BitcoinRPCProvider'
import BitcoinLedgerProvider from './bitcoin/BitcoinLedgerProvider'
import BitcoinCrypto from './bitcoin/BitcoinCrypto'

import EthereumRPCProvider from './ethereum/EthereumRPCProvider'
import EthereumLedgerProvider from './ethereum/EthereumLedgerProvider'
import EthereumMetaMaskProvider from './ethereum/EthereumMetaMaskProvider'
import bitcoin from './bitcoin/'
import ethereum from './ethereum/'

export default {
bitcoin: {
BitcoinRPCProvider,
BitcoinLedgerProvider,
crypto: BitcoinCrypto
},
ethereum: {
EthereumRPCProvider,
EthereumLedgerProvider,
EthereumMetaMaskProvider
}
bitcoin,
ethereum
}
22 changes: 22 additions & 0 deletions test/BitcoinSwapProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-env mocha */

const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const { expect } = chai

const ChainAbstractionLayer = require('../')

const lib = new ChainAbstractionLayer.providers.bitcoin.BitcoinSwapProvider()

describe('Bitcoin Swap provider', () => {
describe('Generate swap', () => {
it('should generate correct bytecode', () => {
return expect(lib.generateSwap('1J7eFp9p48g3U3yCREyhd6LJzhnkywhi5s',
'1GZQKjsC97yasxRj1wtYf5rC61AxpR1zmr',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
1532622116403))
.to.equal('76a97263a820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8814bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb6705339665d700b16d14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa6888ac')
})
})
})
30 changes: 30 additions & 0 deletions test/EthereumSwapProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-env mocha */

const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const { expect } = chai

const ChainAbstractionLayer = require('../')

const lib = new ChainAbstractionLayer.providers.ethereum.EthereumSwapProvider()

describe('Ethereum Swap provider', () => {
describe('Generate swap', () => {
it('should generate correct bytecode', () => {
return expect(lib.generateSwap('0x5acbf79d0cf4139a6c3eca85b41ce2bd23ced04f',
'0x0a81e8be41b21f651a71aab1a85c6813b8bbccf8',
'0x91d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e',
255))
.to.equal('607180600b6000396000f36020806000803760218160008060026048f17f91d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e602151141660435760ff4211605a57005b735acbf79d0cf4139a6c3eca85b41ce2bd23ced04fff5b730a81e8be41b21f651a71aab1a85c6813b8bbccf8ff')
})

it('should generate correct bytecode with different expiration length', () => {
return expect(lib.generateSwap('0x5acbf79d0cf4139a6c3eca85b41ce2bd23ced04f',
'0x0a81e8be41b21f651a71aab1a85c6813b8bbccf8',
'0x91d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e',
6016519))
.to.equal('607380600b6000396000f36020806000803760218160008060026048f17f91d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e6021511416604557625bce074211605c57005b735acbf79d0cf4139a6c3eca85b41ce2bd23ced04fff5b730a81e8be41b21f651a71aab1a85c6813b8bbccf8ff')
})
})
})

0 comments on commit 75afd36

Please sign in to comment.