Skip to content

Commit

Permalink
Add signature verification on endpoints and test code
Browse files Browse the repository at this point in the history
  • Loading branch information
TrustHenry committed Aug 29, 2023
1 parent c26f8b3 commit e2c71de
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 60 deletions.
41 changes: 35 additions & 6 deletions packages/relay/src/routers/DefaultRouter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NonceManager } from "@ethersproject/experimental";
import { Signer, Wallet } from "ethers";
import { body, param, query, validationResult } from "express-validator";
import { body, validationResult } from "express-validator";
import * as hre from "hardhat";
import { Ledger, LinkCollection, Token } from "../../typechain-types";
import { Config } from "../common/Config";
Expand All @@ -9,8 +9,8 @@ import { GasPriceManager } from "../contract/GasPriceManager";
import { WebService } from "../service/WebService";

import express from "express";
import { ContractUtils } from "../../test/helper/ContractUtils";
import { Validation } from "../validation";
import { ContractUtils } from "../utils/ContractUtils";

export class DefaultRouter {
/**
Expand Down Expand Up @@ -232,7 +232,15 @@ export class DefaultRouter {
const signature: string = String(req.body.signature); // 서명

// TODO amount > 0 조건 검사
// TODO 서명검증

// 서명검증
const userNonce = await (await this.getLedgerContract()).nonceOf(signer);
if (!ContractUtils.verifyPayment(purchaseId, amount, email, franchiseeId, signer, userNonce, signature))
return res.status(200).json(
this.makeResponseData(500, undefined, {
message: "Signature is not valid.",
})
);

// 이메일 EmailLinkerContract에 이메일 등록여부 체크 및 구매자 주소와 동일여부
const emailToAddress: string = await (await this.getEmailLinkerContract()).toAddress(email);
Expand Down Expand Up @@ -289,7 +297,14 @@ export class DefaultRouter {
const signature: string = String(req.body.signature); // 서명

// TODO amount > 0 조건 검사
// TODO 서명검증
// 서명검증
const userNonce = await (await this.getLedgerContract()).nonceOf(signer);
if (!ContractUtils.verifyPayment(purchaseId, amount, email, franchiseeId, signer, userNonce, signature))
return res.status(200).json(
this.makeResponseData(500, undefined, {
message: "Signature is not valid.",
})
);

// 이메일 EmailLinkerContract에 이메일 등록여부 체크 및 구매자 주소와 동일여부
const emailToAddress: string = await (await this.getEmailLinkerContract()).toAddress(email);
Expand Down Expand Up @@ -344,7 +359,14 @@ export class DefaultRouter {
const signature: string = String(req.body.signature); // 서명

// TODO amountToken > 0 조건 검사
// TODO 서명검증
// 서명검증
const userNonce = await (await this.getLedgerContract()).nonceOf(signer);
if (!ContractUtils.verifyExchange(signer, email, amountToken, userNonce, signature))
return res.status(200).json(
this.makeResponseData(500, undefined, {
message: "Signature is not valid.",
})
);

// 이메일 EmailLinkerContract에 이메일 등록여부 체크 및 구매자 주소와 동일여부
const emailToAddress: string = await (await this.getEmailLinkerContract()).toAddress(email);
Expand Down Expand Up @@ -399,7 +421,14 @@ export class DefaultRouter {
const signature: string = String(req.body.signature); // 서명

// TODO amountMileage > 0 조건 검사
// TODO 서명검증
// 서명검증
const userNonce = await (await this.getLedgerContract()).nonceOf(signer);
if (!ContractUtils.verifyExchange(signer, email, amountMileage, userNonce, signature))
return res.status(200).json(
this.makeResponseData(500, undefined, {
message: "Signature is not valid.",
})
);

// 이메일 EmailLinkerContract에 이메일 등록여부 체크 및 구매자 주소와 동일여부
const emailToAddress: string = await (await this.getEmailLinkerContract()).toAddress(email);
Expand Down
118 changes: 118 additions & 0 deletions packages/relay/src/utils/ContractUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as crypto from "crypto";
import { BigNumberish, ethers, Signer } from "ethers";
// tslint:disable-next-line:no-submodule-imports
import { arrayify } from "ethers/lib/utils";

export class ContractUtils {
/**
* It generates hash values.
* @param data The source data
*/
public static sha256(data: Buffer): Buffer {
return crypto.createHash("sha256").update(data).digest();
}

public static sha256String(data: string): string {
return ContractUtils.BufferToString(crypto.createHash("sha256").update(Buffer.from(data.trim())).digest());
}

/**
* Convert hexadecimal strings into Buffer.
* @param hex The hexadecimal string
*/
public static StringToBuffer(hex: string): Buffer {
const start = hex.substring(0, 2) === "0x" ? 2 : 0;
return Buffer.from(hex.substring(start), "hex");
}

/**
* Convert Buffer into hexadecimal strings.
* @param data The data
*/
public static BufferToString(data: Buffer): string {
return "0x" + data.toString("hex");
}

public static async sign(signer: Signer, hash: string, nonce: BigNumberish): Promise<string> {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["bytes32", "address", "uint256"],
[hash, await signer.getAddress(), nonce]
);
const message = arrayify(ethers.utils.keccak256(encodedResult));
return signer.signMessage(message);
}

public static async signPayment(
signer: Signer,
purchaseId: string,
amount: BigNumberish,
userEmail: string,
franchiseeId: string,
nonce: BigNumberish
): Promise<string> {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["string", "uint256", "bytes32", "string", "address", "uint256"],
[purchaseId, amount, userEmail, franchiseeId, await signer.getAddress(), nonce]
);
const message = arrayify(ethers.utils.keccak256(encodedResult));
return signer.signMessage(message);
}

public static verifyPayment(
purchaseId: string,
amount: BigNumberish,
userEmail: string,
franchiseeId: string,
signerAddress: string,
nonce: BigNumberish,
signature: string
): boolean {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["string", "uint256", "bytes32", "string", "address", "uint256"],
[purchaseId, amount, userEmail, franchiseeId, signerAddress, nonce]
);
const message = arrayify(ethers.utils.keccak256(encodedResult));
let res: string;
try {
res = ethers.utils.verifyMessage(message, signature);
} catch (error) {
return false;
}
return res.toLowerCase() === signerAddress.toLowerCase();
}

public static async signExchange(
signer: Signer,
userEmail: string,
amount: BigNumberish,
nonce: BigNumberish
): Promise<string> {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["bytes32", "uint256", "address", "uint256"],
[userEmail, amount, await signer.getAddress(), nonce]
);
const message = arrayify(ethers.utils.keccak256(encodedResult));
return signer.signMessage(message);
}

public static verifyExchange(
signerAddress: string,
userEmail: string,
amount: BigNumberish,
nonce: BigNumberish,
signature: string
): boolean {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["bytes32", "uint256", "address", "uint256"],
[userEmail, amount, signerAddress, nonce]
);
const message = arrayify(ethers.utils.keccak256(encodedResult));
let res: string;
try {
res = ethers.utils.verifyMessage(message, signature);
} catch (error) {
return false;
}
return res.toLowerCase() === signerAddress.toLowerCase();
}
}
51 changes: 41 additions & 10 deletions packages/relay/test/Endpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,23 @@ describe("Test of Server", function () {
);
});

it("Failure Exchange token to mileage", async () => {
const nonce = await ledgerContract.nonceOf(users[0].address);
const signature = await ContractUtils.signExchange(users[1], emailHashes[0], amountToken, nonce);

const uri = URI(serverURL).directory("exchangeTokenToMileage");
const url = uri.toString();

const response = await client.post(url, {
email: emailHashes[0],
amountToken: amountToken.toString(),
signer: users[0].address,
signature,
});
assert.deepStrictEqual(response.data.code, 500);
assert.ok(response.data.error.message === "Signature is not valid.");
});

it("Success Exchange token to mileage", async () => {
const nonce = await ledgerContract.nonceOf(users[0].address);
const signature = await ContractUtils.signExchange(users[0], emailHashes[0], amountToken, nonce);
Expand Down Expand Up @@ -332,6 +349,28 @@ describe("Test of Server", function () {
);
});

it("Failure Exchange mileage to token", async () => {
const nonce = await ledgerContract.nonceOf(users[0].address);
const signature = await ContractUtils.signExchange(
users[1],
emailHashes[0],
purchaseData.amount,
nonce
);

const uri = URI(serverURL).directory("exchangeMileageToToken");
const url = uri.toString();

const response = await client.post(url, {
email: emailHashes[0],
amountMileage: purchaseData.amount.toString(),
signer: users[0].address,
signature,
});
assert.deepStrictEqual(response.data.code, 500);
assert.ok(response.data.error.message === "Signature is not valid.");
});

it("Success Exchange mileage to token", async () => {
const nonce = await ledgerContract.nonceOf(users[0].address);
const signature = await ContractUtils.signExchange(
Expand Down Expand Up @@ -435,11 +474,7 @@ describe("Test of Server", function () {
});

assert.deepStrictEqual(response.data.code, 500);
assert.ok(response.data.error.code === 500);
assert.ok(
response.data.error.message ===
"VM Exception while processing transaction: reverted with reason string 'Invalid signature'"
);
assert.ok(response.data.error.message === "Signature is not valid.");
});

it("Failure test of the path /payMileage 'Email is not valid.'", async () => {
Expand Down Expand Up @@ -579,11 +614,7 @@ describe("Test of Server", function () {
});

assert.deepStrictEqual(response.data.code, 500);
assert.ok(response.data.error.code === 500);
assert.ok(
response.data.error.message ===
"VM Exception while processing transaction: reverted with reason string 'Invalid signature'"
);
assert.ok(response.data.error.message === "Signature is not valid.");
});

it("Failure test of the path /payToken 'Email is not valid.'", async () => {
Expand Down
44 changes: 0 additions & 44 deletions packages/relay/test/helper/ContractUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,6 @@ export class ContractUtils {
return signer.signMessage(message);
}

public static verifyPayment(
purchaseId: string,
amount: BigNumberish,
userEmail: string,
franchiseeId: string,
signerAddress: string,
nonce: BigNumberish,
signature: string
): boolean {
const encodedResult = hre.ethers.utils.defaultAbiCoder.encode(
["string", "uint256", "bytes32", "string", "address", "uint256"],
[purchaseId, amount, userEmail, franchiseeId, signerAddress, nonce]
);
const message = arrayify(hre.ethers.utils.keccak256(encodedResult));
let res: string;
try {
res = hre.ethers.utils.verifyMessage(message, signature);
} catch (error) {
return false;
}
return res.toLowerCase() === signerAddress.toLowerCase();
}

public static async signExchange(
signer: Signer,
userEmail: string,
Expand All @@ -95,25 +72,4 @@ export class ContractUtils {
const message = arrayify(hre.ethers.utils.keccak256(encodedResult));
return signer.signMessage(message);
}

public static verifyExchange(
signerAddress: string,
userEmail: string,
amount: BigNumberish,
nonce: BigNumberish,
signature: string
): boolean {
const encodedResult = hre.ethers.utils.defaultAbiCoder.encode(
["bytes32", "uint256", "address", "uint256"],
[userEmail, amount, signerAddress, nonce]
);
const message = arrayify(hre.ethers.utils.keccak256(encodedResult));
let res: string;
try {
res = hre.ethers.utils.verifyMessage(message, signature);
} catch (error) {
return false;
}
return res.toLowerCase() === signerAddress.toLowerCase();
}
}

0 comments on commit e2c71de

Please sign in to comment.