Skip to content

Commit

Permalink
feat: added WASM functions for private data upload to vault using rec…
Browse files Browse the repository at this point in the history
…eipts & added metamask test
  • Loading branch information
mickvandijke committed Nov 4, 2024
1 parent 374e84b commit 21d4044
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 68 deletions.
4 changes: 2 additions & 2 deletions autonomi/examples/metamask/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<body>
<!-- credits: https://rustwasm.github.io/docs/wasm-bindgen/examples/without-a-bundler.html -->
<script type="module">
import {externalSignerPut} from "./index.js";
import {externalSignerPrivateDataPutToVault} from "./index.js";

async function run() {
document.getElementById("btn-run").disabled = true;
const peerAddr = document.getElementById('peer_id').value;
await externalSignerPut(peerAddr);
await externalSignerPrivateDataPutToVault(peerAddr);
}

document.getElementById("btn-run").addEventListener("click", run, false);
Expand Down
188 changes: 136 additions & 52 deletions autonomi/examples/metamask/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import init, * as autonomi from '../../pkg/autonomi.js';

export async function externalSignerPut(peerAddr) {
export async function externalSignerPrivateDataPutToVault(peerAddr) {
try {
// Check if MetaMask (window.ethereum) is available
if (typeof window.ethereum === 'undefined') {
Expand All @@ -21,76 +21,107 @@ export async function externalSignerPut(peerAddr) {
// Generate 1MB of random bytes in a Uint8Array
const data = new Uint8Array(1024 * 1024).map(() => Math.floor(Math.random() * 256));

// Get quotes and payment information (this would need actual implementation)
const [quotes, quotePayments, free_chunks] = await client.getQuotes(data);
// Encrypt the data to chunks
const [dataMapChunk, dataChunks, dataMapChunkAddress, dataChunkAddresses] = autonomi.encryptData(data);

// Get the EVM network
let evmNetwork = autonomi.getEvmNetwork();
// Fetch quotes for the chunks
const [quotes, quotePayments, _freeChunks] = await client.getQuotes(dataChunkAddresses);

// Form quotes payment calldata
const payForQuotesCalldata = autonomi.getPayForQuotesCalldata(
evmNetwork,
quotePayments
);
// Pay for data chunks (not the data map)
const receipt = await executeQuotePayments(sender, quotes, quotePayments);

// Form approve to spend tokens calldata
const approveCalldata = autonomi.getApproveToSpendTokensCalldata(
evmNetwork,
payForQuotesCalldata.approve_spender,
payForQuotesCalldata.approve_amount
);
// Wait for a few seconds to allow tx to confirm
await new Promise(resolve => setTimeout(resolve, 5000));

console.log("Sending approve transaction..");
// Upload the data
const privateDataAccess = await client.privateDataPutWithReceipt(data, receipt);

// Approve to spend tokens
let txHash = await sendTransaction({
from: sender,
to: approveCalldata[1],
data: approveCalldata[0]
});
// Create a private archive
const privateArchive = new autonomi.PrivateArchive();

// Add our data's data map chunk to the private archive
privateArchive.addNewFile("test", privateDataAccess);

// Get the private archive's bytes
const privateArchiveBytes = privateArchive.bytes();

// Encrypt the private archive to chunks
const [paDataMapChunk, paDataChunks, paDataMapChunkAddress, paDataChunkAddresses] = autonomi.encryptData(privateArchiveBytes);

await waitForTransactionConfirmation(txHash);
// Fetch quotes for the private archive chunks
const [paQuotes, paQuotePayments, _paFreeChunks] = await client.getQuotes(paDataChunkAddresses);

let payments = {};
// Pay for the private archive chunks (not the data map)
const paReceipt = await executeQuotePayments(sender, paQuotes, paQuotePayments);

// Execute batched quote payment transactions
for (const [calldata, quoteHashes] of payForQuotesCalldata.batched_calldata_map) {
console.log("Sending batched data payment transaction..");
// Wait for a few seconds to allow tx to confirm
await new Promise(resolve => setTimeout(resolve, 5000));

let txHash = await sendTransaction({
from: sender,
to: payForQuotesCalldata.to,
data: calldata
});
// Upload the private archive
const privateArchiveAccess = await client.privateArchivePutWithReceipt(privateArchive, paReceipt);

await waitForTransactionConfirmation(txHash);
// Generate a random vault key (should normally be derived from a constant signature)
const vaultKey = autonomi.genSecretKey();

// Record the transaction hashes for each quote
quoteHashes.forEach(quoteHash => {
payments[quoteHash] = txHash;
});
// Fetch user data from vault (won't exist, so will be empty)
let userData;

try {
userData = await client.getUserDataFromVault(vaultKey);
} catch (err) {
userData = new autonomi.UserData();
}

// Generate payment proof
const proof = autonomi.getPaymentProofFromQuotesAndPayments(quotes, payments);
// Add archive to user data
userData.addPrivateFileArchive(privateArchiveAccess, "test-archive");

// Submit the data with proof of payment
const addr = await client.dataPutWithProof(data, proof);
// Get or create a scratchpad for the user data
let scratchpad = await client.getOrCreateUserDataScratchpad(vaultKey);

// Wait for a few seconds to allow data to propagate
await new Promise(resolve => setTimeout(resolve, 10000));
// Content address of the scratchpad
let scratchPadAddress = scratchpad.xorName();

// Fetch the data back
const fetchedData = await client.dataGet(addr);
// Fetch quotes for the scratchpad
const [spQuotes, spQuotePayments, _spFreeChunks] = await client.getQuotes(scratchPadAddress ? [scratchPadAddress] : []);

if (fetchedData.toString() === data.toString()) {
console.log("Fetched data matches the original data!");
} else {
throw new Error("Fetched data does not match original data!")
}
// Pay for the private archive chunks (not the data map)
const spReceipt = await executeQuotePayments(sender, spQuotes, spQuotePayments);

// Wait for a few seconds to allow tx to confirm
await new Promise(resolve => setTimeout(resolve, 5000));

console.log("Data successfully put and verified!");
// Update vault
await client.putUserDataToVaultWithReceipt(userData, spReceipt, vaultKey);

// VERIFY UPLOADED DATA

// Fetch user data
let fetchedUserData = await client.getUserDataFromVault(vaultKey);

// Get the first key
let fetchedPrivateArchiveAccess = fetchedUserData.privateFileArchives().keys().next().value;

// Get private archive
let fetchedPrivateArchive = await client.privateArchiveGet(fetchedPrivateArchiveAccess);

// Select first file in private archive
let [fetchedFilePath, [fetchedPrivateFileAccess, fetchedFileMetadata]] = fetchedPrivateArchive.map().entries().next().value;

console.log(fetchedFilePath);
console.log(fetchedPrivateFileAccess);
console.log(fetchedFileMetadata);

// Fetch private file/data
let fetchedPrivateFile = await client.privateDataGet(fetchedPrivateFileAccess);

// Compare to original data
console.log("Comparing fetched data to original data..");

if (fetchedPrivateFile.toString() === data.toString()) {
console.log("Data matches! Private file upload to vault was successful!");
} else {
console.log("Data does not match!! Something went wrong..")
}
} catch (error) {
console.error("An error occurred:", error);
}
Expand Down Expand Up @@ -146,4 +177,57 @@ async function waitForTransactionConfirmation(txHash) {
// Wait for 1 second before checking again
await delay(1000);
}
}

const executeQuotePayments = async (sender, quotes, quotePayments) => {
// Get the EVM network
let evmNetwork = autonomi.getEvmNetwork();

// Form quotes payment calldata
const payForQuotesCalldata = autonomi.getPayForQuotesCalldata(
evmNetwork,
quotePayments
);

// Form approve to spend tokens calldata
const approveCalldata = autonomi.getApproveToSpendTokensCalldata(
evmNetwork,
payForQuotesCalldata.approve_spender,
payForQuotesCalldata.approve_amount
);

console.log("Sending approve transaction..");

// Approve to spend tokens
let hash = await sendTransaction({
from: sender,
to: approveCalldata[1],
data: approveCalldata[0]
});

// Wait for approve tx to confirm
await waitForTransactionConfirmation(hash);

let payments = {};

// Execute batched quote payment transactions
for (const [calldata, quoteHashes] of payForQuotesCalldata.batched_calldata_map) {
console.log("Sending batched data payment transaction..");

let hash = await sendTransaction({
from: sender,
to: payForQuotesCalldata.to,
data: calldata
});

await waitForTransactionConfirmation(hash);

// Record the transaction hashes for each quote
quoteHashes.forEach(quoteHash => {
payments[quoteHash] = hash;
});
}

// Generate receipt
return autonomi.getReceiptFromQuotesAndPayments(quotes, payments);
}
13 changes: 3 additions & 10 deletions autonomi/src/client/external_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,12 @@ impl Client {

/// Encrypts data as chunks.
///
/// Returns the data map chunk, file chunks and a list of all content addresses including the data map.
pub fn encrypt_data(data: Bytes) -> Result<(Chunk, Vec<Chunk>, Vec<XorName>), PutError> {
/// Returns the data map chunk and file chunks.
pub fn encrypt_data(data: Bytes) -> Result<(Chunk, Vec<Chunk>), PutError> {
let now = sn_networking::target_arch::Instant::now();
let result = encrypt(data)?;

debug!("Encryption took: {:.2?}", now.elapsed());

let map_xor_name = *result.0.address().xorname();
let mut xor_names = vec![map_xor_name];

for chunk in &result.1 {
xor_names.push(*chunk.name());
}

Ok((result.0, result.1, xor_names))
Ok((result.0, result.1))
}
Loading

0 comments on commit 21d4044

Please sign in to comment.