Skip to content

Commit

Permalink
TW-1240: Magic Square campaign. + Msg nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-tsx committed Jan 13, 2024
1 parent de6e712 commit 6c5abb7
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 26 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dotenv": "^9.0.2",
"express": "^4.18.2",
"firebase-admin": "^10.0.2",
"http-status-codes": "^2.3.0",
"ioredis": "^5.3.2",
"lodash": "^4.17.21",
"memoizee": "^0.4.15",
Expand Down
8 changes: 3 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { coinGeckoTokens } from './utils/gecko-tokens';
import { getExternalApiErrorPayload, isDefined, isNonEmptyString } from './utils/helpers';
import logger from './utils/logger';
import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url';
import { getSigningNonce, SIGNING_NONCE_TTL } from './utils/signing-nonce';
import { getSigningNonce } from './utils/signing-nonce';
import SingleQueryDataProvider from './utils/SingleQueryDataProvider';
import { tezExchangeRateProvider } from './utils/tezos';
import { getExchangeRatesFromDB } from './utils/tokens';
Expand Down Expand Up @@ -360,14 +360,12 @@ app.get('/api/magic-square-quest/participants', basicAuth, async (req, res) => {
}
});

app.get('/api/auth-nonce', async (req, res) => {
app.get('/api/signing-nonce', async (req, res) => {
try {
const pkh = req.query.pkh;
if (!pkh || typeof pkh !== 'string') throw new Error('PKH is not a string');

Check warning on line 366 in src/index.ts

View workflow job for this annotation

GitHub Actions / Checks if ts and lint works

Unexpected value in conditional. A boolean expression is required

const nonce = getSigningNonce(pkh);

res.status(200).send({ nonce, ttl: SIGNING_NONCE_TTL });
res.status(200).send(getSigningNonce(pkh));
} catch (error: any) {

Check warning on line 369 in src/index.ts

View workflow job for this annotation

GitHub Actions / Checks if ts and lint works

Unexpected any. Specify a different type
console.error(error);

Expand Down
27 changes: 18 additions & 9 deletions src/magic-square.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import * as ethersAddressUtils from '@ethersproject/address';
import * as ethersHashUtils from '@ethersproject/hash';
import * as ethersStringsUtils from '@ethersproject/strings';
import * as ethersTxUtils from '@ethersproject/transactions';
import { STATUS_CODE } from '@taquito/http-utils';
import { validateAddress, ValidationResult, verifySignature, getPkhfromPk } from '@taquito/utils';
import { StatusCodes } from 'http-status-codes';

import { redisClient } from './redis';
import { CodedError } from './utils/errors';
import { safeCheck } from './utils/helpers';
import { getSigningNonce } from './utils/signing-nonce';
import { getSigningNonce, removeSigningNonce } from './utils/signing-nonce';

const REDIS_DB_KEY = 'magic_square_quest';

Expand All @@ -32,21 +32,31 @@ export async function startMagicSquareQuest({ pkh, publicKey, messageBytes, sign
// Public Key Hashes

if (!safeCheck(() => validateAddress(pkh) === ValidationResult.VALID && getPkhfromPk(publicKey) === pkh))
throw new CodedError(STATUS_CODE.BAD_REQUEST, 'Invalid Tezos public key (hash)');
throw new CodedError(StatusCodes.BAD_REQUEST, 'Invalid Tezos public key (hash)');

let evmPkh: string;
try {
// Corrects lower-cased addresses. Throws if invalid.
evmPkh = ethersAddressUtils.getAddress(evm.pkh);
} catch (err) {
console.error(err);
throw new CodedError(STATUS_CODE.BAD_REQUEST, 'Invalid EVM public key hash');
throw new CodedError(StatusCodes.BAD_REQUEST, 'Invalid EVM public key hash');
}

// Nonce
const { value: nonce } = getSigningNonce(pkh);
const nonceBytes = Buffer.from(nonce, 'utf-8').toString('hex');

if (!messageBytes.includes(nonceBytes))
throw new CodedError(StatusCodes.UNAUTHORIZED, 'Invalid Tezos message nonce', 'INVALID_NONCE_TEZ');

if (!evm.messageBytes.includes(nonceBytes))
throw new CodedError(StatusCodes.UNAUTHORIZED, 'Invalid EVM message nonce', 'INVALID_NONCE_EVM');

// Signatures

if (!safeCheck(() => verifySignature(messageBytes, publicKey, signature)))
throw new CodedError(STATUS_CODE.UNAUTHORIZED, 'Invalid Tezos signature or message');
throw new CodedError(StatusCodes.UNAUTHORIZED, 'Invalid Tezos signature or message');

if (
!safeCheck(() => {
Expand All @@ -56,19 +66,18 @@ export async function startMagicSquareQuest({ pkh, publicKey, messageBytes, sign
return ethersTxUtils.recoverAddress(messageHash, evm.signature) === evmPkh;
})
)
throw new CodedError(STATUS_CODE.UNAUTHORIZED, 'Invalid EVM signature or message');
throw new CodedError(StatusCodes.UNAUTHORIZED, 'Invalid EVM signature or message');

// Presence check

const exists = await redisClient
.lrange(REDIS_DB_KEY, 0, -1)
.then(items => items.some(item => item.includes(pkh) && item.includes(evmPkh)));

if (exists)
throw new CodedError(STATUS_CODE.CONFLICT, 'Quest already started for the given credentials', 'QUEST_STARTED');
if (exists) throw new CodedError(StatusCodes.CONFLICT, 'Your quest was already started before', 'QUEST_IS_STARTED');

// Auth nonce
getSigningNonce.delete(pkh);
removeSigningNonce(pkh);

// Registering

Expand Down
6 changes: 5 additions & 1 deletion src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { StatusCodes } from 'http-status-codes';

interface CodedErrorForResponse {
message: string;
code?: string;
}

type StatusCodeNumber = (typeof StatusCodes)[keyof typeof StatusCodes];

export class CodedError extends Error {
constructor(public code: number, message: string, public errorCode?: string) {
constructor(public code: StatusCodeNumber, message: string, public errorCode?: string) {
super(message);
}

Expand Down
31 changes: 20 additions & 11 deletions src/utils/signing-nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@ import memoizee from 'memoizee';

import { CodedError } from './errors';

export const SIGNING_NONCE_TTL = 5 * 60_000;
const SIGNING_NONCE_TTL = 5 * 60_000;

const MEMOIZE_OPTIONS = {
max: 500,
maxAge: SIGNING_NONCE_TTL
};
export const getSigningNonce = memoizee(
(pkh: string) => {
if (validateAddress(pkh) !== ValidationResult.VALID) throw new CodedError(400, 'Invalid address');

export const getSigningNonce = memoizee((pkh: string) => {
if (validateAddress(pkh) !== ValidationResult.VALID) throw new CodedError(400, 'Invalid address');
return buildNonce();
},
{
max: 500,
maxAge: SIGNING_NONCE_TTL
}
);

return buildNonce();
}, MEMOIZE_OPTIONS);
export function removeSigningNonce(pkh: string) {
getSigningNonce.delete(pkh);
}

function buildNonce() {
// The way it is done in SIWE.generateNonce()
return randomStringForEntropy(96);
// Same as in in SIWE.generateNonce()
const value = randomStringForEntropy(96);

const expiresAt = new Date(Date.now() + SIGNING_NONCE_TTL).toISOString();

return { value, expiresAt };
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,11 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"

http-status-codes@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.3.0.tgz#987fefb28c69f92a43aecc77feec2866349a8bfc"
integrity sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==

https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
Expand Down

0 comments on commit 6c5abb7

Please sign in to comment.