From ad9e2f074d76d5aa84a8c7a63a715b2537630020 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 30 Apr 2024 16:54:15 -0500 Subject: [PATCH] Implement transaction creation --- CHANGELOG.md | 21 ++ librustzcash-wrapper/Cargo.lock | 168 +++++++++++++ librustzcash-wrapper/Cargo.toml | 5 +- librustzcash-wrapper/src/lib.rs | 414 +++++++++++++++++++++++++++++--- src/C/Zcash.chs | 16 ++ src/ZcashHaskell/Transparent.hs | 65 ++--- src/ZcashHaskell/Types.hs | 49 ++++ zcash-haskell.cabal | 2 +- 8 files changed, 682 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a6082..6f9e53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1.0] + +### Added + +- Function to create a raw transaction +- New types for transaction creation: + - `Rseed` + - `TransparentTxSpend` + - `SaplingTxSpend` + - `OrchardTxSpend` + - `OutgoingNote` +- Rust crates: + - `secp256k1` + - `jubjub` + - `rand_core` + + +### Changed + +- `DecodedNote` type now includes a field for `rho` and one for `rseed` + ## [0.6.0.0] ### Added diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index afcddf1..53583a4 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -318,6 +318,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bumpalo" +version = "3.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" + [[package]] name = "byteorder" version = "1.4.3" @@ -703,6 +709,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "hdwallet" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" +dependencies = [ + "lazy_static", + "rand_core", + "ring", + "secp256k1", + "thiserror", +] + [[package]] name = "heck" version = "0.4.1" @@ -796,6 +815,15 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jubjub" version = "0.10.0" @@ -1313,6 +1341,30 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "rustix" version = "0.37.20" @@ -1336,10 +1388,13 @@ dependencies = [ "f4jumble", "haskell-ffi", "incrementalmerkletree", + "jubjub", "nonempty", "orchard", "proc-macro2", + "rand_core", "sapling-crypto", + "secp256k1", "zcash_address 0.2.0", "zcash_client_backend", "zcash_note_encryption", @@ -1385,6 +1440,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "secp256k1" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -1661,6 +1734,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "version_check" version = "0.9.4" @@ -1673,6 +1752,70 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.32", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "4.4.0" @@ -1684,6 +1827,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" @@ -1882,6 +2047,7 @@ dependencies = [ "ff", "fpe", "group", + "hdwallet", "hex", "incrementalmerkletree", "jubjub", @@ -1891,7 +2057,9 @@ dependencies = [ "rand", "rand_core", "redjubjub", + "ripemd", "sapling-crypto", + "secp256k1", "sha2 0.10.6", "subtle", "tracing", diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 5ae9b85..b8b2b37 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -13,13 +13,16 @@ borsh = "0.10" bech32 = "0.11" orchard = "0.7.1" zcash_note_encryption = "0.4.0" -zcash_primitives = "0.14.0" +zcash_primitives = { version = "0.14.0", features = ["transparent-inputs"]} zcash_client_backend = "0.11.1" sapling-crypto = "0.1.3" zip32 = "0.1.0" proc-macro2 = "1.0.66" nonempty = "0.7.0" incrementalmerkletree = "0.5.0" +secp256k1 = "0.26.0" +jubjub = "0.10.0" +rand_core = { version = "0.6.4", features = ["getrandom"]} [features] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 4c43d75..6d98974 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -12,6 +12,8 @@ use std::{ use nonempty::NonEmpty; +use rand_core::OsRng; + use f4jumble; use borsh::{BorshDeserialize, BorshSerialize}; @@ -23,6 +25,10 @@ use haskell_ffi::{ FromHaskell, HaskellSize, ToHaskell }; +use secp256k1::SecretKey; + +use jubjub::Fr; + use incrementalmerkletree::{ frontier::CommitmentTree, witness::IncrementalWitness @@ -39,8 +45,13 @@ use sapling_crypto::{ Node, MerklePath, PaymentAddress, + Anchor as SaplingAnchor, value::ValueCommitment as SaplingValueCommitment, - note::ExtractedNoteCommitment as SaplingNoteCommitment, + note::{ + ExtractedNoteCommitment as SaplingNoteCommitment, + Note as SaplingNote, + Rseed + }, keys::{ PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, ExpandedSpendingKey, @@ -57,6 +68,11 @@ use sapling_crypto::{ Authorized as SaplingAuthorized, Bundle as SaplingBundle }, + value::NoteValue as SaplingNoteValue, + circuit::{ + SpendParameters, + OutputParameters + }, zip32::{ sapling_find_address, DiversifierKey @@ -70,18 +86,34 @@ use zcash_primitives::{ read_incremental_witness, write_incremental_witness }, + legacy::{ + Script, + TransparentAddress + }, zip339::{Count, Mnemonic}, - transaction::components::{ - amount::Amount, - transparent::{ - Bundle as TransparentBundle, - TxIn, - TxOut, - OutPoint, - Authorized + transaction::{ + Transaction, + fees::zip317::FeeRule, + builder::{ + Builder, + Error, + BuildConfig + }, + components::{ + amount::{ + Amount, + NonNegativeAmount + }, + transparent::{ + Bundle as TransparentBundle, + TxIn, + TxOut, + OutPoint, + Authorized + } } }, - transaction::Transaction, + memo::MemoBytes, consensus::{ BranchId::Nu5, MainNetwork, @@ -107,6 +139,7 @@ use zcash_client_backend::keys::sapling::{ use zcash_primitives::zip32::DiversifierIndex; use orchard::{ + Address as OrchardAddress, Bundle as OrchardBundle, bundle::{ Authorized as OrchardAuthorized, @@ -114,11 +147,18 @@ use orchard::{ }, Action, keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, - note::{Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, + note::{RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note_encryption::OrchardDomain, primitives::redpallas::{VerificationKey, SpendAuth, Signature}, - tree::MerkleHashOrchard, - value::ValueCommitment + tree::{ + MerklePath as OrchardMerklePath, + MerkleHashOrchard, + Anchor as OrchardAnchor + }, + value::{ + ValueCommitment, + NoteValue + } }; use bech32::{ @@ -255,7 +295,9 @@ pub struct Hnote { note: u64, recipient: Vec, memo: Vec, - nullifier: Vec + nullifier: Vec, + rho: Vec, + rseed: Hrseed } impl ToHaskell for Hnote { @@ -265,6 +307,27 @@ impl ToHaskell for Hnote { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Hrseed { + kind: u8, + bytes: Vec +} + + +impl FromHaskell for Hrseed { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hrseed::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for Hrseed { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + #[derive(BorshSerialize, BorshDeserialize)] pub struct Hua { net: u8, @@ -363,6 +426,13 @@ pub struct HTxOut { script: Vec } +impl FromHaskell for HTxOut { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HTxOut::deserialize(buf)?; + Ok(x) + } +} + impl ToHaskell for HTxOut { fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { self.serialize(writer)?; @@ -374,6 +444,9 @@ impl HTxOut { pub fn pack(t: &TxOut) -> HTxOut { return HTxOut { amt: i64::from_le_bytes(t.value.to_i64_le_bytes()) , script: t.script_pubkey.0.clone() } } + pub fn unpack(&self) -> TxOut { + TxOut { value: NonNegativeAmount::from_nonnegative_i64(self.amt).unwrap(), script_pubkey: Script(self.script.clone())} + } } #[derive(BorshSerialize, BorshDeserialize)] @@ -382,6 +455,13 @@ pub struct Houtpoint { index: u32 } +impl FromHaskell for Houtpoint { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Houtpoint::deserialize(buf)?; + Ok(x) + } +} + impl ToHaskell for Houtpoint { fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { self.serialize(writer)?; @@ -393,6 +473,31 @@ impl Houtpoint { pub fn pack(o: &OutPoint) -> Houtpoint { return Houtpoint {hash: o.hash().to_vec() , index: o.n() } } + + pub fn unpack(&self) -> OutPoint { + OutPoint::new(to_array(self.hash.clone()), self.index) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HtransparentInput { + sk: Vec, + utxo: Houtpoint, + coin: HTxOut +} + +impl FromHaskell for HtransparentInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HtransparentInput::deserialize(buf)?; + Ok(x) + } +} + +impl ToHaskell for HtransparentInput { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } } #[derive(BorshSerialize, BorshDeserialize)] @@ -447,6 +552,52 @@ impl Hspend { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HsaplingInput { + sk: Vec, + note: Hnote, + iw: Vec +} + +impl FromHaskell for HsaplingInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HsaplingInput::deserialize(buf)?; + Ok(x) + } +} + + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct HorchardInput { + sk: Vec, + note: Hnote, + iw: Vec +} + +impl FromHaskell for HorchardInput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = HorchardInput::deserialize(buf)?; + Ok(x) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Houtput { + kind: u8, + ovk: Vec, + to: Vec, + amt: u64, + memo: Vec, + chg: bool +} + +impl FromHaskell for Houtput { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Houtput::deserialize(buf)?; + Ok(x) + } +} + #[derive(BorshSerialize, BorshDeserialize)] pub struct HOBundle { empty: bool, @@ -771,23 +922,31 @@ pub extern "C" fn rust_wrapper_sapling_esk_decrypt( match result { Some((n, r, m)) => { let nullifier = n.nf(&nk, pos); - let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: nullifier.to_vec() }; + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed {kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed {kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: nullifier.to_vec(), rho: vec![0], rseed }; marshall_to_haskell_var(&hn, out, out_len, RW); }, None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, Err(_e1) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]} }; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, Err(_e) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed{ kind: 0, bytes: vec![0]} }; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -816,23 +975,31 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: vec![0]}; + let rseed = match n.rseed() { + Rseed::BeforeZip212(x) => { + Hrseed { kind: 1, bytes: x.to_bytes().to_vec()} + }, + Rseed::AfterZip212(y) => { + Hrseed { kind: 2, bytes: y.to_vec()} + } + }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec(), nullifier: vec![0], rho: vec![0], rseed}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, Err(_e1) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] , nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] , nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } } Err(_e) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -866,17 +1033,19 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: vec![0]}; + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: vec![0], rho, rseed}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -914,11 +1083,13 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt_sk( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: n.nullifier(&fvk).to_bytes().to_vec()}; + let rho = n.rho().to_bytes().to_vec(); + let rseed = Hrseed {kind: 3, bytes: n.rseed().as_bytes().to_vec()}; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec(), nullifier: n.nullifier(&fvk).to_bytes().to_vec(), rho, rseed}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0]}; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0], nullifier: vec![0], rho: vec![0], rseed: Hrseed {kind: 0, bytes: vec![0]}}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -1454,8 +1625,193 @@ pub extern "C" fn rust_wrapper_decode_sapling_address( #[no_mangle] pub extern "C" fn rust_wrapper_create_transaction( - sapling: *const u8, - sapling_len: usize, + sap_wit: *const u8, + sap_wit_len: usize, + orch_wit: *const u8, + orch_wit_len: usize, + t_input: *const u8, + t_input_len: usize, + s_input: *const u8, + s_input_len: usize, + o_input: *const u8, + o_input_len: usize, + out_list: *const u8, + out_list_len: usize, + sapspend: *const u8, + sapspend_len: usize, + sapoutput: *const u8, + sapoutput_len: usize, + net: bool, + bl_height: u32, out: *mut u8, out_len: &mut usize){ + let sap_wit_in: Vec = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); + let sap_wit_reader = Cursor::new(sap_wit_in); + let sap_iw: Option> = read_incremental_witness(sap_wit_reader).ok(); + let sap_anchor = match sap_iw { + Some(s_iw) => { + Some(SaplingAnchor::from(s_iw.root())) + }, + None => { + None + } + }; + let orch_wit_in: Vec = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); + let orch_wit_reader = Cursor::new(orch_wit_in); + let orch_iw: Option> = read_incremental_witness(orch_wit_reader).ok(); + let orch_anchor = match orch_iw { + Some(o_iw) => { + Some(OrchardAnchor::from(o_iw.root())) + }, + None => { + None + } + }; + let build_config = BuildConfig::Standard {sapling_anchor: sap_anchor, orchard_anchor: orch_anchor}; + let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config); + let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config); + let trans_input: Vec = marshall_from_haskell_var(t_input, t_input_len, RW); + for t_in in trans_input { + if t_in.sk.len() > 1 { + let k = SecretKey::from_slice(&t_in.sk).unwrap(); + if net { + match main_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { + Ok(()) => { + continue; + }, + Err(_e) => { println!("Error reading transparent input"); } + } + } else { + match test_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) { + Ok(()) => { + continue; + }, + Err(_e) => { println!("Error reading transparent input"); } + } + } + } + } + let sap_input: Vec = marshall_from_haskell_var(s_input, s_input_len, RW); + for s_in in sap_input { + if s_in.sk.len() > 1 { + let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); + match sp_key { + Ok(sk) => { + let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap(); + let rseed = if s_in.note.rseed.kind == 1 { + Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap()) + } else { + Rseed::AfterZip212(to_array(s_in.note.rseed.bytes)) + }; + let note = SaplingNote::from_parts(pay_addr, SaplingNoteValue::from_raw(s_in.note.note), rseed); + let wit_reader = Cursor::new(s_in.iw); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let merkle_path = iw.path().unwrap(); + if net { + let _mb = main_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + } else { + let _tb = test_builder.add_sapling_spend::(&sk, note, merkle_path).unwrap(); + } + }, + Err(_e) => { + continue; + } + } + } + } + let orch_input: Vec = marshall_from_haskell_var(o_input, o_input_len, RW); + for o_in in orch_input { + if o_in.sk.len() > 1 { + let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); + let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap(); + let rho = Nullifier::from_bytes(&to_array(o_in.note.rho)).unwrap(); + let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap(); + let val = NoteValue::from_raw(o_in.note.note); + let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); + let wit_reader = Cursor::new(o_in.iw); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let merkle_path = OrchardMerklePath::from(iw.path().unwrap()); + if net { + let _mb = main_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + } else { + let _tb = test_builder.add_orchard_spend::(&sp_key, note, merkle_path).unwrap(); + } + } + } + let outputs: Vec = marshall_from_haskell_var(out_list, out_list_len, RW); + for output in outputs { + match output.kind { + 1 => { + let recipient = TransparentAddress::PublicKeyHash(to_array(output.to)); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + if net { + let _mb = main_builder.add_transparent_output(&recipient, val); + } else { + let _tb = test_builder.add_transparent_output(&recipient, val); + } + }, + 2 => { + let recipient = TransparentAddress::ScriptHash(to_array(output.to)); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + if net { + let _mb = main_builder.add_transparent_output(&recipient, val); + } else { + let _tb = test_builder.add_transparent_output(&recipient, val); + } + }, + 3 => { + let ovk = Some(ExpandedSpendingKey::from_spending_key(&output.ovk).ovk); + let recipient = PaymentAddress::from_bytes(&to_array(output.to)).unwrap(); + let val = NonNegativeAmount::from_u64(output.amt).unwrap(); + let memo = MemoBytes::from_bytes(&output.memo).unwrap(); + if net { + let _mb = main_builder.add_sapling_output::(ovk, recipient, val, memo); + } else { + let _tb = test_builder.add_sapling_output::(ovk, recipient, val, memo); + } + }, + 4 => { + let sk = SpendingKey::from_bytes(output.ovk[0..32].try_into().unwrap()).unwrap(); + let ovk = if output.chg { + Some(FullViewingKey::from(&sk).to_ovk(Scope::Internal)) + }else { + Some(FullViewingKey::from(&sk).to_ovk(Scope::External)) + }; + let recipient = OrchardAddress::from_raw_address_bytes(&to_array(output.to)).unwrap(); + let val = output.amt; + let memo = MemoBytes::from_bytes(&output.memo).unwrap(); + if net { + let _mb = main_builder.add_orchard_output::(ovk, recipient, val, memo); + } else { + let _tb = test_builder.add_orchard_output::(ovk, recipient, val, memo); + } + }, + _ => { + continue; + } + } + } + let spend_params_in: Vec = marshall_from_haskell_var(sapspend, sapspend_len, RW); + let spend_params_reader = Cursor::new(spend_params_in); + let spend_prover = SpendParameters::read(spend_params_reader, true).unwrap(); + let output_params_in: Vec = marshall_from_haskell_var(sapoutput, sapoutput_len, RW); + let output_params_reader = Cursor::new(output_params_in); + let output_prover = OutputParameters::read(output_params_reader, true).unwrap(); + let result = if net { + main_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) + } else { + test_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard()) + }; + match result { + Ok(r) => { + let mut out_bytes: Vec = Vec::new(); + let _t = r.transaction().write_v5(&mut out_bytes); + let h = Hhex {bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + let x = Hhex {bytes: vec![0]}; + marshall_to_haskell_var(&x, out, out_len, RW); + } + } } diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 6e0dff8..8f27fa2 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -275,3 +275,19 @@ import ZcashHaskell.Types } -> `()' #} + +{# fun unsafe rust_wrapper_create_transaction as rustWrapperCreateTx + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , toBorshVar* `[TransparentTxSpend]'& + , toBorshVar* `[SaplingTxSpend]'& + , toBorshVar* `[OrchardTxSpend]'& + , toBorshVar* `[OutgoingNote]'& + , toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , `Bool' + , `Word64' + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 4bdc46d..afe09e3 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -21,9 +21,9 @@ import Crypto.Secp256k1 import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58, encodeBase58) -import Data.Char (chr) import qualified Data.ByteString.Char8 as BC -import Data.HexString +import Data.Char (chr) +import Data.HexString import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word @@ -31,6 +31,7 @@ import Haskoin.Address (Address(..)) import qualified Haskoin.Crypto.Hash as H import Haskoin.Crypto.Keys.Extended import ZcashHaskell.Types + -- ( AccountId -- , CoinType(..) -- , Scope(..) @@ -44,7 +45,7 @@ import ZcashHaskell.Types -- , getTransparentPrefix -- , getValue -- ) -import ZcashHaskell.Utils( encodeBech32m, decodeBech32 ) +import ZcashHaskell.Utils (decodeBech32, encodeBech32m) -- | Required for `TransparentReceiver` encoding and decoding sha256 :: BS.ByteString -> BS.ByteString @@ -127,60 +128,70 @@ decodeTransparentAddress taddress = do 189 -> Just $ TransparentAddress MainNet $ - TransparentReceiver P2SH (fromRawBytes transparentReceiver) + TransparentReceiver + P2SH + (fromRawBytes transparentReceiver) 186 -> Just $ TransparentAddress TestNet $ - TransparentReceiver P2SH (fromRawBytes transparentReceiver) + TransparentReceiver + P2SH + (fromRawBytes transparentReceiver) 184 -> Just $ TransparentAddress MainNet $ - TransparentReceiver P2PKH (fromRawBytes transparentReceiver) + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) _ -> Nothing 29 -> if sb == 37 then Just $ TransparentAddress TestNet $ - TransparentReceiver P2PKH (fromRawBytes transparentReceiver) + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) else Nothing _ -> Nothing -- | Encode an Exchange Addresss into HRF from TransparentReceiver -encodeExchangeAddress:: ZcashNet -> TransparentReceiver -> Maybe T.Text -encodeExchangeAddress net tr = do +encodeExchangeAddress :: ZcashNet -> TransparentReceiver -> Maybe T.Text +encodeExchangeAddress net tr = do case (tr_type tr) of - P2PKH -> do + P2PKH -> do case net of MainNet -> do let vhash = encodeBech32m (BC.pack "tex") (toBytes (tr_bytes tr)) Just vhash - TestNet -> do + TestNet -> do let vhash = encodeBech32m (BC.pack "textest") (toBytes (tr_bytes tr)) Just vhash - _ -> Nothing + _ -> Nothing -- | Decode an Exchange Address into a ExchangeAddress -decodeExchangeAddress:: T.Text-> Maybe ExchangeAddress -decodeExchangeAddress ex = do - if (T.length ex ) > 1 - then do - let rawd = decodeBech32 (E.encodeUtf8 ex) - let tMain = BS.unpack (BC.pack "tex") - let tTest = BS.unpack (BC.pack "textest") - let tFail = BS.unpack (BC.pack "fail") - let hr = BS.unpack (hrp rawd) +decodeExchangeAddress :: T.Text -> Maybe ExchangeAddress +decodeExchangeAddress ex = do + if (T.length ex) > 1 + then do + let rawd = decodeBech32 (E.encodeUtf8 ex) + let tMain = BS.unpack (BC.pack "tex") + let tTest = BS.unpack (BC.pack "textest") + let tFail = BS.unpack (BC.pack "fail") + let hr = BS.unpack (hrp rawd) if hr /= tFail then do let transparentReceiver = bytes rawd - if hr == tMain + if hr == tMain then Just $ ExchangeAddress MainNet $ TransparentReceiver P2PKH (fromRawBytes transparentReceiver) - else do - if hr == tTest + else do + if hr == tTest then Just $ ExchangeAddress TestNet $ - TransparentReceiver P2PKH (fromRawBytes transparentReceiver) - else Nothing + TransparentReceiver + P2PKH + (fromRawBytes transparentReceiver) + else Nothing else Nothing - else Nothing \ No newline at end of file + else Nothing diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 91ccae3..91f01b5 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -638,11 +638,60 @@ data DecodedNote = DecodedNote , a_recipient :: !BS.ByteString -- ^ The recipient Orchard receiver. , a_memo :: !BS.ByteString -- ^ The decoded shielded memo field. , a_nullifier :: !HexString -- ^ The calculated nullifier + , a_rho :: !BS.ByteString + , a_rseed :: !Rseed } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote +data Rseed = Rseed + { rs_kind :: !Word8 + , rs_bytes :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct Rseed + +data TransparentTxSpend = TransparentTxSpend + { ts_sk :: !BS.ByteString + , ts_utxo :: !RawOutPoint + , ts_coin :: !RawTxOut + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct TransparentTxSpend + +data SaplingTxSpend = SaplingTxSpend + { ss_sk :: !BS.ByteString + , ss_note :: !DecodedNote + , ss_iw :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingTxSpend + +data OrchardTxSpend = OrchardTxSpend + { ss_sk :: !BS.ByteString + , ss_note :: !DecodedNote + , ss_iw :: !BS.ByteString + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardTxSpend + +data OutgoingNote = OutGoingNote + { on_kind :: !Word8 + , on_key :: !BS.ByteString + , on_recipient :: !BS.ByteString + , on_amt :: !Word64 + , on_memo :: !BS.ByteString + , on_chg :: !Bool + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OutgoingNote + -- * Classes -- | Class to represent types with a bytestring representation class ToBytes a where diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index e739a59..4730957 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: zcash-haskell -version: 0.6.0.0 +version: 0.6.1.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain