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

Add handle blocks #89

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
126 changes: 72 additions & 54 deletions js/pivx_shield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@ interface Block {
height: number;
}

/**
* Block that's deserialized in rust
*/
interface RustBlock {
txs: string[];
}

interface TransactionResult {
decrypted_notes: [Note, string][];
decrypted_new_notes: [Note, string][];
commitment_tree: string;
nullifiers: string[];
/**
* hex of the transactions belonging to the wallet
* i.e. either the spend or output belongs to us
*/
wallet_transactions: string[];
}

interface Transaction {
Expand Down Expand Up @@ -304,39 +316,70 @@ export class PIVXShield {
return { pivxShield, success: currVersion == PIVXShield.version };
}

/**
* Loop through the txs of a block and update useful shield data
* @param block - block outputted from any PIVX node
* @returns list of transactions belonging to the wallet
*/
async handleBlock(block: Block) {
let walletTransactions: string[] = [];
if (this.lastProcessedBlock > block.height) {
async handleBlocks(blocks: Block[]) {
if (blocks.length === 0) return [];
if (
!blocks.every((block, i) => {
if (i === 0) {
return block.height > this.lastProcessedBlock;
} else {
return block.height > blocks[i - 1].height;
}
})
) {
throw new Error(
"Blocks must be processed in a monotonically increasing order!",
"Blocks must be provided in monotonically increaisng order",
);
}
for (const tx of block.txs) {
const { belongToWallet, decryptedNewNotes } = await this.addTransaction(
tx.hex,
);
if (belongToWallet) {
walletTransactions.push(tx.hex);

for (const block of blocks) {
for (const tx of block.txs) {
this.pendingUnspentNotes.delete(tx.txid);
}
// Add all the decryptedNotes to the Nullifier->Note map
for (const note of decryptedNewNotes) {
const nullifier = await this.generateNullifierFromNote(note);
const simplifiedNote = {
value: note[0].value,
recipient: await this.getShieldAddressFromNote(note[0]),
}

const {
decrypted_notes,
decrypted_new_notes,
nullifiers,
commitment_tree,
wallet_transactions,
} = await this.callWorker<TransactionResult>(
"handle_blocks",
this.commitmentTree,
blocks.map((block) => {
return {
txs: block.txs.map(({ hex }) => hex),
};
this.mapNullifierNote.set(nullifier, simplifiedNote);
}
// Delete the corresponding pending transaction
this.pendingUnspentNotes.delete(tx.txid);
}) satisfies RustBlock[],
this.extfvk,
this.isTestnet,
this.unspentNotes,
);
this.commitmentTree = commitment_tree;
this.unspentNotes = [...decrypted_notes, ...decrypted_new_notes];
for (const note of decrypted_new_notes) {
const nullifier = await this.generateNullifierFromNote(note);
const simplifiedNote = {
value: note[0].value,
recipient: await this.getShieldAddressFromNote(note[0]),
};

this.mapNullifierNote.set(nullifier, simplifiedNote);
}
this.lastProcessedBlock = block.height;
return walletTransactions;
await this.removeSpentNotes(nullifiers);
this.lastProcessedBlock = blocks[blocks.length - 1].height;

return wallet_transactions;
}

/**
* Loop through the txs of a block and update useful shield data
* @param block - block outputted from any PIVX node
* @returns list of transactions belonging to the wallet
*/
async handleBlock(block: Block) {
return await this.handleBlocks([block]);
}

/**
Expand Down Expand Up @@ -371,37 +414,12 @@ export class PIVXShield {
}
return simplifiedNotes;
}
async addTransaction(hex: string) {
const res = await this.callWorker<TransactionResult>(
"handle_transaction",
this.commitmentTree,
hex,
this.extfvk,
this.isTestnet,
this.unspentNotes,
);
this.commitmentTree = res.commitment_tree;
this.unspentNotes = res.decrypted_notes.concat(res.decrypted_new_notes);

if (res.nullifiers.length > 0) {
await this.removeSpentNotes(res.nullifiers);
}
// Check if the transaction belongs to the wallet:
let belongToWallet = res.decrypted_new_notes.length > 0;
for (const nullifier of res.nullifiers) {
if (belongToWallet) {
break;
}
belongToWallet = belongToWallet || this.mapNullifierNote.has(nullifier);
}
return { belongToWallet, decryptedNewNotes: res.decrypted_new_notes };
}

async decryptTransaction(hex: string) {
const res = await this.callWorker<TransactionResult>(
"handle_transaction",
"handle_blocks",
this.commitmentTree,
hex,
[{ txs: [hex] }] satisfies RustBlock[],
this.extfvk,
this.isTestnet,
[],
Expand Down
70 changes: 46 additions & 24 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ pub use pivx_primitives::consensus::{BlockHeight, MAIN_NETWORK, TEST_NETWORK};
use pivx_primitives::legacy::Script;
pub use pivx_primitives::memo::MemoBytes;
pub use pivx_primitives::merkle_tree::{CommitmentTree, IncrementalWitness, MerklePath};
pub use pivx_primitives::sapling::PaymentAddress;
pub use pivx_primitives::transaction::builder::Progress;

use crate::keys::decode_generic_address;
Expand Down Expand Up @@ -88,44 +87,67 @@ pub struct JSTxSaplingData {
pub decrypted_new_notes: Vec<(Note, String)>,
pub nullifiers: Vec<String>,
pub commitment_tree: String,
pub wallet_transactions: Vec<String>,
}

#[derive(Serialize, Deserialize)]
pub struct Block {
txs: Vec<String>,
}

fn read_commitment_tree(tree_hex: &str) -> Result<CommitmentTree<Node>, Box<dyn Error>> {
let buff = Cursor::new(hex::decode(tree_hex)?);
Ok(CommitmentTree::<Node>::read(buff)?)
}

//Input a tx and return: the updated commitment merkletree, all the nullifier found in the tx and all the node decoded with the corresponding witness
#[wasm_bindgen]
pub fn handle_transaction(
pub fn handle_blocks(
Duddino marked this conversation as resolved.
Show resolved Hide resolved
tree_hex: &str,
tx: &str,
blocks: JsValue,
enc_extfvk: &str,
is_testnet: bool,
comp_notes: JsValue,
) -> Result<JsValue, JsValue> {
let buff = Cursor::new(
hex::decode(tree_hex).map_err(|_| "Cannot decode commitment tree from hexadecimal")?,
);
let mut tree =
CommitmentTree::<Node>::read(buff).map_err(|_| "Cannot decode commitment tree!")?;
let blocks: Vec<Block> = serde_wasm_bindgen::from_value(blocks)?;
let mut tree = read_commitment_tree(tree_hex).map_err(|_| "Couldn't read commitment tree")?;
let comp_note: Vec<(Note, String)> = serde_wasm_bindgen::from_value(comp_notes)?;
let extfvk =
decode_extended_full_viewing_key(enc_extfvk, is_testnet).map_err(|e| e.to_string())?;
let key = UnifiedFullViewingKey::new(Some(extfvk.to_diversifiable_full_viewing_key()), None)
.ok_or("Failed to create unified full viewing key")?;
let comp_note: Vec<(Note, String)> = serde_wasm_bindgen::from_value(comp_notes)?;
let mut comp_note = comp_note
.into_iter()
.map(|(note, witness)| {
let wit = Cursor::new(hex::decode(witness).unwrap());
(note, IncrementalWitness::read(wit).unwrap())
})
.collect::<Vec<_>>();
let mut new_comp_note: Vec<(Note, IncrementalWitness<Node>)> = vec![];
let nullifiers =
handle_transaction_internal(&mut tree, tx, key, true, &mut comp_note, &mut new_comp_note)
.map_err(|_| "Cannot decode tx")?;
let ser_comp_note: Vec<(Note, String)> =
serialize_comp_note(comp_note).map_err(|_| "Cannot serialize notes")?;
let ser_new_comp_note: Vec<(Note, String)> =
serialize_comp_note(new_comp_note).map_err(|_| "Cannot serialize notes")?;
let mut ser_nullifiers: Vec<String> = vec![];
let mut nullifiers = vec![];
let mut new_notes = vec![];
let mut wallet_transactions = vec![];
for block in blocks {
for tx in block.txs {
let old_note_length = new_notes.len();
let tx_nullifiers = handle_transaction(
&mut tree,
&tx,
key.clone(),
is_testnet,
&mut comp_note,
&mut new_notes,
)
.map_err(|_| "Couldn't handle transaction")?;
if !tx_nullifiers.is_empty() || old_note_length != new_notes.len() {
wallet_transactions.push(tx);
}
nullifiers.extend(tx_nullifiers);
}
}

let ser_comp_note = serialize_comp_note(comp_note).map_err(|_| "couldn't decrypt notes")?;
let ser_new_comp_note = serialize_comp_note(new_notes).map_err(|_| "couldn't decrypt notes")?;

let mut ser_nullifiers: Vec<String> = Vec::with_capacity(nullifiers.len());
for nullif in nullifiers.iter() {
ser_nullifiers.push(hex::encode(nullif.0));
}
Expand All @@ -134,13 +156,13 @@ pub fn handle_transaction(
tree.write(&mut buff)
.map_err(|_| "Cannot write tree to buffer")?;

let res: JSTxSaplingData = JSTxSaplingData {
Ok(serde_wasm_bindgen::to_value(&JSTxSaplingData {
decrypted_notes: ser_comp_note,
decrypted_new_notes: ser_new_comp_note,
nullifiers: ser_nullifiers,
commitment_tree: hex::encode(buff),
};
Ok(serde_wasm_bindgen::to_value(&res).map_err(|_| "Cannot serialize tx output")?)
wallet_transactions,
decrypted_new_notes: ser_new_comp_note,
})?)
}

pub fn serialize_comp_note(
Expand All @@ -158,7 +180,7 @@ pub fn serialize_comp_note(
}

//add a tx to a given commitment tree and the return a witness to each output
pub fn handle_transaction_internal(
pub fn handle_transaction(
tree: &mut CommitmentTree<Node>,
tx: &str,
key: UnifiedFullViewingKey,
Expand Down
7 changes: 3 additions & 4 deletions src/transaction/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::transaction::{create_transaction_internal, get_nullifier_from_note_internal};

use super::handle_transaction_internal;
use super::handle_transaction;
use either::Either;
use jubjub::Fr;
use pivx_client_backend::encoding;
Expand Down Expand Up @@ -30,8 +30,7 @@ fn check_tx_decryption() {
.expect("Failed to create key");
let mut new_comp_note = vec![];
let nullifiers =
handle_transaction_internal(&mut tree, tx, key, true, &mut vec![], &mut new_comp_note)
.unwrap();
handle_transaction(&mut tree, tx, key, true, &mut vec![], &mut new_comp_note).unwrap();
//This was a t-s tx
assert_eq!(nullifiers.len(), 0);
//Successfully decrypt exactly 1 note
Expand Down Expand Up @@ -86,7 +85,7 @@ pub async fn test_create_transaction() -> Result<(), Box<dyn Error>> {
let input_tx = "0300000001a347f398c8957afee7ef0fae759ff29feda25f3e72ab5052ea09729389fd48ca000000006b483045022100c332effdceaa20b3225d52d20059e443ed112d561329b81f78a9db338637e6a102204f948d70c37bfe96bbe776f8279ad5fa857c638338d8ce49f553a3ec60993d8f0121025c6802ec58464d8e65d5f01be0b7ce6e8404e4a99f28ea3bfe47efe40df9108cffffffff01e89bd55a050000001976a9147888a1affe25e5c7af03fffdbea29f13ee1be22b88ac0000000001006cca88ffffffff000150585de8e31e6c65dfa07981275f13ebb8c9c67d8c7d088622aacca6c35c67a23642ad16653acda3cf9b5230f652592197e578ea1eae78c2496a3dc274a4ba0b522216af4c44abd4e9b81964d0a801929df1cb543c4fea041d056cc493b2f8dd90db662a0a43dae4d80a8cb0bd93e22b7000c0bcdab93f94800b88268a78a4d77147f2f16bde98b2386e5ac4025260df5f63adaef13bc8d7a920dbd14fa7e8ef0c5ff29f00942341e29b15509bfa99b4b1bd0ba29c5cf2c419113c27288b3a8d8f4919a4845e47d4e5fe1d1081a98e0ee49bb0e422b339e949276a1264c236850d9beb94c7855143a4f00689d1bf8d996eee9f0ee865ca780713f5aa1990aa848d47a39ea45c926141a1ff5a5a45c2e2e78d470a180e02b3dd47e0b206a4542d4dbfc540023ee5cb35e54a086942657232c27a15c87eef6dd11587e871ea690a45002e0b60605d7c4ac7fde81a71aadde9d0cc0d5c347fbe942993bd2a69ca2ca98ea0885454e7387d609192094bea075b96f020a8ed7080b5ef0aaf13e73da67a68e377db62720724e8c0d2913487be2a3e39380b33a90f0336f07fa031345a42784460356987da3227bd40a8cf933e4b8661020cf566af785a5c9b404c84153a69d9280739cb567c6cdf41f7a1a38b4d5847b33956b4dfa847b386850eff2a3e9fe7434fb551d1c6d31fae868a2f491ebd4f382a0ac203652f4be9fb3cff3ed10e6295639af76a41e40e562862d4359e4874b565aa1bae4b68abb0a7fe66884b75250d16276521925ead4821c7f04338286c2e52e7772f980d7a228ad2b89c18c8eeaf3ee1b4d5c5a959fc93c1cda3f9340f8256a88076b96a8718efc5dcb3733e3e11f6ca1198a97a248ff4ab0a7e883e360b8495470badc7ec75f84e58d87ff83d03c594a11b9029177efa5fea026a71c2c328a6356bd447eb154ac39e43963118033fc1a72702b12e641e7dfa8f98a58e43d75f6b3350af9fc54e683c6074cfd76e86752d7f598b6816696a4f17ba5f10c983ad2f8e102f44f42b2d07b24fb599abbfd067373c4b00f9ae830fcdd79ca8fa8c90eb414f8f5bb070d1199b9e9fae7124772865e0d6f486d7f10f073a0d61bd9e8c94b7a963c831e76b5c07cef22c06877a683aca53396289b115f8b59989f3d5906c4961891ef4677ce73d752ee0ba8929056f38d7630b02db2188d512d733126fa2479217dcd5ed4061928e5ba374300d7a5fa08af2b64cbf5a2176e07b3a4a5bb4812c46c2e608d364d8589225f9b7620116e0cd6a175ab397d295ff0ee0100d2415db6c6736a0f6e2248a62c4c47b39103f67e30814cf3c9b0b82936546d4b81826cd8fdebe24ae91a81b69e7188f4b18c3422d61b367bc4ca92f8815c0fc42caf524b3337a8b9a6737557e1d471745e02a8e88a19fe730e224126d290a";

let mut new_notes = vec![];
let _nullifiers = handle_transaction_internal(
let _nullifiers = handle_transaction(
&mut commitment_tree,
input_tx,
key,
Expand Down
Loading