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

Display real json data from xdr #115

Merged
merged 9 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ yarn-error.log*

# Storacha proof and keys
.storacha
encrypted_proof.bin.test
24 changes: 24 additions & 0 deletions AES256-encryptor/.gitignore
tupui marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
package-lock.json

npm-debug.log*
yarn-debug.log*
yarn-error.log*
2 changes: 2 additions & 0 deletions AES256-encryptor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
You can encryt string data to AES256 encrypted payload and decrypt it.
The displayed key is Hex data of key.
40 changes: 40 additions & 0 deletions AES256-encryptor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "encrypt-proof",
"version": "0.1.0",
"private": true,
"dependencies": {
"cra-template": "1.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"formatter": "prettier --write .",
"linter": "prettier -c ."
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"prettier": "^3.4.2"
}
}
43 changes: 43 additions & 0 deletions AES256-encryptor/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
25 changes: 25 additions & 0 deletions AES256-encryptor/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
Empty file added AES256-encryptor/src/App.css
Empty file.
75 changes: 75 additions & 0 deletions AES256-encryptor/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useState, useEffect } from "react";
import { generateKey, encryptData, getKeyHex } from "./encryptData.js";
import { decryptData, getKeyFromHex } from "./decryptData.js";

const headerText = "storachaProof";
const header = new TextEncoder().encode(headerText);

const App = () => {
const [keyHex, setKeyHex] = useState(null);
const [plaintext, setPlaintext] = useState("This is secret data");
const [encryptedPayload, setEncryptedPayload] = useState(null);
const [decryptedText, setDecryptedText] = useState(null);

useEffect(() => {
(async () => {
const newKey = await generateKey();
setKeyHex(await getKeyHex(newKey));
})();
}, []);

const handleEncrypt = async () => {
if (keyHex) {
const data = new TextEncoder().encode(plaintext);
const key = await getKeyFromHex(keyHex);
const payload = await encryptData(key, data, header);
setEncryptedPayload(payload);
}
};

const handleDecrypt = async () => {
if (keyHex && encryptedPayload) {
const key = await getKeyFromHex(keyHex);
const decrypted = await decryptData(key, encryptedPayload);
setDecryptedText(new TextDecoder().decode(decrypted));
}
};

return (
<div>
<h1>AES Encryption/Decryption in React</h1>
<div>
<label>Plaintext:</label>
<input
type="text"
value={plaintext}
onChange={(e) => setPlaintext(e.target.value)}
/>
</div>
<div>
<button onClick={handleEncrypt}>Encrypt</button>
</div>
{keyHex && (
<div>
<h2>Key:</h2>
<p>{keyHex}</p>
</div>
)}
{encryptedPayload && (
<div>
<h2>Encrypted Payload:</h2>
<pre>{JSON.stringify(encryptedPayload, null, 2)}</pre>
<button onClick={handleDecrypt}>Decrypt</button>
</div>
)}
{decryptedText && (
<div>
<h2>Decrypted Text:</h2>
<p>{decryptedText}</p>
</div>
)}
</div>
);
};

export default App;
77 changes: 77 additions & 0 deletions AES256-encryptor/src/decryptData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
function base64ToArrayBuffer(base64) {
const binaryString = window.atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}

function hexToArrayBuffer(hex) {
if (hex.length % 2 !== 0) {
throw new Error("Invalid hex string");
}
const byteArray = new Uint8Array(hex.length / 2);
for (let i = 0; i < byteArray.length; i++) {
byteArray[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return byteArray.buffer;
}

/**
* Decrypts the given payload using the provided AES-GCM key.
* @param {CryptoKey} key - The AES-GCM key
* @param {Object} payload - The JSON payload with nonce, header, ciphertext, tag
* @returns {Uint8Array} plaintext
*/
export async function decryptData(key, payload) {
const nonce = new Uint8Array(base64ToArrayBuffer(payload.nonce));
const header = new Uint8Array(base64ToArrayBuffer(payload.header));
const ciphertext = new Uint8Array(base64ToArrayBuffer(payload.ciphertext));
const tag = new Uint8Array(base64ToArrayBuffer(payload.tag));

// In AES-GCM, the tag is appended at the end of ciphertext,
// so we must recombine them before decrypting.
const encryptedCombined = new Uint8Array(ciphertext.length + tag.length);
encryptedCombined.set(ciphertext, 0);
encryptedCombined.set(tag, ciphertext.length);

const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: nonce,
additionalData: header,
tagLength: 128,
},
key,
encryptedCombined.buffer,
);

return new Uint8Array(decrypted);
}

export async function getKeyFromHex(keyHex) {
const keyBytes = hexToArrayBuffer(keyHex);
const key = await window.crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "AES-GCM", length: 256 },
true, // extractable (can be set to false in production if you don't need to export it)
["encrypt", "decrypt"],
);

return key;
}

export async function getKeyFromBase64(keyBase64) {
const keyBytes = base64ToArrayBuffer(keyBase64);
const key = await window.crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "AES-GCM", length: 256 },
true, // extractable (can be set to false in production if you don't need to export it)
["encrypt", "decrypt"],
);

return key;
}
79 changes: 79 additions & 0 deletions AES256-encryptor/src/encryptData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// A helper function to convert ArrayBuffers to base64
function arrayBufferToBase64(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}

// A helper function to convert ArrayBuffers to Hex
function arrayBufferToHex(buffer) {
const bytes = new Uint8Array(buffer);
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
}

// Generate a new AES-GCM key (32-byte key)
export async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
);
}

/**
* Encrypts given data using the provided AES-GCM key.
* @param {CryptoKey} key - The AES-GCM key.
* @param {Uint8Array} data - The plaintext data to encrypt.
* @param {Uint8Array} header - Additional authenticated data.
* @returns {Object} payload containing nonce, header, ciphertext, tag
*/
export async function encryptData(key, data, header) {
// Generate a random nonce (nonce)
const nonce = window.crypto.getRandomValues(new Uint8Array(12));

const encrypted = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: nonce,
additionalData: header,
tagLength: 128,
},
key,
data,
);

// The encrypted result includes the ciphertext and tag together in AES-GCM.
// The last 16 bytes of the encrypted data are the tag.
const encryptedBytes = new Uint8Array(encrypted);
const ciphertext = encryptedBytes.slice(0, encryptedBytes.length - 16);
const tag = encryptedBytes.slice(encryptedBytes.length - 16);

const payload = {
nonce: arrayBufferToBase64(nonce.buffer),
header: arrayBufferToBase64(header.buffer),
ciphertext: arrayBufferToBase64(ciphertext.buffer),
tag: arrayBufferToBase64(tag.buffer),
};

return payload;
}

export async function getKeyHex(key) {
const rawKey = await window.crypto.subtle.exportKey("raw", key);
const keyHex = arrayBufferToHex(rawKey);

return keyHex;
}

export async function getKeyBase64(key) {
const rawKey = await window.crypto.subtle.exportKey("raw", key);
const keyBase64 = arrayBufferToBase64(rawKey);

return keyBase64;
}
8 changes: 8 additions & 0 deletions AES256-encryptor/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
11 changes: 11 additions & 0 deletions AES256-encryptor/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Loading
Loading