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

Scaffold sendTransaction function in wallet #51

Merged
merged 11 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 2 additions & 0 deletions packages/data-facade/src/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
function buildUseQuery(baseQueryKey: string) {
return <TResolver extends ResolverFunc, TReturn extends Awaited<ReturnType<TResolver>>>(resolver: TResolver) => {
return {
buildQueryKey: (args?: unknown) => buildQueryKey(args),
resolver,
useQuery: (args?: unknown, opts?: UseQueryOptionsWithoutKey<TReturn>): UseQueryResult<TReturn> => {
return useQuery<TReturn>({
...opts,
Expand Down
4 changes: 3 additions & 1 deletion packages/data-facade/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ type UseQueryType<
export type HandlerQueryBuilderReturn<TResolver extends ResolverFunc> = (
baseQueryKey: string,
) => {
useQuery: UseQueryType<TResolver, Awaited<ReturnType<TResolver>>>;
buildQueryKey: (args: Parameters<TResolver>["length"] extends 0 ? null | undefined : Parameters<TResolver>[0]) => any[];
resolver: (args: Parameters<TResolver>["length"] extends 0 ? null | undefined : Parameters<TResolver>[0]) => Awaited<ReturnType<TResolver>>;
useQuery: UseQueryType<TResolver>;
};

// Mutation type utils
Expand Down
74 changes: 74 additions & 0 deletions packages/ironfish-native-module/ios/IronfishNativeModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ struct ExpoOutput : Record {
var note: String
}

struct SpendComponentsInput: Record {
@Field
var components: [SpendComponentInput]
}

struct SpendComponentInput: Record {
@Field
var note: String

@Field
var witnessRootHash: String

@Field
var witnessTreeSize: String

@Field
var witnessAuthPath: [WitnessNodeInput]
}

struct WitnessNodeInput: Record {
@Field
var side: String

@Field
var hashOfSibling: String
}

struct OutputsInput: Record {
@Field
var outputs: [String]
}

public class IronfishNativeModule: Module {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
Expand Down Expand Up @@ -114,5 +146,47 @@ public class IronfishNativeModule: Module {
AsyncFunction("nullifier") { (note: String, position: String, viewKey: String) throws -> String in
return try nullifier(note: note, position: position, viewKey: viewKey)
}

AsyncFunction ("createNote") { (owner: Data, value: String, memo: Data, assetId: Data, sender: Data) throws -> Data in
do {
let note = try createNote(params: NoteParams(owner: owner, value: UInt64(value)!, memo: memo, assetId: assetId, sender: sender))
return note
} catch let error as NSError {
print("Unexpected error: \(error.debugDescription)")
throw error
}
}

AsyncFunction("createTransaction") { (spendComponents: SpendComponentsInput, outputs: OutputsInput, spendingKey: Data) throws -> Data in
let spendComponentsConverted = spendComponents.components.map { spendComponent in
let witnessAuthPath: [WitnessNode] = spendComponent.witnessAuthPath.map { WitnessNode(side: $0.side, hashOfSibling: Data(hexString: $0.hashOfSibling)!) }
return SpendComponents(note: Data(hexString: spendComponent.note)!, witnessRootHash: Data(hexString: spendComponent.witnessRootHash)!, witnessTreeSize: UInt64(spendComponent.witnessTreeSize)!, witnessAuthPath: witnessAuthPath)
}
do {
let transaction = try createTransaction(spendComponents: spendComponentsConverted, outputs: outputs.outputs.map {Data(hexString: $0)!}, spendingKey: spendingKey)
return transaction
} catch let error as NSError {
print("Unexpected error: \(error.debugDescription)")
throw error
}
}
}
}

extension Data {
init?(hexString: String) {
let length = hexString.count / 2
var data = Data(capacity: length)
for i in 0..<length {
let j = hexString.index(hexString.startIndex, offsetBy: i*2)
let k = hexString.index(j, offsetBy: 2)
let bytes = hexString[j..<k]
if var num = UInt8(bytes, radix: 16) {
data.append(&num, count: 1)
} else {
return nil
}
}
self = data
}
}
131 changes: 129 additions & 2 deletions packages/ironfish-native-module/rust_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ extern crate num;
extern crate num_derive;

use std::{
cmp,
fs::{self, File},
io::{Read, Seek, SeekFrom},
io::{Cursor, Read, Seek, SeekFrom},
};
use tokio_rayon::rayon::iter::{
IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator,
};
use zune_inflate::DeflateDecoder;

use crate::num::FromPrimitive;
use ironfish::{keys::Language, serializing::bytes_to_hex, Note, PublicAddress, SaplingKey};
use ironfish::sapling_bls12::Scalar;
use ironfish::{
assets::asset::ID_LENGTH, keys::Language, note::MEMO_SIZE, serializing::bytes_to_hex,
MerkleNoteHash, Note, PublicAddress, SaplingKey,
};

uniffi::setup_scaffolding!();

Expand Down Expand Up @@ -65,6 +70,29 @@ pub struct DecryptedNote {
pub note: String,
}

#[derive(uniffi::Record)]
pub struct WitnessNode {
pub side: String,
pub hash_of_sibling: Vec<u8>,
}

#[derive(uniffi::Record)]
pub struct SpendComponents {
pub note: Vec<u8>,
pub witness_root_hash: Vec<u8>,
pub witness_tree_size: u64,
pub witness_auth_path: Vec<WitnessNode>,
}

#[derive(uniffi::Record)]
pub struct NoteParams {
owner: Vec<u8>,
value: u64,
memo: Vec<u8>,
asset_id: Vec<u8>,
sender: Vec<u8>,
}

#[uniffi::export]
fn generate_key() -> Key {
let sapling_key: SaplingKey = SaplingKey::generate_key();
Expand Down Expand Up @@ -274,3 +302,102 @@ pub fn nullifier(note: String, position: String, view_key: String) -> Result<Str

Ok(const_hex::encode(note.nullifier(&view_key, position_u64).0))
}

#[uniffi::export]
pub fn create_note(params: NoteParams) -> Result<Vec<u8>, EnumError> {
let mut owner_vec = Cursor::new(params.owner);
let mut sender_vec = Cursor::new(params.sender);

//memo
let num_to_copy = cmp::min(params.memo.len(), MEMO_SIZE);
let mut memo_bytes = [0; MEMO_SIZE];
memo_bytes[..num_to_copy].copy_from_slice(&params.memo[..num_to_copy]);

//asset id
let mut asset_id_bytes = [0; ID_LENGTH];
asset_id_bytes.clone_from_slice(&params.asset_id[0..ID_LENGTH]);
let asset_id = asset_id_bytes.try_into().map_err(|_| EnumError::Error {
msg: "Error converting to AssetIdentifier".to_string(),
})?;

let note = Note::new(
PublicAddress::read(&mut owner_vec).map_err(|e| EnumError::Error { msg: e.to_string() })?,
params.value,
memo_bytes,
asset_id,
PublicAddress::read(&mut sender_vec)
.map_err(|e| EnumError::Error { msg: e.to_string() })?,
);

let mut arr: Vec<u8> = vec![];
note.write(&mut arr)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;

Ok(arr)
}

#[uniffi::export]
pub fn create_transaction(
spend_components: Vec<SpendComponents>,
outputs: Vec<Vec<u8>>,
spending_key: Vec<u8>,
) -> Result<Vec<u8>, EnumError> {
let mut transaction =
ironfish::ProposedTransaction::new(ironfish::transaction::TransactionVersion::V2);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will probably need to add a wallet server API to return proper value for this.

for spend_component in spend_components {
let note_data = Cursor::new(spend_component.note);
let note =
ironfish::Note::read(note_data).map_err(|e| EnumError::Error { msg: e.to_string() })?;

let root_hash_data = Cursor::new(spend_component.witness_root_hash);
let root_hash = MerkleNoteHash::read(root_hash_data)
.map_err(|e| EnumError::Error { msg: e.to_string() })?
.0;
let witness_auth_path: Vec<ironfish::witness::WitnessNode<Scalar>> = spend_component
.witness_auth_path
.into_iter()
.map(|witness_node| {
let hash_of_sibling_data = Cursor::new(witness_node.hash_of_sibling);
let hash_of_sibling: Scalar = MerkleNoteHash::read(hash_of_sibling_data)
.map_err(|e| EnumError::Error { msg: e.to_string() })?
.0;
match witness_node.side.as_str() {
"Left" => Ok(ironfish::witness::WitnessNode::Left(hash_of_sibling)),
"Right" => Ok(ironfish::witness::WitnessNode::Right(hash_of_sibling)),
_ => Err(EnumError::Error {
msg: "Invalid side".to_string(),
}),
}
})
.collect::<Result<Vec<ironfish::witness::WitnessNode<Scalar>>, EnumError>>()?;
let witness = ironfish::witness::Witness {
root_hash,
tree_size: spend_component.witness_tree_size as usize,
auth_path: witness_auth_path,
};
transaction
.add_spend(note, &witness)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
}

for output in outputs {
let output_data = Cursor::new(output);
let output = ironfish::Note::read(output_data)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
transaction
.add_output(output)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
}

let mut spending_key_data = Cursor::new(spending_key);
let spending_key = ironfish::SaplingKey::read(&mut spending_key_data)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
let posted = transaction
.post(&spending_key, None, 1)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
let mut buffer = Vec::new();
posted
.write(&mut buffer)
.map_err(|e| EnumError::Error { msg: e.to_string() })?;
Ok(buffer)
}
33 changes: 33 additions & 0 deletions packages/ironfish-native-module/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,36 @@ export function nullifier({
}): Promise<string> {
return IronfishNativeModule.nullifier(note, position, viewHexKey);
}

export function createNote({
owner,
value,
memo,
assetId,
sender,
}: {
owner: Uint8Array;
value: string;
memo: Uint8Array;
assetId: Uint8Array;
sender: Uint8Array;
}): Promise<Uint8Array> {
return IronfishNativeModule.createNote(owner, value, memo, assetId, sender);
}

export function createTransaction(
spendComponents: {
note: string;
witnessRootHash: string;
witnessTreeSize: string;
witnessAuthPath: { side: "Left" | "Right"; hashOfSibling: string }[];
}[],
outputs: string[],
spendingKey: Uint8Array,
): Promise<Uint8Array> {
return IronfishNativeModule.createTransaction(
{ components: spendComponents },
{ outputs },
spendingKey,
);
}
Loading
Loading