Skip to content

Commit

Permalink
Merge pull request #119 from hoprnet/jjpa/idx-sign-59
Browse files Browse the repository at this point in the history
Adding verification of node and query
  • Loading branch information
0xjjpa committed Sep 3, 2021
2 parents eb863bd + 67ccca8 commit 15d075b
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 24 deletions.
75 changes: 63 additions & 12 deletions components/atoms/VerifyNode.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEthers } from "@usedapp/core";
import { useEffect, useState } from "react";
import { truncate } from '../../utils/string';
import { truncate } from "../../utils/string";

import { EligibilityPerAddress } from "./EligibilityPerAddress";
import {
Expand All @@ -11,16 +11,22 @@ import {
HOPR_ADDRESS_CHAR_LENGTH,
HOPR_WEB3_SIGNATURE_DOMAIN,
HOPR_WEB3_SIGNATURE_TYPES,
HOPR_WEB3_SIGNATURE_PRIMARY_TYPE,
HOPR_WEB3_SIGNATURE_FOR_NODE_TYPES,
} from "../../constants/hopr";

const getWeb3SignatureFaucetContents = (hoprAddress, ethAddress) => ({
hoprAddress,
ethAddress,
});

const sendSignatureToAPI = async (account, signature, message) => {
const response = await fetch(`/api/faucet/fund/${account}`, {
const getWeb3SignatureVerifyContents = (hoprAddress, hoprSignature, ethAddress) => ({
hoprAddress,
hoprSignature,
ethAddress,
});

const sendSignatureToAPI = async (endpoint, account, signature, message) => {
const response = await fetch(endpoint, {
body: JSON.stringify({ signature, message }),
method: "POST",
headers: new Headers({
Expand Down Expand Up @@ -139,6 +145,7 @@ const NodeTable = ({ nodes = [], signRequest, copyCodeToClipboard }) => {
export const VerifyNode = ({ idx, copyCodeToClipboard }) => {
const { account, library } = useEthers();
const [inputValue, setInputValue] = useState();
const [signatureValue, setSignatureValue] = useState();
// NB: These would fit better grouped via a reducer
const [isLoading, setLoading] = useState(false);
const [loadingMessage, setLoadingMessage] = useState("");
Expand All @@ -152,6 +159,24 @@ export const VerifyNode = ({ idx, copyCodeToClipboard }) => {
setProfile(profile);
};

const signSignature = async (hoprAddress, hoprSignature, ethAddress) => {
const message = getWeb3SignatureVerifyContents(hoprAddress, hoprSignature, ethAddress);
const signature = await library
.getSigner()
._signTypedData(
HOPR_WEB3_SIGNATURE_DOMAIN,
HOPR_WEB3_SIGNATURE_FOR_NODE_TYPES,
message
);
const response = await sendSignatureToAPI(
`/api/sign/verify/${account}`,
account,
signature,
message
);
return response.message;
};

const signRequest = async (hoprAddress, ethAddress) => {
const message = getWeb3SignatureFaucetContents(hoprAddress, ethAddress);
const signature = await library
Expand All @@ -161,7 +186,12 @@ export const VerifyNode = ({ idx, copyCodeToClipboard }) => {
HOPR_WEB3_SIGNATURE_TYPES,
message
);
const response = await sendSignatureToAPI(account, signature, message);
const response = await sendSignatureToAPI(
`/api/faucet/fund/${account}`,
account,
signature,
message
);
return response;
};

Expand Down Expand Up @@ -292,32 +322,53 @@ export const VerifyNode = ({ idx, copyCodeToClipboard }) => {
<small>
By verifying your node, you are elegible to NFT rewards based on the
on-chain actions your node(s) execute(s). You can only verify nodes
you control. Copy your Ethereum address and go to the admin interface
of your HOPR node. Using the command “sign”, sign your copied address
and paste the result here. e.g. “sign
0x2402da10A6172ED018AEEa22CA60EDe1F766655C”
you control. First, add your HOPR node address in the following input
value field.
</small>
<div display="block" style={{ marginTop: "10px" }}>
<input
type="text"
onChange={(e) => setInputValue(e.target.value)}
placeholder="16Uiu2HA..."
style={{ width: "98%", padding: "5px" }}
/>
</div>
<br />
<small>
Now, copy your Ethereum address (the one you want your NFT rewards to
go to and you can use for signing, usually your MetaMask one) and go
to the admin interface of your HOPR node. Using the command “sign”,
sign your copied address and paste the result here. e.g. “sign
{' '}{account}
</small>
<br />
<br />
<small>
Copy and paste the contents of the sign function in the following text
field and click on “Verify your HOPR node in IDX”. If valid, your node
field and click on “Verify node for rewards”. If valid, your node
will then be shown as verified in our network with your Ethereum
address.
</small>
<div display="block" style={{ marginTop: "5px" }}>
<textarea
placeholder="0x304402203208f46d1d25c4939760..."
rows="3"
onChange={(e) => setSignatureValue(e.target.value)}
display="block"
style={{ width: "98%", padding: "5px" }}
/>
</div>
<button
disabled={true}
disabled={!signatureValue || isLoading}
onClick={async () => {
setLoading(true);
const message = await signSignature(inputValue, signatureValue, account);
setLoading(false);
alert(message);
}}
style={{ backgroundColor: "rgba(248, 114, 54, 0.5)" }}
>
Verify node for rewards (after Testnet).
{ isLoading ? 'Adding your node' : 'Verify node for rewards' }
</button>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions constants/ceramic.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const CERAMIC_IDX_ALIASES = {
alias1: "basicProfile",
};
export const CERAMIC_IDX_HOPR_NAMESPACE = 'hopr-wildhorn'
export const CERAMIC_TILE_ID = 'kjzl6cwe1jw1468n6gtygg5kdg0j69treni29fruvwxvatff9ebb4p4q995fv4i'
8 changes: 8 additions & 0 deletions constants/hopr.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const HOPR_WEB3_SIGNATURE_TYPES = {
{ name: "ethAddress", type: "address" },
],
};

export const HOPR_WEB3_SIGNATURE_FOR_NODE_TYPES = {
Node: [
{ name: "hoprAddress", type: "string" },
{ name: "hoprSignature", type: "string" },
{ name: "ethAddress", type: "address" },
],
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dependencies": {
"@3id/connect": "^0.1.6",
"@ceramicnetwork/3id-did-resolver": "^1.3.1",
"@ceramicnetwork/http-client": "^1.1.1",
"@ceramicnetwork/http-client": "^1.2.0",
"@ceramicnetwork/stream-tile": "^1.2.0",
"@ceramicstudio/idx": "^0.12.1",
"@hoprnet/hopr-utils": "1.76.0-next.18",
"@urql/core": "^2.2.0",
Expand All @@ -22,6 +23,8 @@
"ethers": "^5.4.4",
"graphql": "^15.5.1",
"isomorphic-fetch": "3.0.0",
"key-did-provider-ed25519": "^1.1.0",
"key-did-resolver": "^1.4.0",
"multiaddr": "^10.0.0",
"next": "9.5.5",
"node-sass": "4.14.1",
Expand Down
32 changes: 32 additions & 0 deletions pages/api/sign/get/all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Ed25519Provider } from "key-did-provider-ed25519";
import KeyResolver from "key-did-resolver";
import { DID } from "dids";
import CeramicClient from "@ceramicnetwork/http-client";
import { TileDocument } from "@ceramicnetwork/stream-tile";
import {
CERAMIC_API_URL,
CERAMIC_TILE_ID,
} from "../../../../constants/ceramic";

import { utils } from "ethers";

const secretKey = Uint8Array.from(
utils.arrayify(`0x${process.env.HOPR_DASHBOARD_API_PRIVATE_KEY}`)
);
const provider = new Ed25519Provider(secretKey);
const did = new DID({ provider, resolver: KeyResolver.getResolver() });
const client = new CeramicClient(CERAMIC_API_URL);
const tileId = CERAMIC_TILE_ID;

export default async (req, res) => {
await did.authenticate();
client.setDID(did);

const records = await TileDocument.load(client, tileId);

return res.status(200).json({
status: "ok",
tileId,
records: records.content,
});
};
86 changes: 86 additions & 0 deletions pages/api/sign/verify/[address].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Ed25519Provider } from "key-did-provider-ed25519";
import KeyResolver from "key-did-resolver";
import { DID } from "dids";
import CeramicClient from "@ceramicnetwork/http-client";
import { TileDocument } from "@ceramicnetwork/stream-tile";
import {
HOPR_WEB3_SIGNATURE_DOMAIN,
HOPR_WEB3_SIGNATURE_FOR_NODE_TYPES,
} from "../../../../constants/hopr";
import {
CERAMIC_API_URL,
CERAMIC_TILE_ID,
} from "../../../../constants/ceramic";

import { verifySignatureFromPeerId } from "@hoprnet/hopr-utils";
import { utils} from "ethers";

// NB: HOPR Node sign messages using the prefix to avoid having
// the nodes sign any generic data which could be used maliciously
// (e.g. a transfer request). Thus, we need to prefix the message
// to get a valid signature.
// see https://github.com/hoprnet/hoprnet/blob/master/packages/core/src/index.ts#L865-L870
const HOPR_PREFIX = "HOPR Signed Message: ";

const secretKey = Uint8Array.from(
utils.arrayify(`0x${process.env.HOPR_DASHBOARD_API_PRIVATE_KEY}`)
);
const provider = new Ed25519Provider(secretKey);
const did = new DID({ provider, resolver: KeyResolver.getResolver() });
const client = new CeramicClient(CERAMIC_API_URL);
const tileId = CERAMIC_TILE_ID;

export default async (req, res) => {
const { address } = req.query;
const { signature, message } = req.body;

const signerAddress = utils.verifyTypedData(
HOPR_WEB3_SIGNATURE_DOMAIN,
HOPR_WEB3_SIGNATURE_FOR_NODE_TYPES,
message,
signature
);
const isValidSignature = address == signerAddress;

if (isValidSignature) {
const checksumedAddress = utils.getAddress(address);
const { hoprSignature, hoprAddress, ethAddress } = message;
const messageSignedByNode = `${HOPR_PREFIX}${ethAddress}`;

const isAddressOwnerOfNode = await verifySignatureFromPeerId(
hoprAddress,
messageSignedByNode,
hoprSignature
);

if (isAddressOwnerOfNode) {

await did.authenticate();
client.setDID(did);

const docs = await TileDocument.load(client, tileId);
const mutatedDoc = Object.assign({}, docs.content, {
[hoprAddress]: ethAddress,
});
await docs.update(mutatedDoc);

return res.status(200).json({
status: "ok",
tile: docs.id.toString(),
message: `Your node was recorded into the Ceramic network.`,
});
} else {
return res.status(200).json({
status: "invalid",
address: checksumedAddress,
node: hoprAddress,
message: `Your signature does not match the address you are submitting. Please try with a new signature.`,
});
}
} else {
return res.json({
status: "err",
message: "Signature is invalid.",
});
}
};
Loading

1 comment on commit 15d075b

@vercel
Copy link

@vercel vercel bot commented on 15d075b Sep 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.