From 80db9293f5a384a220aebe3f6da4733e0b0ca337 Mon Sep 17 00:00:00 2001 From: v-almonacid Date: Sat, 4 Jul 2020 01:23:13 +0200 Subject: [PATCH] add tx_input, tx_output, tx_hash for Android --- .../HaskellShelleyModule.java | 70 ++++++++++++++++ .../io/emurgo/rnhaskellshelley/Native.java | 14 ++++ example/App.js | 12 +++ index.d.ts | 60 ++++++++++++++ index.js | 83 +++++++++++++++++++ rust/src/android/hash_type.rs | 40 +++++++++ rust/src/android/mod.rs | 7 +- rust/src/android/transaction_hash.rs | 35 ++++++++ rust/src/android/transaction_input.rs | 55 ++++++++++++ rust/src/android/transaction_output.rs | 55 ++++++++++++ rust/src/ptr_impl.rs | 4 + 11 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 rust/src/android/hash_type.rs create mode 100644 rust/src/android/transaction_hash.rs create mode 100644 rust/src/android/transaction_input.rs create mode 100644 rust/src/android/transaction_output.rs diff --git a/android/src/main/java/io/emurgo/rnhaskellshelley/HaskellShelleyModule.java b/android/src/main/java/io/emurgo/rnhaskellshelley/HaskellShelleyModule.java index 533a74e..5807c6f 100644 --- a/android/src/main/java/io/emurgo/rnhaskellshelley/HaskellShelleyModule.java +++ b/android/src/main/java/io/emurgo/rnhaskellshelley/HaskellShelleyModule.java @@ -59,6 +59,24 @@ public final void addrKeyHashToBytes(String addrKeyHash, Promise promise) { .pour(promise); } + // TransactionHash + + @ReactMethod + public final void transactionHashFromBytes(String bytes, Promise promise) { + Native.I + .transactionHashFromBytes(Base64.decode(bytes, Base64.DEFAULT)) + .map(RPtr::toJs) + .pour(promise); + } + + @ReactMethod + public final void transactionHashToBytes(String addrKeyHash, Promise promise) { + Native.I + .transactionHashToBytes(new RPtr(addrKeyHash)) + .map(bytes -> Base64.encodeToString(bytes, Base64.DEFAULT)) + .pour(promise); + } + // StakeCredential @ReactMethod @@ -110,4 +128,56 @@ public final void baseAddressStakeCred(String baseAddress, Promise promise) { .pour(promise); } + // TransactionInput + + @ReactMethod + public final void transactionInputFromBytes(String bytes, Promise promise) { + Native.I + .transactionInputFromBytes(Base64.decode(bytes, Base64.DEFAULT)) + .map(RPtr::toJs) + .pour(promise); + } + + @ReactMethod + public final void transactionInputToBytes(String transactionInput, Promise promise) { + Native.I + .transactionInputToBytes(new RPtr(transactionInput)) + .map(bytes -> Base64.encodeToString(bytes, Base64.DEFAULT)) + .pour(promise); + } + + @ReactMethod + public final void transactionInputNew(String transactionId, Double index, Promise promise) { + Native.I + .transactionInputNew(new RPtr(transactionId), index.longValue()) + .map(RPtr::toJs) + .pour(promise); + } + + // TransactionOutput + + @ReactMethod + public final void transactionOutputFromBytes(String bytes, Promise promise) { + Native.I + .transactionOutputFromBytes(Base64.decode(bytes, Base64.DEFAULT)) + .map(RPtr::toJs) + .pour(promise); + } + + @ReactMethod + public final void transactionOutputToBytes(String transactionOutput, Promise promise) { + Native.I + .transactionOutputToBytes(new RPtr(transactionOutput)) + .map(bytes -> Base64.encodeToString(bytes, Base64.DEFAULT)) + .pour(promise); + } + + @ReactMethod + public final void transactionOutputNew(String address, Double amount, Promise promise) { + Native.I + .transactionOutputNew(new RPtr(address), amount.longValue()) + .map(RPtr::toJs) + .pour(promise); + } + } diff --git a/android/src/main/java/io/emurgo/rnhaskellshelley/Native.java b/android/src/main/java/io/emurgo/rnhaskellshelley/Native.java index 9646ef0..89b480f 100644 --- a/android/src/main/java/io/emurgo/rnhaskellshelley/Native.java +++ b/android/src/main/java/io/emurgo/rnhaskellshelley/Native.java @@ -23,6 +23,10 @@ private Native() { } public final native Result addrKeyHashToBytes(RPtr addrKeyHash); public final native Result addrKeyHashFromBytes(byte[] bytes); + // TransactionHash + public final native Result transactionHashToBytes(RPtr transactionHash); + public final native Result transactionHashFromBytes(byte[] bytes); + // StakeCredential public final native Result stakeCredentialFromKeyHash(RPtr addrKeyHash); public final native Result stakeCredentialToKeyHash(RPtr stakeCredential); @@ -33,5 +37,15 @@ private Native() { } public final native Result baseAddressPaymentCred(RPtr baseAddress); public final native Result baseAddressStakeCred(RPtr baseAddress); + // TransactionInput + public final native Result transactionInputToBytes(RPtr transactionInput); + public final native Result transactionInputFromBytes(byte[] bytes); + public final native Result transactionInputNew(RPtr transactionId, long index); + + // TransactionOutput + public final native Result transactionOutputToBytes(RPtr transactionOutput); + public final native Result transactionOutputFromBytes(byte[] bytes); + public final native Result transactionOutputNew(RPtr address, long amount); + public final native void ptrFree(RPtr ptr); } diff --git a/example/App.js b/example/App.js index 00dcb7d..2f5213f 100644 --- a/example/App.js +++ b/example/App.js @@ -14,6 +14,7 @@ import { AddrKeyHash, BaseAddress, StakeCredential, + TransactionHash, } from 'react-native-haskell-shelley' const assert = (value: any, message: string, ...args: any) => { @@ -33,6 +34,7 @@ export default class App extends Component<{}> { const addr = '0000b03c3aa052f51c086c54bd4059ead2d2e426ac89fa4b3ce41cbfd8800b51' const addrBytes = Buffer.from(addr, 'hex') + try { // ------------------ AddrKeyHash ----------------------- const addrKeyHash = await AddrKeyHash.from_bytes(addrBytes) @@ -44,6 +46,15 @@ export default class App extends Component<{}> { 'AddrKeyHash.to_bytes should match original input address', ) + // ------------------ TransactionHash ----------------------- + const txHash = await TransactionHash.from_bytes(addrBytes) + const txHashToBytes = await txHash.to_bytes() + console.log(Buffer.from(txHashToBytes).toString('hex')) + assert( + Buffer.from(txHashToBytes).toString('hex') === addr, + 'TransactionHash.to_bytes should match original input address', + ) + // ---------------- StakeCredential --------------------- const stakeCred = await StakeCredential.from_keyhash(addrKeyHash) const addrKeyHashOrig = await stakeCred.to_keyhash() @@ -80,6 +91,7 @@ export default class App extends Component<{}> { }) console.log('addrKeyHash', addrKeyHash) + console.log('txHash', addrKeyHash) console.log('pymntAddrKeyHash', pymntAddrKeyHash) console.log('paymentCred', paymentCred) console.log('stakeCred', stakeCred) diff --git a/index.d.ts b/index.d.ts index ecce6f3..aa5dc64 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,9 @@ export type Optional = T | undefined; +export type TransactionIndex = number; + +export type Coin = number; + export class Ptr { /** * Frees the pointer @@ -37,6 +41,20 @@ export class AddrKeyHash extends Ptr { } +export class TransactionHash extends Ptr { + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static from_bytes(bytes: Uint8Array): Promise; + + /** + * @returns {Promise} + */ + to_bytes(): Promise; + +} + export class StakeCredential extends Ptr { /** @@ -76,3 +94,45 @@ export class BaseAddress extends Ptr { */ stake_cred(): Promise } + +export class TransactionInput extends Ptr { + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static from_bytes(bytes: Uint8Array): Promise; + + /** + * @returns {Promise} + */ + to_bytes(): Promise; + + /** + * @param {TransactionHash} transactionId + * @param {TransactionIndex} index + * @returns {Promise} + */ + static new(transactionId: TransactionHash, index: TransactionIndex): Promise; + +} + +export class TransactionOutput extends Ptr { + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static from_bytes(bytes: Uint8Array): Promise; + + /** + * @returns {Promise} + */ + to_bytes(): Promise; + + /** + * @param {Address} address + * @param {Coin} amount + * @returns {Promise} + */ + static new(address: Address, amount: Coin): Promise; + +} diff --git a/index.js b/index.js index f5d9b34..e9fa396 100644 --- a/index.js +++ b/index.js @@ -91,6 +91,26 @@ export class AddrKeyHash extends Ptr { } } +export class TransactionHash extends Ptr { + + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static async from_bytes(bytes) { + const ret = await HaskellShelley.transactionHashFromBytes(b64FromUint8Array(bytes)); + return Ptr._wrap(ret, TransactionHash); + } + + /** + * @returns {Promise} + */ + async to_bytes() { + const b64 = await HaskellShelley.transactionHashToBytes(this.ptr); + return Uint8ArrayFromB64(b64); + } +} + export class StakeCredential extends Ptr { /** @@ -150,3 +170,66 @@ export class BaseAddress extends Ptr { return Ptr._wrap(ret, StakeCredential); } } + +export class TransactionInput extends Ptr { + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static async from_bytes(bytes) { + const ret = await HaskellShelley.transactionInputFromBytes(b64FromUint8Array(bytes)); + return Ptr._wrap(ret, TransactionInput); + } + + /** + * @returns {Promise} + */ + async to_bytes() { + const b64 = await HaskellShelley.transactionInputToBytes(this.ptr); + return Uint8ArrayFromB64(b64); + } + + /** + * @param {TransactionHash} transactionId + * @param {TransactionIndex} index + * @returns {Promise} + */ + static async new(transactionId, index) { + const transactionIdPtr = Ptr._assertClass(transactionId, TransactionHash); + const indexPtr = Ptr._assertClass(index, TransactionIndex); + const ret = await HaskellShelley.transactionInputNew(transactionIdPtr, indexPtr); + return Ptr._wrap(ret, TransactionInput); + } + +} + +export class TransactionOutput extends Ptr { + /** + * @param {Uint8Array} bytes + * @returns {Promise} + */ + static async from_bytes(bytes: Uint8Array) { + const ret = await HaskellShelley.transactionOutputFromBytes(b64FromUint8Array(bytes)); + return Ptr._wrap(ret, TransactionOutput); + } + + /** + * @returns {Promise} + */ + async to_bytes() { + const b64 = await HaskellShelley.transactionOutputToBytes(this.ptr); + return Uint8ArrayFromB64(b64); + } + + /** + * @param {Address} address + * @param {Coin} amount + * @returns {Promise} + */ + static async new(address: Address, amount: Coin) { + const addrPtr = Ptr._assertClass(address, Address); + const ret = await HaskellShelley.transactionOutputNew(addrPtr, amount); + return Ptr._wrap(ret, TransactionOutput); + } + +} diff --git a/rust/src/android/hash_type.rs b/rust/src/android/hash_type.rs new file mode 100644 index 0000000..1b3e1c2 --- /dev/null +++ b/rust/src/android/hash_type.rs @@ -0,0 +1,40 @@ +use super::ptr_j::*; +use super::result::ToJniResult; +use crate::panic::{handle_exception_result, ToResult}; +use crate::ptr::RPtrRepresentable; +use jni::objects::{JObject}; +use jni::sys::{jbyteArray, jobject}; +use jni::JNIEnv; +use wasm_bindgen::JsValue; + +pub trait HashType { + fn to_bytes(&self) -> Vec; + fn from_bytes(bytes: Vec) -> Result where Self: Sized; +} + +pub unsafe fn hash_to_bytes( + env: JNIEnv, hash: JRPtr +) -> jobject { + handle_exception_result(|| { + let hash = hash.rptr(&env)?; + hash + .typed_ref::() + .map(|hash| hash.to_bytes()) + .and_then(|bytes| env.byte_array_from_slice(&bytes).into_result()) + .map(|arr| JObject::from(arr)) + }) + .jresult(&env) +} + +pub unsafe fn hash_from_bytes( + env: JNIEnv, bytes: jbyteArray, +) -> jobject { + handle_exception_result(|| { + env + .convert_byte_array(bytes) + .into_result() + .and_then(|bytes| T::from_bytes(bytes).into_result()) + .and_then(|hash| hash.rptr().jptr(&env)) + }) + .jresult(&env) +} diff --git a/rust/src/android/mod.rs b/rust/src/android/mod.rs index bed1981..5ffde52 100644 --- a/rust/src/android/mod.rs +++ b/rust/src/android/mod.rs @@ -1,20 +1,21 @@ mod address; // TODO: remove? mod primitive; mod ptr_j; +mod hash_type; mod result; mod string; mod addr_key_hash; +mod transaction_hash; mod stake_credential; mod base_address; +mod transaction_input; +mod transaction_output; // declare other modules here // mod transaction; - pub use address::*; pub use addr_key_hash::*; pub use stake_credential::*; -// and here: -// pub use transaction::*; #[allow(non_snake_case)] #[no_mangle] diff --git a/rust/src/android/transaction_hash.rs b/rust/src/android/transaction_hash.rs new file mode 100644 index 0000000..8c9245e --- /dev/null +++ b/rust/src/android/transaction_hash.rs @@ -0,0 +1,35 @@ +use super::ptr_j::*; +use super::hash_type::*; +use jni::objects::{JObject}; +use jni::sys::{jbyteArray, jobject}; +use jni::JNIEnv; +use cddl_lib::crypto::{TransactionHash}; +use wasm_bindgen::JsValue; + + +impl HashType for TransactionHash { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn from_bytes(bytes: Vec) -> Result { + TransactionHash::from_bytes(bytes) + } + +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_transactionHashToBytes( + env: JNIEnv, _: JObject, transaction_hash: JRPtr +) -> jobject { + hash_to_bytes::(env, transaction_hash) +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_transactionHashFromBytes( + env: JNIEnv, _: JObject, bytes: jbyteArray +) -> jobject { + hash_from_bytes::(env, bytes) +} diff --git a/rust/src/android/transaction_input.rs b/rust/src/android/transaction_input.rs new file mode 100644 index 0000000..c27b6c4 --- /dev/null +++ b/rust/src/android/transaction_input.rs @@ -0,0 +1,55 @@ +use super::primitive::ToPrimitiveObject; +use super::ptr_j::*; +use super::result::ToJniResult; +use crate::panic::{handle_exception_result, ToResult}; +use crate::ptr::RPtrRepresentable; +use jni::objects::JObject; +use jni::sys::{jbyteArray, jobject, jint}; +use jni::JNIEnv; +use cddl_lib::address::{StakeCredential}; +use cddl_lib::crypto::{TransactionHash}; +use cddl_lib::TransactionInput; + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionInputToBytes( + env: JNIEnv, _: JObject, ptr: JRPtr +) -> jobject { + handle_exception_result(|| { + let tx_input = ptr.rptr(&env)?; + tx_input + .typed_ref::() + .map(|tx_input| tx_input.to_bytes()) + .and_then(|bytes| env.byte_array_from_slice(&bytes).into_result()) + .map(|arr| JObject::from(arr)) + }) + .jresult(&env) +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionInputFromBytes( + env: JNIEnv, _: JObject, bytes: jbyteArray +) -> jobject { + handle_exception_result(|| { + env + .convert_byte_array(bytes) + .into_result() + .and_then(|bytes| TransactionInput::from_bytes(bytes).into_result()) + .and_then(|tx_input| tx_input.rptr().jptr(&env)) + }) + .jresult(&env) +} + +// TODO: consider using jlong instead of jint +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionInputNew( + env: JNIEnv, _: JObject, transaction_hash: JRPtr, transaction_index: jint +) -> jobject { + let transaction_hash = transaction_hash.owned::(&env); + handle_exception_result(|| { + TransactionInput::new(transaction_hash?, transaction_index as u32).rptr().jptr(&env) + }) + .jresult(&env) +} diff --git a/rust/src/android/transaction_output.rs b/rust/src/android/transaction_output.rs new file mode 100644 index 0000000..bc51b48 --- /dev/null +++ b/rust/src/android/transaction_output.rs @@ -0,0 +1,55 @@ +use super::primitive::ToPrimitiveObject; +use super::ptr_j::*; +use super::result::ToJniResult; +use crate::panic::{handle_exception_result, ToResult}; +use crate::ptr::RPtrRepresentable; +use jni::objects::JObject; +use jni::sys::{jbyteArray, jobject, jlong}; +use jni::JNIEnv; +use cddl_lib::address::{Address, StakeCredential}; +use cddl_lib::crypto::{TransactionHash}; +use cddl_lib::TransactionOutput; + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionOutputToBytes( + env: JNIEnv, _: JObject, ptr: JRPtr +) -> jobject { + handle_exception_result(|| { + let tx_output = ptr.rptr(&env)?; + tx_output + .typed_ref::() + .map(|tx_output| tx_output.to_bytes()) + .and_then(|bytes| env.byte_array_from_slice(&bytes).into_result()) + .map(|arr| JObject::from(arr)) + }) + .jresult(&env) +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionOutputFromBytes( + env: JNIEnv, _: JObject, bytes: jbyteArray +) -> jobject { + handle_exception_result(|| { + env + .convert_byte_array(bytes) + .into_result() + .and_then(|bytes| TransactionOutput::from_bytes(bytes).into_result()) + .and_then(|tx_output| tx_output.rptr().jptr(&env)) + }) + .jresult(&env) +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_TransactionOutputNew( + env: JNIEnv, _: JObject, address: JRPtr, amount: jlong +) -> jobject { + let address = address.owned::
(&env); + let coin_u64 = u64::from_jlong(amount); + handle_exception_result(|| { + TransactionOutput::new(address?, coin_u64).rptr().jptr(&env) + }) + .jresult(&env) +} diff --git a/rust/src/ptr_impl.rs b/rust/src/ptr_impl.rs index dc7bcd6..58d8db0 100644 --- a/rust/src/ptr_impl.rs +++ b/rust/src/ptr_impl.rs @@ -1,8 +1,12 @@ use crate::ptr::RPtrRepresentable; use cddl_lib::address::*; use cddl_lib::crypto::*; +use cddl_lib::*; impl RPtrRepresentable for Address {} impl RPtrRepresentable for AddrKeyHash {} +impl RPtrRepresentable for TransactionHash {} impl RPtrRepresentable for BaseAddress {} impl RPtrRepresentable for StakeCredential {} +impl RPtrRepresentable for TransactionInput {} +impl RPtrRepresentable for TransactionOutput {}