Skip to content

Commit

Permalink
Create arbitrary data form to sign in ARC-60 standard
Browse files Browse the repository at this point in the history
  • Loading branch information
yasincaliskan committed Mar 28, 2024
1 parent 2c1fe35 commit 11b9eae
Show file tree
Hide file tree
Showing 8 changed files with 8,232 additions and 901 deletions.
8,754 changes: 7,937 additions & 817 deletions package-lock.json

Large diffs are not rendered by default.

60 changes: 32 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,42 @@
"private": true,
"dependencies": {
"@hipo/react-ui-toolkit": "1.0.0-beta",
"@perawallet/connect": "^1.3.3",
"@perawallet/onramp": "^1.1.1",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"@types/react-modal": "^3.13.1",
"@perawallet/connect-beta": "file:../connect",
"@perawallet/onramp": "1.1.1",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.4",
"@testing-library/user-event": "13.5.0",
"@types/react-modal": "3.13.1",
"ajv": "^8.12.0",
"algosdk": "2.7.0",
"notiboy-js-sdk": "^1.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-modal": "^3.16.1",
"react-scripts": "^4.0.3",
"typescript": "^4.5.4",
"web-vitals": "^2.1.4"
"json-canonicalize": "^1.0.6",
"notiboy-js-sdk": "1.1.0",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-json-view": "^1.21.3",
"react-modal": "3.16.1",
"react-scripts": "4.0.3",
"tweetnacl": "^1.0.3",
"typescript": "4.5.4",
"web-vitals": "2.1.4"
},
"devDependencies": {
"@hipo/eslint-config-base": "^4.2.1",
"@hipo/eslint-config-react": "^2.1.0",
"@hipo/eslint-config-typescript": "^1.1.0",
"@hipo/stylelint-config-base": "^2.3.0",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"prettier": "^2.5.1",
"react-error-overlay": "^6.0.9",
"@hipo/eslint-config-base": "4.2.1",
"@hipo/eslint-config-react": "2.1.0",
"@hipo/eslint-config-typescript": "1.1.0",
"@hipo/stylelint-config-base": "2.3.0",
"@typescript-eslint/eslint-plugin": "5.6.0",
"@typescript-eslint/parser": "5.6.0",
"eslint": "7.32.0",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-typescript": "2.5.0",
"prettier": "2.5.1",
"react-error-overlay": "6.0.9",
"sass": "1.56.1",
"stylelint": "^13.12.0",
"stylelint-no-unsupported-browser-features": "^4.1.4",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.19.0"
"stylelint": "13.12.0",
"stylelint-no-unsupported-browser-features": "4.1.4",
"stylelint-order": "4.1.0",
"stylelint-scss": "3.19.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down
4 changes: 2 additions & 2 deletions src/core/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import "./_home.scss";

import {Button, Dropdown, DropdownOption, Switch, useToaster} from "@hipo/react-ui-toolkit";
import {useEffect, useState} from "react";
import {PeraWalletConnect} from "@perawallet/connect";
import {PeraWalletConnect} from "@perawallet/connect-beta";
import {PeraOnramp} from "@perawallet/onramp";
import {SignerTransaction} from "@perawallet/connect/dist/util/model/peraWalletModels";
import {SignerTransaction} from "@perawallet/connect-beta/dist/util/model/peraWalletModels";

import AccountBalance from "./account-balance/AccountBalance";
import SignTxn from "./sign-txn/SignTxn";
Expand Down
15 changes: 15 additions & 0 deletions src/core/home/arbitrary-data/arc-60/arc60-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"ARC60Domain": {
"type": "string",
"description": "The ARC-60 domain separator"
},
"bytes": {
"type": "string",
"description": "Byte string being signed."
}
},
"required": ["ARC60Domain", "bytes"],
"additionalProperties": true
}
30 changes: 30 additions & 0 deletions src/core/home/arbitrary-data/arc-60/arc60Types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export enum ScopeType {
MSGSIG,
LSIG,
}

export type StdData = string;

export type Ed25519Pk = Uint8Array

export interface StdSignMetadata {
scope: ScopeType;
schema: string;
message?: string;
}

export type SignDataFunction = (
data: StdData,
metadata: StdSignMetadata,
signer: Ed25519Pk,
) => Promise<(Uint8Array | null)>

export interface ARC60SchemaType {
ARC60Domain: string;
bytes: Uint8Array;
}

export enum ApprovalOption {
CONFIRM = "Confirm",
REJECT = "Reject",
}
157 changes: 157 additions & 0 deletions src/core/home/arbitrary-data/create/CreateArbitraryData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {ReactComponent as CloseIcon} from "../../../ui/icon/close.svg";
import * as arc60Schema from "../arc-60/arc60-schema.json";
import "./_create-arbitrary-data.scss";

import {PeraWalletConnect} from "@perawallet/connect-beta";
import {Button, Dropdown, FormField, Input} from "@hipo/react-ui-toolkit";
import {canonicalize} from 'json-canonicalize'
import {useState} from "react";
import ReactJson from 'react-json-view';
import Ajv, {JSONSchemaType} from 'ajv';

import Modal from "../../../component/modal/Modal";
import {ARC60SchemaType} from "../arc-60/arc60Types";

const ajv = new Ajv();
const forbiddenDomains = ["TX", "TG"];
const allowedDomains = [{id: "arc60", title: "ARC-60"}, {id: "perawallet", title: "PeraWallet"}];
const scopes = [{id: "msgsig", title: "MSGSIG"}, {id: "lsig", title: "LSIG"}];

interface CreateArbitraryDataProps {
address: string;
isOpen: boolean;
onClose: VoidFunction;
peraWallet: PeraWalletConnect;
}

function CreateArbitraryData({
address,
isOpen,
onClose,
peraWallet
}: CreateArbitraryDataProps) {
const [formState, setFormState] = useState({
arc60Domain: allowedDomains[0],
bytes: "",
metadata: {
scope: scopes[0],
message: "",
schema: canonicalize(arc60Schema),
}
});

return (
<Modal
customClassName={"create-arbitrary-data"}
contentLabel={"Create Arbitrary Modal"}
isOpen={isOpen}
onClose={onClose}>
<CloseIcon onClick={onClose} className={"modal__close"} width={24} height={24} />

<h3 style={{marginBottom: "30px"}}>{"Create and Sign Arbitrary Data in ARC-60 Standard"}</h3>

<div className={"create-arbitrary-data-body"}>
<form>
<FormField label={"ARC60 Domain"}>
<Dropdown
customClassName={"app__header__chain-select-dropdown"}
role={"menu"}
options={allowedDomains}
selectedOption={formState.arc60Domain}
onSelect={(option) => setFormState({...formState, arc60Domain: option!})}
hasDeselectOption={false}
/>
</FormField>

<FormField label={"Scope"}>
<Dropdown
customClassName={"app__header__chain-select-dropdown"}
role={"menu"}
options={scopes}
selectedOption={formState.metadata.scope}
onSelect={(option) => setFormState({...formState, metadata: {...formState.metadata, scope: option!}})}
hasDeselectOption={false}
/>
</FormField>

<FormField label={"Bytes (string)"}>
<Input
value={formState.bytes}
name={"bytes"}
onChange={(e) => setFormState({...formState, bytes: e.currentTarget.value})}
/>
</FormField>


<FormField label={"Message (string)"}>
<Input
value={formState.metadata.message}
name={"message"}
onChange={(e) => setFormState({...formState, metadata: {...formState.metadata, message: e.currentTarget.value}})}
/>
</FormField>
</form>

<div>
<h5>{"Arbitrary Data"}</h5>

<ReactJson src={formState} name={null} />
</div>
</div>


<Button
onClick={signData}
customClassName={
"create-txn__cta"
}>{`Sign arbitrary data`}
</Button>
</Modal>
);

function signData() {
const data = {
ARC60Domain: formState.arc60Domain.id,
bytes: formState.bytes,
}

const parsedSchema: JSONSchemaType<ARC60SchemaType> = JSON.parse(formState.metadata.schema)
const parsedData = JSON.parse(canonicalize(data))

// Check for forbidden domain separators
if (forbiddenDomains.includes(parsedData.ARC60Domain)) {
throw new Error('Invalid input')
}

// Check domain separator consistency
if (formState.metadata.scope.title === "MSGSIG" && !(allowedDomains.find((domain) => domain.id === parsedData.ARC60Domain))) {
throw new Error('Invalid input')
}

// Validate the schema
const validate = ajv.compile<ARC60SchemaType>(parsedSchema)
const isValid = validate(parsedData)

if (!isValid) {
throw new Error('Invalid input')
}

// bytes cannot be a transaction
// eslint-disable-next-line no-magic-numbers
const tag = Buffer.from(parsedData.bytes.slice(0, 2)).toString();

if (forbiddenDomains.includes(tag)) {
throw new Error('Invalid input')
}

const signArbitraryData = new Uint8Array(Buffer.from(parsedData.ARC60Domain + parsedData.bytes));
// const signatureBytes = nacl.sign(signArbitraryData, keypair.secretKey)

const signatureBytes = peraWallet.signData(signArbitraryData, formState.metadata.message, address);

return Promise.resolve(signatureBytes)

}
}

export default CreateArbitraryData;
13 changes: 13 additions & 0 deletions src/core/home/arbitrary-data/create/_create-arbitrary-data.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.create-arbitrary-data-body {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}

.create-arbitrary-data.modal {
overflow-y: scroll;

width: 70vw;

padding: 28px;
}
Loading

0 comments on commit 11b9eae

Please sign in to comment.