diff --git a/src/signer/rfqv1.ts b/src/signer/rfqv1.ts index 4aedc58..fc7624d 100644 --- a/src/signer/rfqv1.ts +++ b/src/signer/rfqv1.ts @@ -64,7 +64,7 @@ export async function signByMMPSigner( ]) signature = '0x' + signatureBuffer.toString('hex') return signature - } else if (walletType === WalletType.ERC1271) { + } else if (walletType === WalletType.ERC1271_EIP712_EIP191) { // | 32 byte | 32 byte |1 byte| 1 bytes | // +---------|---------|------|---------+ // | R | S | V | type(5) | @@ -91,9 +91,62 @@ export const forwardUnsignedOrder = async (signingUrl: string, orderInfo: any): } } +export const signRFQTx = async ( + chainId: number, + rfqAddr: string, + signedOrder: any, + user: Wallet, + receiverAddr: string, + signatureType = SignatureType.EIP712 +) => { + const domain = { + name: 'Tokenlon', + version: 'v5', + chainId: chainId, + verifyingContract: rfqAddr, + } + + const types = { + fillWithPermit: [ + { name: 'makerAddr', type: 'address' }, + { name: 'takerAssetAddr', type: 'address' }, + { name: 'makerAssetAddr', type: 'address' }, + { name: 'takerAssetAmount', type: 'uint256' }, + { name: 'makerAssetAmount', type: 'uint256' }, + { name: 'takerAddr', type: 'address' }, + { name: 'receiverAddr', type: 'address' }, + { name: 'salt', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + { name: 'feeFactor', type: 'uint256' }, + ], + } + + // The data to sign + const value = { + makerAddr: signedOrder.makerAddr, + takerAssetAddr: signedOrder.takerAssetAddr, + makerAssetAddr: signedOrder.makerAssetAddr, + takerAssetAmount: signedOrder.takerAssetAmount.toString(), + makerAssetAmount: signedOrder.makerAssetAmount.toString(), + takerAddr: signedOrder.takerAddr, + receiverAddr: receiverAddr, + salt: signedOrder.salt.toString(), + deadline: signedOrder.deadline.toString(), + feeFactor: signedOrder.feeFactor.toString(), + } + + const signatureTypedData = await user._signTypedData(domain, types, value) + const signature = Buffer.concat([ + ethUtils.toBuffer(signatureTypedData), + ethUtils.toBuffer(signatureType), + ]) + const eip712sig = '0x' + signature.toString('hex') + return eip712sig +} + export const buildSignedOrder = async ( signer: Wallet, - order, + order: any, userAddr: string, chainId: number, rfqAddr: string, @@ -111,16 +164,42 @@ export const buildSignedOrder = async ( order.salt = generateSaltWithFeeFactor(feeFactor, salt) const rfqOrer = toRFQOrder(order) + const orderHash = getOrderHash(rfqOrer) console.log(`orderHash: ${orderHash}`) const orderSignDigest = getOrderSignDigest(rfqOrer, chainId, rfqAddr) console.log(`orderSignDigest: ${orderSignDigest}`) let makerWalletSignature if (!signingUrl) { - makerWalletSignature = - signer.address.toLowerCase() == order.makerAddress.toLowerCase() - ? await signByEOA(orderSignDigest, signer) - : await signByMMPSigner(orderSignDigest, userAddr, feeFactor, signer, walletType) + if (signer.address.toLowerCase() == order.makerAddress.toLowerCase()) { + makerWalletSignature = await signRFQTx( + chainId, + rfqAddr, + rfqOrer, + signer, + rfqOrer.makerAddr, + SignatureType.EIP712 + ) + } else if (walletType === WalletType.ERC1271_EIP712) { + // standard ERC1271 + ERC712 signature + makerWalletSignature = await signRFQTx( + chainId, + rfqAddr, + rfqOrer, + signer, + rfqOrer.makerAddr, + SignatureType.WalletBytes32 + ) + } else { + // non-standard wallet contract signature checks + makerWalletSignature = await signByMMPSigner( + orderSignDigest, + userAddr, + feeFactor, + signer, + walletType + ) + } } else { makerWalletSignature = await forwardUnsignedOrder(signingUrl, { rfqOrer: rfqOrer, diff --git a/src/signer/types.ts b/src/signer/types.ts index d24af07..f012bb1 100644 --- a/src/signer/types.ts +++ b/src/signer/types.ts @@ -47,6 +47,7 @@ export enum SignatureType { export enum WalletType { MMP_VERSION_4 = 1, // https://gist.github.com/NIC619/a3db1a743175bf592f2db983f17680dd#file-mmpv4-sol-L1236 MMP_VERSION_5 = 2, // https://github.com/consenlabs/tokenlon-contracts/blob/e2edf7581b69bc8a40e61ff7fc1cd29674ae4887/contracts/MarketMakerProxy.sol#L19 - ERC1271 = 3, // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.6.0/contracts/utils/cryptography/SignatureChecker.sol#L36 + ERC1271_EIP712_EIP191 = 3, // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.6.0/contracts/utils/cryptography/SignatureChecker.sol#L36 EOA = 4, // less security for market makers + ERC1271_EIP712 = 5, } diff --git a/test/new_order.spec.ts b/test/new_order.spec.ts index 866f47f..02aa574 100644 --- a/test/new_order.spec.ts +++ b/test/new_order.spec.ts @@ -565,7 +565,7 @@ describe('NewOrder', function () { mockMarkerMakerConfigUpdater.cacheResult = cacheResult updaterStack['markerMakerConfigUpdater'] = mockMarkerMakerConfigUpdater const signedOrderResp = await newOrder({ - walletType: WalletType.ERC1271, + walletType: WalletType.ERC1271_EIP712_EIP191, signer: mmpSigner, chainID: 1, quoter: { @@ -670,6 +670,7 @@ describe('NewOrder', function () { expect(signedOrderResp).is.not.null // verify data object const order = signedOrderResp.order + console.log(order) expect(order).is.not.null expect(order.protocol).eq(Protocol.RFQV1) expect(order.quoteId).eq('1--echo-testing-8888') @@ -695,14 +696,49 @@ describe('NewOrder', function () { expect(order.takerFee).eq('0') // verify signature type const sigBytes = utils.arrayify(signedOrderResp.order.makerWalletSignature) - expect(sigBytes.length).eq(98) - expect(sigBytes[97]).eq(SignatureType.EthSign) + expect(sigBytes.length).eq(66) + expect(sigBytes[65]).eq(SignatureType.EIP712) // verify signature const rfqAddr = updaterStack['markerMakerConfigUpdater'].cacheResult.addressBookV5.RFQ - const orderSignDigest = getOrderSignDigest(toRFQOrder(signedOrderResp.order), 1, rfqAddr) - const recovered = utils.verifyMessage( - utils.arrayify(orderSignDigest), - utils.hexlify(sigBytes.slice(0, 65)) + const signedOrder = toRFQOrder(signedOrderResp.order) + const domain = { + name: 'Tokenlon', + version: 'v5', + chainId: chainId, + verifyingContract: rfqAddr, + } + const types = { + fillWithPermit: [ + { name: 'makerAddr', type: 'address' }, + { name: 'takerAssetAddr', type: 'address' }, + { name: 'makerAssetAddr', type: 'address' }, + { name: 'takerAssetAmount', type: 'uint256' }, + { name: 'makerAssetAmount', type: 'uint256' }, + { name: 'takerAddr', type: 'address' }, + { name: 'receiverAddr', type: 'address' }, + { name: 'salt', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + { name: 'feeFactor', type: 'uint256' }, + ], + } + // The data to sign + const value = { + makerAddr: signedOrder.makerAddr, + takerAssetAddr: signedOrder.takerAssetAddr, + makerAssetAddr: signedOrder.makerAssetAddr, + takerAssetAmount: signedOrder.takerAssetAmount, + makerAssetAmount: signedOrder.makerAssetAmount, + takerAddr: signedOrder.takerAddr, + receiverAddr: signedOrder.makerAddr, + salt: signedOrder.salt, + deadline: signedOrder.deadline, + feeFactor: signedOrder.feeFactor, + } + const recovered = ethers.utils.verifyTypedData( + domain, + types, + value, + signedOrderResp.order.makerWalletSignature.slice(0, -2) ) expect(recovered.toLowerCase()).eq(signer.address.toLowerCase()) // verify random values