Skip to content

Commit

Permalink
Merge branch 'main' into fature-offline-fetcher
Browse files Browse the repository at this point in the history
  • Loading branch information
jinglescode committed Nov 15, 2024
2 parents e667535 + 6b26824 commit 81ea535
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from "react";

import { checkSignature } from "@meshsdk/core";
import { useWallet } from "@meshsdk/react";

import Input from "~/components/form/input";
Expand Down
4 changes: 0 additions & 4 deletions apps/playground/src/pages/governance/index.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { useState } from "react";

import { checkSignature, generateNonce } from "@meshsdk/core";
import { CardanoWallet, useWallet } from "@meshsdk/react";

import Button from "~/components/button/button";
import LayoutImageHeaderAndBody from "~/components/layouts/image-header-and-body";

Expand Down
17 changes: 15 additions & 2 deletions apps/playground/src/pages/guides/prove-wallet-ownership/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CardanoWallet, useWallet } from "@meshsdk/react";

import Button from "~/components/button/button";
import DemoResult from "~/components/sections/demo-result";
import Codeblock from "~/components/text/codeblock";

export default function Demo() {
const { wallet, connected } = useWallet();
Expand All @@ -16,15 +17,27 @@ export default function Demo() {

setLoading(true);
const nonce = generateNonce("Sign to login in to Mesh: ");
const signature = await wallet.signData(nonce);
const address = (await wallet.getUsedAddresses())[0];
const signature = await wallet.signData(nonce, address);

const result = checkSignature(nonce, signature);
const result = checkSignature(nonce, signature, address);
setResponse(result.toString());
setLoading(false);
}

let code = ``;
code += `import { checkSignature, generateNonce } from "@meshsdk/core";\n`;
code += `\n`;
code += `const nonce = generateNonce("Sign to login in to Mesh: ");\n`;
code += `\n`;
code += `const address = (await wallet.getUsedAddresses())[0];\n`;
code += `const signature = await wallet.signData(nonce, address);\n`;
code += `\n`;
code += `const result = checkSignature(nonce, signature, address);\n`;

return (
<>
<Codeblock data={code} />
<p>Connect your wallet and click on the button to sign a message.</p>
<CardanoWallet />
<Button
Expand Down
18 changes: 7 additions & 11 deletions apps/playground/src/pages/guides/prove-wallet-ownership/index.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { useState } from "react";

import { checkSignature, generateNonce } from "@meshsdk/core";
import { CardanoWallet, useWallet } from "@meshsdk/react";

import Button from "~/components/button/button";
import LayoutImageHeaderAndBody from "~/components/layouts/image-header-and-body";
import { guideownership } from "~/data/links-guides";
Expand Down Expand Up @@ -66,7 +62,7 @@ To ensure the signature is valid, the same cryptographic algorithm is applied to

The User model stored in the database of the backend server must have two compulsory fields: public address and nonce. Furthermore, this address has to be unique. Other details about the user, such as username, Twitter ID, and name fields can be added, but are not essential for this process.

On Cardano, to obtain a user's public address as an identifier, we can use their wallet's staking address. This will be stored in the server side database, so that authorized wallets can be linked. The user never has to worry about manually entering their address, since it can be retrieved using **wallet.getRewardAddresses()**.
On Cardano, to obtain a user's public address as an identifier, we can use their wallet's staking address. This will be stored in the server side database, so that authorized wallets can be linked. The user never has to worry about manually entering their address, since it can be retrieved using **wallet.getUsedAddresses()**.

With the user's wallet connected, the first step is to get the user's staking address and send it to our backend server.

Expand All @@ -75,7 +71,7 @@ const { wallet, connected } = useWallet();
async function frontendStartLoginProcess() {
if (connected) {
const userAddress = (await wallet.getRewardAddresses())[0];
const userAddress = (await wallet.getUsedAddresses())[0];
// do: send request with 'userAddress' to the backend
}
Expand Down Expand Up @@ -117,7 +113,7 @@ We request the user's authorization and show them the message that is to be sign
```
async function frontendSignMessage(nonce) {
try {
const userAddress = (await wallet.getRewardAddresses())[0];
const userAddress = (await wallet.getUsedAddresses())[0];
const signature = await wallet.signData(nonce, userAddress);
// do: send request with 'signature' and 'userAddress' to the backend
Expand All @@ -143,7 +139,7 @@ import { checkSignature } from '@meshsdk/core';
async function backendVerifySignature(userAddress, signature) {
// do: get 'nonce' from user (database) using 'userAddress'
const result = checkSignature(nonce, signature);
const result = checkSignature(nonce, signature, userAddress);
// do: update 'nonce' in the database with another random string
Expand Down Expand Up @@ -177,15 +173,15 @@ export default function Page() {
async function frontendStartLoginProcess() {
if (connected) {
const userAddress = (await wallet.getRewardAddresses())[0];
const userAddress = (await wallet.getUsedAddresses())[0];
const nonce = await backendGetNonce(userAddress);
await frontendSignMessage(nonce);
}
}
async function frontendSignMessage(nonce) {
try {
const userAddress = (await wallet.getRewardAddresses())[0];
const userAddress = (await wallet.getUsedAddresses())[0];
const signature = await wallet.signData(nonce, userAddress);
await backendVerifySignature(userAddress, signature);
} catch (error) {
Expand Down Expand Up @@ -217,7 +213,7 @@ async function backendGetNonce(userAddress) {
async function backendVerifySignature(userAddress, signature) {
// do: get 'nonce' from database
const result = checkSignature(nonce, signature);
const result = checkSignature(nonce, signature, userAddress);
if(result){
// create JWT or approve certain process
}
Expand Down
92 changes: 91 additions & 1 deletion packages/mesh-core-cst/src/message-signing/check-signature.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,102 @@
import { Credential } from "@cardano-sdk/core/dist/cjs/Cardano";
import { Bip32PublicKey } from "@stricahq/bip32ed25519";

import { DataSignature } from "@meshsdk/common";

import {
Address,
BaseAddress,
Ed25519KeyHashHex,
EnterpriseAddress,
Hash28ByteBase16,
NetworkId,
RewardAddress,
} from "../types";
import { CoseSign1, getPublicKeyFromCoseKey } from "./cose-sign1";

/** @param address - Optional Bech32 string of a stake, stake_test1, addr, or addr_test1 address. If provided, this function will validate the signer's address against this value. */
export const checkSignature = (
data: string,
{ key, signature }: DataSignature,
address?: string,
) => {
const builder = CoseSign1.fromCbor(signature);
const publicKeyBuffer = getPublicKeyFromCoseKey(key);

if (address) {
let network = NetworkId.Mainnet;
const paymentAddress = BaseAddress.fromAddress(Address.fromBech32(address));
const coseSign1PublicKey = new Bip32PublicKey(publicKeyBuffer);

const credential: Credential = {
hash: Hash28ByteBase16.fromEd25519KeyHashHex(
Ed25519KeyHashHex(
coseSign1PublicKey.toPublicKey().hash().toString("hex"),
),
),
type: 0,
};

if (address.startsWith("addr")) {
if (address.startsWith("addr_test1")) {
network = NetworkId.Testnet;
}

const stakeCredential = paymentAddress?.getStakeCredential();
if (stakeCredential) {
const paymentAddressBech32 = BaseAddress.fromCredentials(
network,
credential,
stakeCredential,
)
.toAddress()
.toBech32();

if (address !== paymentAddressBech32) {
const extractedRewardAddress = RewardAddress.fromCredentials(
network,
stakeCredential,
)
.toAddress()
.toBech32();

const rewardAddress = RewardAddress.fromCredentials(
network,
credential,
)
.toAddress()
.toBech32();

if (rewardAddress !== extractedRewardAddress) {
return false;
}
}
} else {
const enterpriseAddress = EnterpriseAddress.fromCredentials(
network,
credential,
)
.toAddress()
.toBech32();
if (enterpriseAddress !== address) {
return false;
}
}
} else if (address.startsWith("stake")) {
if (address.startsWith("stake_test1")) {
network = NetworkId.Testnet;
}
const rewardAddress = RewardAddress.fromCredentials(network, credential)
.toAddress()
.toBech32();

if (rewardAddress !== address) {
return false;
}
} else {
return false;
}
}

if (builder.getPayload() === null) {
return false;
Expand All @@ -17,6 +107,6 @@ export const checkSignature = (
}

return builder.verifySignature({
publicKeyBuffer: getPublicKeyFromCoseKey(key),
publicKeyBuffer,
});
};
56 changes: 43 additions & 13 deletions packages/mesh-core-cst/test/message-signing.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
import { checkSignature } from "@meshsdk/core-cst";

const config = {
wallet_address:
"addr_test1qqmrzjhtanauj20wg37uk58adyrqfm82a9qr52vdnv0e54r42v0mu8ngky0f5yxmh3wl3z0da2fryk59kavth0u8xhvsufgmc8",
nonce:
"5369676e20746f2076657269667920746865206164647265737320666f723a207374616b655f74657374317570363478386137726535747a3835367a72646d6368306333386b373479336a74327a6d776b396d6837726e746b6773367a786a70315a424e474e79506d3975565677514a53385837724663476551424843657433",
rewardAddress:
"stake_test1up64x8a7re5tz856zrdmch0c38k74y3jt2zmwk9mh7rntkgs6zxjp",
invalidAddress:
"addr_test1qrqtawercjsj29xyq4kssxeru6s33y68kwmh8tj00q4vkhaeucuvwvhegqxf6ka0ewy0pallk044nnrtsj8zhevw603sjxytg2",
signature: {
signature:
"84582aa201276761646472657373581de075531fbe1e68b11e9a10dbbc5df889edea92325a85b758bbbf8735d9a166686173686564f458805369676e20746f2076657269667920746865206164647265737320666f723a207374616b655f74657374317570363478386137726535747a3835367a72646d6368306333386b373479336a74327a6d776b396d6837726e746b6773367a786a70315a424e474e79506d3975565677514a533858377246634765514248436574335840321349435491a3b5992a9b172aebc2b78de5af639045fdb57703f5e39d22b757c597a00b333ef4130c1259858e4230e7bf9ae51e2fa24cdb63dfcebde810860b",
key: "a40101032720062158201220e6aa326f24f12d644a1011dad9d138965c84566d2b7e20b79db7cf2aa73f",
},
};

describe("MessageSigning", () => {
it("checkSignature", () => {
const config = {
wallet_address:
"addr_test1qqmrzjhtanauj20wg37uk58adyrqfm82a9qr52vdnv0e54r42v0mu8ngky0f5yxmh3wl3z0da2fryk59kavth0u8xhvsufgmc8",
nonce:
"5369676e20746f2076657269667920746865206164647265737320666f723a207374616b655f74657374317570363478386137726535747a3835367a72646d6368306333386b373479336a74327a6d776b396d6837726e746b6773367a786a70315a424e474e79506d3975565677514a53385837724663476551424843657433",
rewardAddress:
"stake_test1up64x8a7re5tz856zrdmch0c38k74y3jt2zmwk9mh7rntkgs6zxjp",
signature: {
signature:
"84582aa201276761646472657373581de075531fbe1e68b11e9a10dbbc5df889edea92325a85b758bbbf8735d9a166686173686564f458805369676e20746f2076657269667920746865206164647265737320666f723a207374616b655f74657374317570363478386137726535747a3835367a72646d6368306333386b373479336a74327a6d776b396d6837726e746b6773367a786a70315a424e474e79506d3975565677514a533858377246634765514248436574335840321349435491a3b5992a9b172aebc2b78de5af639045fdb57703f5e39d22b757c597a00b333ef4130c1259858e4230e7bf9ae51e2fa24cdb63dfcebde810860b",
key: "a40101032720062158201220e6aa326f24f12d644a1011dad9d138965c84566d2b7e20b79db7cf2aa73f",
},
};
const result = checkSignature(config.nonce, config.signature);
expect(result).toBe(true);
});

it("checkSignature validates signature's address against provided rewardAddress", () => {
const result = checkSignature(
config.nonce,
config.signature,
config.rewardAddress,
);
expect(result).toBe(true);
});

it("checkSignature validates signature's address against provided wallet_address", () => {
const result = checkSignature(
config.nonce,
config.signature,
config.wallet_address,
);
expect(result).toBe(true);
});

it("checkSignature validates signature's address against provided invalidAddress", () => {
const result = checkSignature(
config.nonce,
config.signature,
config.invalidAddress,
);
expect(result).toBe(false);
});
});

0 comments on commit 81ea535

Please sign in to comment.