Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for signInMessage method #883

Open
wants to merge 44 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4796485
Initial attempt to add support for signInMessage.
kujtimprenkuSQA Aug 10, 2023
4f6749d
Set contract only if it exists in store and localStorage.
kujtimprenkuSQA Aug 12, 2023
d9366fe
Export SignInMessageParams interface.
kujtimprenkuSQA Aug 14, 2023
4b36261
Add signInMessage to modal-ui.
kujtimprenkuSQA Aug 14, 2023
3da67a3
Avoid shadowing the message variable.
kujtimprenkuSQA Aug 14, 2023
1caa420
Add signInMessage to modal-ui-js.
kujtimprenkuSQA Aug 14, 2023
4a53439
Add SignInMessage button to examples to trigger the signInMessage.
kujtimprenkuSQA Aug 14, 2023
4087dd4
Add docs for signInMessage.
kujtimprenkuSQA Aug 14, 2023
dd92728
Remove console.log
kujtimprenkuSQA Aug 14, 2023
da1b4af
Trigger signInMessage for ledger.
kujtimprenkuSQA Aug 15, 2023
a801803
Add signInMessage to meteor-wallet for testing purposes
kujtimprenkuSQA Aug 16, 2023
6b22452
Pass message to setup_wallet_modules event.
kujtimprenkuSQA Aug 16, 2023
fdc2e0f
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Sep 11, 2023
57ff4b0
Change return type for signInMessage method to SignedMessage.
kujtimprenkuSQA Sep 11, 2023
831f19d
Return the SignedMessage for meteor wallet.
kujtimprenkuSQA Sep 11, 2023
99373b2
Persist signed message in a signedInMessage key in localStorage and r…
kujtimprenkuSQA Sep 11, 2023
abe6bfe
Make message and signedInMessage optional in WalletEvents.
kujtimprenkuSQA Sep 11, 2023
a6492fb
Set message and signedInMessage only when there is no contract.
kujtimprenkuSQA Sep 11, 2023
dea073a
Return the account that was used to sign the message for meteor.
kujtimprenkuSQA Sep 12, 2023
6467d49
Allow re-signIn with into a wallet.
kujtimprenkuSQA Sep 12, 2023
1032b22
Allow re-signIn with into a wallet.
kujtimprenkuSQA Sep 12, 2023
5ca37f7
Add signInMessage for Here Wallet.
kujtimprenkuSQA Sep 12, 2023
ef2384a
Add signInType() function to the selector API.
kujtimprenkuSQA Sep 13, 2023
9b6474c
Update docs.
kujtimprenkuSQA Sep 13, 2023
af71e65
Add a new event signedInMessage in wallet modules and in selector.
kujtimprenkuSQA Sep 13, 2023
1134f2c
Split the logic for signIn and signInMessage in core package.
kujtimprenkuSQA Sep 13, 2023
efb665d
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Sep 18, 2023
bc7a0c5
Add signInMessage to near-snap and nightly wallet.
kujtimprenkuSQA Sep 18, 2023
89e670c
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Oct 6, 2023
70afd22
Add signInMessage support for WELLDONE wallet.
kujtimprenkuSQA Oct 6, 2023
dfacc9d
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Oct 24, 2023
6d315f8
Add signInMessage to NearMobileWallet.
kujtimprenkuSQA Oct 24, 2023
e490f70
Save only accountId and publicKey on localStorage
kujtimprenkuSQA Oct 27, 2023
30255c4
Rename signedInMessage to signedInMessageAccount in store and localSt…
kujtimprenkuSQA Oct 27, 2023
4ac2768
Fix verifyMessage on examples.
kujtimprenkuSQA Oct 27, 2023
bb61ad8
Improve core package to handle signInMessage for browser wallets.
kujtimprenkuSQA Oct 27, 2023
400983f
Add support for signInMessage to my-near-wallet.
kujtimprenkuSQA Oct 27, 2023
60c1a77
Add new helper fuction verifyMessageNEP413 and refactor code
kujtimprenkuSQA Oct 30, 2023
da49a3d
Use helper function for here-wallet.
kujtimprenkuSQA Oct 30, 2023
d1e82a0
Update docs.
kujtimprenkuSQA Oct 30, 2023
2924219
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Nov 24, 2023
0651e9c
Add support for signInMessage to sender.
kujtimprenkuSQA Nov 27, 2023
8dcb255
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Feb 6, 2024
bec7c97
Merge branch 'dev' into SQC-571/add-signin-message
kujtimprenkuSQA Feb 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"eslint.format.enable": true,
"prettier.enable": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint":
true
"source.fixAll.eslint": "explicit"
},
"typescript.tsdk": "node_modules/typescript/lib"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@

<ng-container *ngIf="!accountId">
<button (click)="signIn()">Log In</button>
<button (click)="signInMessage()">Sign In Message</button>
<near-wallet-selector-sign-in></near-wallet-selector-sign-in>
</ng-container>
17 changes: 12 additions & 5 deletions examples/angular/src/app/components/content/content.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ export class ContentComponent implements OnInit, OnDestroy {
this.modal.show();
}

signInMessage() {
const message = "test message to sign";
const nonce = Buffer.from(Array.from(Array(32).keys()));
const recipient = "guest-book.testnet";
this.modal.signInMessage({ message, nonce, recipient });
}

async signOut() {
const wallet = await this.selector.wallet();

Expand Down Expand Up @@ -211,14 +218,14 @@ export class ContentComponent implements OnInit, OnDestroy {
const publicKey = urlParams.get("publicKey") as string;
const signature = urlParams.get("signature") as string;

if (!accId && !publicKey && !signature) {
return;
}

const message: SignMessageParams = JSON.parse(
localStorage.getItem("message") as string
);

if ((!accId && !publicKey && !signature) || !message) {
return;
}

const signedMessage = {
accountId: accId,
publicKey,
Expand Down Expand Up @@ -328,7 +335,7 @@ export class ContentComponent implements OnInit, OnDestroy {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
signerId: this.accountId!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
receiverId: contract!.contractId,
receiverId: contract?.contractId || CONTRACT_ID,
actions: [
{
type: "FunctionCall",
Expand Down
18 changes: 13 additions & 5 deletions examples/react/components/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ const Content: React.FC = () => {
modal.show();
};

const handleSignInMessage = () => {
const message = "test message to sign";
const nonce = Buffer.from(Array.from(Array(32).keys()));
const recipient = "guest-book.testnet";
modal.signInMessage({ message, nonce, recipient });
};

const handleSignOut = async () => {
const wallet = await selector.wallet();

Expand Down Expand Up @@ -192,7 +199,7 @@ const Content: React.FC = () => {
for (let i = 0; i < 2; i += 1) {
transactions.push({
signerId: accountId!,
receiverId: contract!.contractId,
receiverId: contract?.contractId || CONTRACT_ID,
actions: [
{
type: "FunctionCall",
Expand Down Expand Up @@ -275,14 +282,14 @@ const Content: React.FC = () => {
const publicKey = urlParams.get("publicKey") as string;
const signature = urlParams.get("signature") as string;

if (!accId && !publicKey && !signature) {
return;
}

const message: SignMessageParams = JSON.parse(
localStorage.getItem("message")!
);

if ((!accId && !publicKey && !signature) || !message) {
return;
}

const signedMessage = {
accountId: accId,
publicKey,
Expand Down Expand Up @@ -378,6 +385,7 @@ const Content: React.FC = () => {
<Fragment>
<div>
<button onClick={handleSignIn}>Log in</button>
<button onClick={handleSignInMessage}>Sign In Message</button>
</div>
<SignIn />
</Fragment>
Expand Down
25 changes: 24 additions & 1 deletion packages/core/docs/api/selector.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,29 @@ Programmatically change active account which will be used to sign and send trans
selector.setActiveAccount("sometestaccount.testnet");
```

### `.signInType()`

**Parameters**

- N/A

**Returns**

- `SignInType`

**Description**

Programmatically check the sign-in type, if signed-in with `wallet.signIn(params)` returns "key" if signed in with `wallet.signInMessage(params)` returns "message"

> Note: This function will throw when calling without being signed in.

**Example**

```ts
const signInType = selector.signInType();

```

### `.on(event, callback)`

**Parameters**
Expand Down Expand Up @@ -183,7 +206,7 @@ subscription.remove();

**Parameters**

- `event` (`string`): Name of the event. This can be: `signedIn | signedOut | accountsChanged | networkChanged | uriChanged`.
- `event` (`string`): Name of the event. This can be: `signedIn | signedInMessage | signedOut | accountsChanged | networkChanged | uriChanged`.
- `callback` (`Function`): Original handler passed to `.on(event, callback)`.

**Returns**
Expand Down
42 changes: 42 additions & 0 deletions packages/core/docs/api/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,45 @@ Returns ID-s of 5 recently signed in wallets.
const { recentlySignedInWallets } = selector.store.getState();
console.log(recentlySignedInWallets); // ["my-near-wallet", "sender", ...]
```

### `.message`

**Returns**

- `SignInMessageParams | null`
- `message` (`string`): The message that wants to be transmitted.
- `nonce` (`Buffer`): A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS).
- `recipient` (`string`): The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com").
- `callbackUrl` (`string?`): Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process.
- `state` (`string?`): Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes.


**Description**

Returns the original message that was used for `signInMessage`.

**Example**

```ts
const { message } = selector.store.getState();
console.log(message); // { message: "test", nonce: [0...31], recipient: "myapp.com" }
```

### `.signedInMessageAccount`

**Returns**

- `Account | null`
- `accountId` (`string`): The account name to which the publicKey corresponds as plain text (e.g. "alice.near").
- `publicKey` (`string`): The public counterpart of the key used to sign, expressed as a string with format "<key-type>:<base58-key-bytes>" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y")

**Description**

Returns the `Account` that was signed with `signInMessage`.

**Example**

```ts
const { signedMessage } = selector.store.getState();
console.log(signedMessage); // { accountId: "alice.near", publicKey: "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y" }
```
33 changes: 33 additions & 0 deletions packages/core/docs/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,36 @@ Allows users to sign a message for a specific recipient using their NEAR account
await wallet.signMessage({ message, recipient, nonce });
})();
```

### `.signInMessage(params)`

**Parameters**
- `params` (`object`)
- `message` (`string`): The message that wants to be transmitted.
- `nonce` (`Buffer`): A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS).
- `recipient` (`string`): The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com").
- `callbackUrl` (`string?`): Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`.
- `state` (`string?`): Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes.

**Returns**
- `Promise<SignedMessage | void>`

**Description**

Allows users to sign-in (login) to a dApp without creating a LAK by signing a message for a specific recipient using their NEAR account, based on the [NEP413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md).

**Example**

```ts
// MyNearWallet
(async () => {
const wallet = await selector.wallet("my-near-wallet");
const message = "test message for verification";
let nonceArray: Uint8Array = new Uint8Array(32);
nonceArray = crypto.getRandomValues(nonceArray);
const nonce = Buffer.from(nonceArray);
const recipient = "myapp.com";

await wallet.signInMessage({ message, nonce, recipient });
})();
```
6 changes: 6 additions & 0 deletions packages/core/docs/guides/custom-wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ const MyWallet: WalletBehaviourFactory<BrowserWallet> = ({
// that allows users to sign a message for a specific receiver using their NEAR account
return await wallet.signMessage({ message, nonce, recipient, callbackUrl, state });
},
async signInMessage({ message, nonce, recipient, callbackUrl, state }) {
// Sign in to My Wallet withotut creating a LAK for access to account(s).
// Signs the message, verifies it and returns the `SignedMessage`.

return [];
},
};
};

Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type {
WalletSelectorParams,
WalletSelectorEvents,
WalletSelectorStore,
SignInType,
} from "./lib/wallet-selector.types";
export { setupWalletSelector } from "./lib/wallet-selector";

Expand Down Expand Up @@ -70,6 +71,7 @@ export type {
AccountImportData,
SignedMessage,
SignMessageParams,
SignInMessageParams,
} from "./lib/wallet";

export type { FinalExecutionOutcome } from "near-api-js/lib/providers";
Expand All @@ -81,6 +83,7 @@ export {
verifyFullKeyBelongsToUser,
verifySignature,
serializeNep413,
verifyMessageNEP413,
} from "./lib/helpers";

export { translate, allowOnlyLanguage } from "./lib/translate/translate";
4 changes: 4 additions & 0 deletions packages/core/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export const PENDING_CONTRACT = "contract:pending";

export const SELECTED_WALLET_ID = `selectedWalletId`;
export const PENDING_SELECTED_WALLET_ID = `selectedWalletId:pending`;

export const SIGN_IN_MESSAGE = "message";
export const PENDING_SIGN_IN_MESSAGE = "message:pending";
export const SIGNED_IN_MESSAGE_ACCOUNT = "signedInMessageAccount";
33 changes: 33 additions & 0 deletions packages/core/src/lib/helpers/verify-signature/verify-signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {
} from "./verify-signature.types";
import { Payload, payloadSchema } from "./payload";
import type { AccessKeyView } from "near-api-js/lib/providers/provider";
import type { SignedMessage, SignMessageParams } from "../../wallet";
import type { Network } from "../../options.types";

export const verifySignature = ({
publicKey,
Expand Down Expand Up @@ -64,3 +66,34 @@ export const verifyFullKeyBelongsToUser = async ({

return permission === "FullAccess";
};

export const verifyMessageNEP413 = async (
message: SignMessageParams,
signedMessage: SignedMessage,
network: Network
) => {
const isSignatureValid = verifySignature({
message: message.message,
nonce: message.nonce,
recipient: message.recipient,
publicKey: signedMessage.publicKey,
signature: signedMessage.signature,
callbackUrl: message.callbackUrl,
});

if (!isSignatureValid) {
throw Error("Failed to verify the signature");
}

const isFullAccess = await verifyFullKeyBelongsToUser({
publicKey: signedMessage.publicKey,
accountId: signedMessage.accountId,
network,
});

if (!isFullAccess) {
throw Error("Message was not signed with full access key");
}

return true;
};
Loading
Loading