Skip to content

Commit

Permalink
Merge pull request #32 from project-kardeshev/fix-strategy-export
Browse files Browse the repository at this point in the history
fix(decryption): add decryption for eth and ar wallet
  • Loading branch information
atticusofsparta authored Oct 31, 2024
2 parents 9b17eb3 + d23610b commit 06643dd
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 60 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@linaria/babel-preset": "^4.4.5",
"@linaria/core": "^4.2.10",
"@linaria/react": "^4.3.8",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/providers": "^18.1.0",
"@othent/kms": "2.1.1",
"@project-kardeshev/ao-sdk": "1.0.0-alpha.3",
Expand Down
130 changes: 130 additions & 0 deletions src/stories/Encryption.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { ComponentStory } from '@storybook/react';
import { useState } from 'react';

import { ConnectButton } from '../components/ConnectButton';
import { AOWalletKit } from '../components/Provider';
import { useActiveStrategy } from '../hooks';
import { fromB64Url, toB64Url } from '../utils';

function Encrypt() {
const strategy = useActiveStrategy();
const message = 'Encrypt me!';
const [encrypted, setEncrypted] = useState<string | null>(null);
const [decrypted, setDecrypted] = useState<string | null>(null);

async function encrypt() {
if (strategy?.encrypt && strategy?.decrypt) {
const encryptedMessage = await strategy.encrypt(
new TextEncoder().encode(message),
);

setEncrypted(toB64Url(Buffer.from(encryptedMessage)));
}
}

async function decrypt() {
if (strategy?.decrypt && encrypted) {
const decryptedMessage = await strategy.decrypt(fromB64Url(encrypted));
setDecrypted(new TextDecoder().decode(decryptedMessage));
}
}
return (
<>
<button
onClick={encrypt}
style={{
backgroundColor: 'rgb(0, 122, 255)',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer',
marginTop: '1rem',
}}
>
Encrypt
</button>

<button
onClick={decrypt}
style={{
backgroundColor: 'rgb(0, 122, 255)',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer',
marginTop: '1rem',
}}
>
Decrypt
</button>
<p>
Encrypted: <strong>{encrypted ?? ''}</strong>
</p>
<p>
Decrypted: <strong>{decrypted ?? ''}</strong>
</p>
</>
);
}

export default {
name: 'Encryption',
component: Encrypt,
};

const Template: ComponentStory<typeof AOWalletKit> = (props) => {
return (
<div
style={{
height: '30vh',
}}
>
<AOWalletKit {...props}>
<ConnectButton accent={'rgb(0, 122, 255)'} />
<Encrypt />
</AOWalletKit>
</div>
);
};

export const Basic = Template.bind({});

Basic.args = {
theme: {
displayTheme: 'light',
accent: {
r: 0,
g: 0,
b: 0,
},
titleHighlight: {
r: 0,
g: 122,
b: 255,
},
radius: 'default',
font: {
fontFamily: 'Manrope',
},
},
config: {
permissions: [
'ACCESS_ADDRESS',
'ACCESS_ALL_ADDRESSES',
'ENCRYPT',
'DECRYPT',
],
ensurePermissions: true,
appInfo: {
name: 'Test App',
logo: 'https://arweave.net/tQUcL4wlNj_NED2VjUGUhfCTJ6pDN9P0e3CbnHo3vUE',
},
gatewayConfig: {
host: 'arweave.net',
port: 443,
protocol: 'https',
},
},
};
77 changes: 38 additions & 39 deletions src/stories/SignDataItem.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,49 @@ import { useActiveStrategy, useApi } from '../hooks';
import { WagmiStrategy } from '../strategy/strategies/Wagmi';
import { createWagmiDataItemSigner } from '../utils';

export default {
name: 'ConnectButton',
component: ConnectButton,
};

const Template: ComponentStory<typeof AOWalletKit> = (props) => {
function Sign() {
const api = useApi();
const strategy = useActiveStrategy();
const [txId, setTxId] = useState<string>('');
function Sign() {
const api = useApi();
const strategy = useActiveStrategy();
const [txId, setTxId] = useState<string>('');

async function sign() {
console.log(strategy, api);
if (strategy instanceof WagmiStrategy) {
const signer = await createWagmiDataItemSigner(strategy.config);
async function sign() {
console.log(strategy, api);
if (strategy instanceof WagmiStrategy) {
const signer = await createWagmiDataItemSigner(strategy.config);

const partialData = {
data: 'blah',
tags: [{ name: 'test', value: 'test' }],
target: ''.padEnd(43, '1'),
};
const { id } = await signer(partialData);
console.log('Signed:', id);
setTxId(id);
}
const partialData = {
data: 'blah',
tags: [{ name: 'test', value: 'test' }],
target: ''.padEnd(43, '1'),
};
const { id } = await signer(partialData);
console.log('Signed:', id);
setTxId(id);
}
return (
<button
onClick={sign}
style={{
backgroundColor: 'rgb(0, 122, 255)',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer',
marginTop: '1rem',
}}
>
Sign Data {txId}
</button>
);
}
return (
<button
onClick={sign}
style={{
backgroundColor: 'rgb(0, 122, 255)',
color: 'white',
padding: '0.5rem 1rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer',
marginTop: '1rem',
}}
>
Sign Data {txId}
</button>
);
}
export default {
name: 'Sign Data Item',
component: Sign,
};

const Template: ComponentStory<typeof AOWalletKit> = (props) => {
return (
<div
style={{
Expand Down
11 changes: 9 additions & 2 deletions src/strategy/Strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,20 @@ export abstract class Strategy {
): Promise<Transaction>;
public abstract getPermissions(): Promise<PermissionType[]>;
public abstract getWalletNames?(): Promise<{ [addr: string]: string }>;
/**
* @description Encrypts data using the active wallet's public key. Important: this method is not available in all strategies, and for arweave wallets the data should be converted to a b64url string after encryption
* - and then back from a b64url string to a Uint8Array before decryption. Decoding to UTF-8 string will break the data, but b64url encoding/decoding will not.
* @param data - data to encrypt, Uint8Array
* @param algorithm - optional, defaults to RsaOaepParams
*
*/
public abstract encrypt?(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
algorithm?: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
): Promise<Uint8Array>;
public abstract decrypt?(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
algorithm?: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
): Promise<Uint8Array>;
public abstract getArweaveConfig?(): Promise<GatewayConfig>;
public abstract signature?(
Expand Down
8 changes: 6 additions & 2 deletions src/strategy/strategies/BrowserWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,18 @@ export class BrowserWalletStrategy implements Strategy {

public async encrypt(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams = {
name: 'RSA-OAEP',
},
): Promise<Uint8Array> {
return await callWindowApi('encrypt', [data, algorithm]);
}

public async decrypt(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams = {
name: 'RSA-OAEP',
},
): Promise<Uint8Array> {
return await callWindowApi('decrypt', [data, algorithm]);
}
Expand Down
43 changes: 27 additions & 16 deletions src/strategy/strategies/Wagmi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { encrypt } from '@metamask/eth-sig-util';
import { AoSigner } from '@project-kardeshev/ao-sdk/web';
import { connect, disconnect, getAccount } from '@wagmi/core';
import { DataItem, DispatchResult, PermissionType } from 'arconnect';
Expand Down Expand Up @@ -194,24 +195,34 @@ export class WagmiStrategy implements Strategy {
) {
listener(new CustomEvent(this.account ?? ''));
}
// unused apis, no need to lint - remove when used
/* eslint-disable */
public async encrypt(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
): Promise<Uint8Array> {
throw new Error(
'Method not yet available on ethereum wallets. (coming soon)',
);

public async encrypt(data: BufferSource): Promise<Uint8Array> {
const signer = await getEthersSigner(this.config);
const publicKey = await signer.provider.send('eth_getEncryptionPublicKey', [
this.account,
]);
const stringData = new TextDecoder().decode(data);
const encryptedData = encrypt({
data: stringData,
publicKey: publicKey,
version: 'x25519-xsalsa20-poly1305',
});
return new TextEncoder().encode(JSON.stringify(encryptedData));
}
public async decrypt(
data: BufferSource,
algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams,
): Promise<Uint8Array> {
throw new Error(
'Method not yet available on ethereum wallets. (coming soon)',
);
public async decrypt(data: BufferSource): Promise<Uint8Array> {
const signer = await getEthersSigner(this.config);
const address = await this.getActiveAddress();
// doing all this bullshit to avoid use of buffer
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const hexData = `0x${Array.from(encoder.encode(decoder.decode(data)))
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')}`;
return signer.provider.send('eth_decrypt', [hexData, address]);
}

// unused apis, no need to lint - remove when used
/* eslint-disable */
public async getPermissions(): Promise<PermissionType[]> {
return [
'ACCESS_ADDRESS',
Expand Down
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2454,6 +2454,14 @@
"@types/mdx" "^2.0.0"
"@types/react" ">=16"

"@metamask/abi-utils@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@metamask/abi-utils/-/abi-utils-2.0.4.tgz#20908c1d910f7a17a89fdf5778a5c59d5cb8b8be"
integrity sha512-StnIgUB75x7a7AgUhiaUZDpCsqGp7VkNnZh2XivXkJ6mPkE83U8ARGQj5MbRis7VJY8BC5V1AbB1fjdh0hupPQ==
dependencies:
"@metamask/superstruct" "^3.1.0"
"@metamask/utils" "^9.0.0"

"@metamask/eth-json-rpc-provider@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz#3fd5316c767847f4ca107518b611b15396a5a32c"
Expand All @@ -2463,6 +2471,18 @@
"@metamask/safe-event-emitter" "^3.0.0"
"@metamask/utils" "^5.0.1"

"@metamask/eth-sig-util@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-8.0.0.tgz#6310d93cd1101cab3cc6bc2a1ff526290ed2695b"
integrity sha512-IwE6aoxUL39IhmsAgE4nk+OZbNo+ThFZRNsUjE1pjdEa4MFpWzm1Rue4zJ5DMy1oUyZBi/aiCLMhdMnjl2bh2Q==
dependencies:
"@ethereumjs/util" "^8.1.0"
"@metamask/abi-utils" "^2.0.4"
"@metamask/utils" "^9.0.0"
"@scure/base" "~1.1.3"
ethereum-cryptography "^2.1.2"
tweetnacl "^1.0.3"

"@metamask/json-rpc-engine@^10.0.1":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.1.tgz#432e4b42770ecd4da8a89f94b52cdeac982bdca3"
Expand Down Expand Up @@ -9220,7 +9240,7 @@ eth-rpc-errors@^4.0.2, eth-rpc-errors@^4.0.3:
dependencies:
fast-safe-stringify "^2.0.6"

ethereum-cryptography@^2.0.0:
ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz#58f2810f8e020aecb97de8c8c76147600b0b8ccf"
integrity sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==
Expand Down

0 comments on commit 06643dd

Please sign in to comment.