Skip to content

Commit 02f66db

Browse files
authored
Save nullifiers and balances to database (#35)
1 parent 3cf9381 commit 02f66db

File tree

8 files changed

+732
-74
lines changed

8 files changed

+732
-74
lines changed

packages/ironfish-native-module/ios/IronfishNativeModule.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,28 @@ public class IronfishNativeModule: Module {
9191
return readPartialFile(path: path, offset: offset, length: length)
9292
}
9393

94-
AsyncFunction("decryptNotesForOwner") { (noteEncrypted: [String], incomingHexKey: String, promise: Promise) in
94+
AsyncFunction("decryptNotesForOwner") { (noteEncrypteds: [String], incomingHexKey: String, promise: Promise) in
9595
Task {
96-
let decryptedNotes = try await decryptNotesForOwner(noteEncrypted: noteEncrypted, incomingHexKey: incomingHexKey)
96+
let decryptedNotes = try await decryptNotesForOwner(noteEncrypteds: noteEncrypteds, incomingHexKey: incomingHexKey)
9797
let expoOutputs = decryptedNotes.map { note in
9898
ExpoOutput(index: note.index, note: Field(wrappedValue: note.note))
9999
}
100100
promise.resolve(expoOutputs)
101101
}
102102
}
103+
104+
AsyncFunction("decryptNotesForSpender") { (noteEncrypteds: [String], outgoingHexKey: String, promise: Promise) in
105+
Task {
106+
let decryptedNotes = try await decryptNotesForSpender(noteEncrypteds: noteEncrypteds, outgoingHexKey: outgoingHexKey)
107+
let expoOutputs = decryptedNotes.map { note in
108+
ExpoOutput(index: note.index, note: Field(wrappedValue: note.note))
109+
}
110+
promise.resolve(expoOutputs)
111+
}
112+
}
113+
114+
AsyncFunction("nullifier") { (note: String, position: String, viewKey: String) throws -> String in
115+
return try nullifier(note: note, position: position, viewKey: viewKey)
116+
}
103117
}
104118
}

packages/ironfish-native-module/rust_lib/src/lib.rs

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use tokio_rayon::rayon::iter::{
1212
use zune_inflate::DeflateDecoder;
1313

1414
use crate::num::FromPrimitive;
15-
use ironfish::{keys::Language, serializing::bytes_to_hex, PublicAddress, SaplingKey};
15+
use ironfish::{keys::Language, serializing::bytes_to_hex, Note, PublicAddress, SaplingKey};
1616

1717
uniffi::setup_scaffolding!();
1818

@@ -159,49 +159,118 @@ pub fn read_partial_file(path: String, offset: u32, length: u32) -> Vec<u8> {
159159

160160
#[uniffi::export(async_runtime = "tokio")]
161161
pub async fn decrypt_notes_for_owner(
162-
note_encrypted: Vec<String>,
162+
note_encrypteds: Vec<String>,
163163
incoming_hex_key: String,
164164
) -> Result<Vec<DecryptedNote>, EnumError> {
165165
let incoming_view_key = ironfish::IncomingViewKey::from_hex(&incoming_hex_key)
166166
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
167167

168-
let idxes = note_encrypted
168+
let idxes = note_encrypteds
169169
.par_iter()
170170
.enumerate()
171171
.filter_map(|(i, output)| {
172172
let bytes = const_hex::decode(output);
173173
if bytes.is_err() {
174-
println!("error converting hex to bytes");
174+
eprintln!("error converting hex to bytes");
175175
return None;
176176
}
177177

178178
let note = ironfish::MerkleNote::read(bytes.unwrap().as_slice());
179179
if note.is_err() {
180-
println!("error reading bytes");
180+
eprintln!("error reading bytes");
181181
return None;
182182
}
183183

184184
let dec: Result<ironfish::Note, ironfish::errors::IronfishError> =
185185
note.unwrap().decrypt_note_for_owner(&incoming_view_key);
186-
if dec.is_ok() {
187-
let mut vec = vec![];
188-
if dec
189-
.unwrap()
190-
.write(&mut vec)
191-
.map_err(|e| EnumError::Error { msg: e.to_string() })
192-
.is_err()
193-
{
194-
println!("error writing bytes");
195-
return None;
196-
}
197-
198-
return Some(DecryptedNote {
199-
index: i as u32,
200-
note: const_hex::encode(&vec),
201-
});
186+
187+
if dec.is_err() {
188+
return None;
189+
}
190+
191+
let mut vec = vec![];
192+
if dec
193+
.unwrap()
194+
.write(&mut vec)
195+
.map_err(|e| EnumError::Error { msg: e.to_string() })
196+
.is_err()
197+
{
198+
eprintln!("error writing bytes");
199+
return None;
202200
}
203-
None
201+
202+
Some(DecryptedNote {
203+
index: i as u32,
204+
note: const_hex::encode(&vec),
205+
})
204206
});
205207

206208
Ok(idxes.collect())
207209
}
210+
211+
#[uniffi::export(async_runtime = "tokio")]
212+
pub async fn decrypt_notes_for_spender(
213+
note_encrypteds: Vec<String>,
214+
outgoing_hex_key: String,
215+
) -> Result<Vec<DecryptedNote>, EnumError> {
216+
let outgoing_view_key = ironfish::OutgoingViewKey::from_hex(&outgoing_hex_key)
217+
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
218+
219+
let idxes = note_encrypteds
220+
.par_iter()
221+
.enumerate()
222+
.filter_map(|(i, output)| {
223+
let bytes = const_hex::decode(output);
224+
if bytes.is_err() {
225+
eprintln!("error converting hex to bytes");
226+
return None;
227+
}
228+
229+
let note = ironfish::MerkleNote::read(bytes.unwrap().as_slice());
230+
if note.is_err() {
231+
eprintln!("error reading bytes");
232+
return None;
233+
}
234+
235+
let dec: Result<ironfish::Note, ironfish::errors::IronfishError> =
236+
note.unwrap().decrypt_note_for_spender(&outgoing_view_key);
237+
238+
if dec.is_err() {
239+
return None;
240+
}
241+
242+
let mut vec = vec![];
243+
if dec
244+
.unwrap()
245+
.write(&mut vec)
246+
.map_err(|e| EnumError::Error { msg: e.to_string() })
247+
.is_err()
248+
{
249+
eprintln!("error writing bytes");
250+
return None;
251+
}
252+
253+
Some(DecryptedNote {
254+
index: i as u32,
255+
note: const_hex::encode(&vec),
256+
})
257+
});
258+
259+
Ok(idxes.collect())
260+
}
261+
262+
#[uniffi::export]
263+
pub fn nullifier(note: String, position: String, view_key: String) -> Result<String, EnumError> {
264+
let view_key = ironfish::ViewKey::from_hex(&view_key)
265+
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
266+
267+
let bytes = const_hex::decode(note).map_err(|e| EnumError::Error { msg: e.to_string() })?;
268+
269+
let position_u64 = position
270+
.parse::<u64>()
271+
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
272+
273+
let note = Note::read(&bytes[..]).map_err(|e| EnumError::Error { msg: e.to_string() })?;
274+
275+
Ok(const_hex::encode(note.nullifier(&view_key, position_u64).0))
276+
}

packages/ironfish-native-module/src/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,21 @@ export function decryptNotesForOwner(
7777
incomingHexKey,
7878
);
7979
}
80+
81+
export function decryptNotesForSpender(
82+
noteEncrypted: string[],
83+
outgoingHexKey: string,
84+
): Promise<{ index: number; note: string }[]> {
85+
return IronfishNativeModule.decryptNotesForSpender(
86+
noteEncrypted,
87+
outgoingHexKey,
88+
);
89+
}
90+
91+
export function nullifier(
92+
note: string,
93+
position: string,
94+
viewKey: string,
95+
): Promise<string> {
96+
return IronfishNativeModule.nullifier(note, position, viewKey);
97+
}

packages/mobile-app/app/(tabs)/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ export default function Balances() {
1515
},
1616
);
1717

18+
const getAccountResult = facade.getAccount.useQuery(
19+
{ name: account },
20+
{
21+
refetchInterval: 1000,
22+
},
23+
);
24+
1825
const getAccountsResult = facade.getAccounts.useQuery();
1926

2027
useEffect(() => {
@@ -38,6 +45,10 @@ export default function Balances() {
3845
</View>
3946
))}
4047
</ScrollView>
48+
<Text style={{ fontWeight: 700, fontSize: 24 }}>Balance</Text>
49+
{getAccountResult.data && (
50+
<Text>{`IRON ${getAccountResult.data.balances.iron.unconfirmed}`}</Text>
51+
)}
4152
<StatusBar style="auto" />
4253
</View>
4354
);

packages/mobile-app/data/facades/wallet/handlers.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { f } from "data-facade";
22
import {
33
Account,
4+
AccountBalance,
45
AccountSettings,
56
Output,
67
Transaction,
@@ -77,21 +78,43 @@ export const walletHandlers = f.facade<WalletHandlers>({
7778
if (!account) {
7879
throw new Error(`Account ${name} does not exist`);
7980
}
81+
82+
const balances = await wallet.getBalances(account.id, Network.TESTNET);
83+
84+
const ironBalance: AccountBalance = {
85+
assetId:
86+
"51f33a2f14f92735e562dc658a5639279ddca3d5079a6d1242b2a588a9cbf44c",
87+
// TODO: Implement available balance in Wallet
88+
available: "0",
89+
// TODO: Implement confirmed balance in Wallet
90+
confirmed: "0",
91+
// TODO: Implement pending balance in Wallet
92+
pending: "0",
93+
unconfirmed: "0",
94+
};
95+
const customBalances: AccountBalance[] = [];
96+
97+
for (const balance of balances) {
98+
if (Uint8ArrayUtils.toHex(balance.assetId) === ironBalance.assetId) {
99+
ironBalance.unconfirmed = balance.value;
100+
} else {
101+
customBalances.push({
102+
assetId: Uint8ArrayUtils.toHex(balance.assetId),
103+
available: "0",
104+
confirmed: "0",
105+
pending: "0",
106+
unconfirmed: balance.value,
107+
});
108+
}
109+
}
110+
80111
const result: Account = {
81112
name: account.name,
82113
viewOnly: account.viewOnly,
83114
publicAddress: account.publicAddress,
84-
// TODO: Implement balances in Wallet
85115
balances: {
86-
iron: {
87-
assetId:
88-
"51f33a2f14f92735e562dc658a5639279ddca3d5079a6d1242b2a588a9cbf44c",
89-
available: "0",
90-
confirmed: "0",
91-
pending: "0",
92-
unconfirmed: "0",
93-
},
94-
custom: [],
116+
iron: ironBalance,
117+
custom: customBalances,
95118
},
96119
// TODO: Implement account syncing in Wallet
97120
head: null,

0 commit comments

Comments
 (0)