Implement Sapling tx decoding

This commit is contained in:
Rene Vergara 2023-08-22 15:05:40 -05:00
parent 4d2540dce1
commit 1d8e3729a8
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
6 changed files with 98 additions and 40 deletions

View file

@ -19,13 +19,17 @@ use haskell_ffi::{
use zcash_primitives::{
zip32::Scope as SaplingScope,
transaction::components::sapling::{
read_zkproof,
GrothProofBytes,
OutputDescription,
CompactOutputDescription
},
sapling::{
value::ValueCommitment as SaplingValueCommitment,
keys::FullViewingKey as SaplingViewingKey,
keys::{
FullViewingKey as SaplingViewingKey,
PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey
},
note_encryption::SaplingDomain,
PaymentAddress,
note::ExtractedNoteCommitment as SaplingExtractedNoteCommitment
@ -311,35 +315,49 @@ pub extern "C" fn rust_wrapper_ufvk_decode(
}
}
//#[no_mangle]
//pub extern "C" fn rust_wrapper_sapling_note_decrypt(
//key: *const u8,
//key_len: usize,
//note: *const u8,
//note_len: usize,
//out: *mut u8,
//out_len: &mut usize
//){
//let evk: Vec<u8> = marshall_from_haskell_var(key, key_len, RW);
//let note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW);
//let svk = ExtendedFullViewingKey::read(&*evk);
//match svk {
//Ok(k) => {
//let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000));
//let action: CompactOutputDescription = CompactOutputDescription {
//ephemeral_key: EphemeralKeyBytes(to_array(note_input.eph_key)),
//cmu: SaplingExtractedNoteCommitment::from_bytes(&to_array(note_input.cmu)).unwrap(),
//enc_ciphertext: to_array(note_input.enc_txt)
//};
//let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External);
//let result = zcash_note_encryption::try_note_decryption(&domain, &ivk, &action);
//}
//Err(_e) => {
//let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
//marshall_to_haskell_var(&hn0, out, out_len, RW);
//}
//}
//}
#[no_mangle]
pub extern "C" fn rust_wrapper_sapling_note_decrypt(
key: *const u8,
key_len: usize,
note: *const u8,
note_len: usize,
out: *mut u8,
out_len: &mut usize
){
let evk: Vec<u8> = marshall_from_haskell_var(key, key_len, RW);
let note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW);
let svk = ExtendedFullViewingKey::read(&*evk);
match svk {
Ok(k) => {
let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000));
let mut action_bytes = vec![0];
action_bytes.extend(&note_input.cv);
action_bytes.extend(&note_input.cmu);
action_bytes.extend(&note_input.eph_key);
action_bytes.extend(&note_input.enc_txt);
action_bytes.extend(&note_input.out_txt);
action_bytes.extend(&note_input.proof);
let action2 = OutputDescription::read(&mut action_bytes.as_slice()).unwrap();
let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External);
let pivk = SaplingPreparedIncomingViewingKey::new(&fvk);
let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action2);
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() };
marshall_to_haskell_var(&hn, out, out_len, RW);
}
None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
marshall_to_haskell_var(&hn0, out, out_len, RW);
}
}
}
Err(_e) => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
marshall_to_haskell_var(&hn0, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_orchard_note_decrypt(

View file

@ -72,6 +72,14 @@ import ZcashHaskell.Types
-> `Bool'
#}
{# fun unsafe rust_wrapper_sapling_note_decrypt as rustWrapperSaplingNoteDecode
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `ShieldedOutput'&
, getVarBuffer `Buffer DecodedNote'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer UnifiedFullViewingKey'&
@ -82,7 +90,7 @@ import ZcashHaskell.Types
{# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `OrchardAction'&
, getVarBuffer `Buffer OrchardDecodedAction'&
, getVarBuffer `Buffer DecodedNote'&
}
-> `()'
#}

View file

@ -35,7 +35,7 @@ decodeUfvk str =
-- | Attempts to decode the given @OrchardAction@ using the given @UnifiedFullViewingKey@.
decryptOrchardAction ::
OrchardAction -> UnifiedFullViewingKey -> Maybe OrchardDecodedAction
OrchardAction -> UnifiedFullViewingKey -> Maybe DecodedNote
decryptOrchardAction encAction key =
case a_value decodedAction of
0 -> Nothing

View file

@ -3,9 +3,12 @@ module ZcashHaskell.Sapling where
import C.Zcash
( rustWrapperIsShielded
, rustWrapperSaplingCheck
, rustWrapperSaplingNoteDecode
, rustWrapperSaplingVkDecode
)
import qualified Data.ByteString as BS
import Foreign.Rust.Marshall.Variable (withPureBorshVarBuffer)
import ZcashHaskell.Types (DecodedNote(..), ShieldedOutput)
-- | Check if given bytesting is a valid encoded shielded address
isValidShieldedAddress :: BS.ByteString -> Bool
@ -18,3 +21,13 @@ isValidSaplingViewingKey = rustWrapperSaplingVkDecode
-- | Check if the given bytestring for the Sapling viewing key matches the second bytestring for the address
matchSaplingAddress :: BS.ByteString -> BS.ByteString -> Bool
matchSaplingAddress = rustWrapperSaplingCheck
-- | Attempt to decode the given Sapling raw output with the given Sapling viewing key
decodeSaplingOutput :: BS.ByteString -> ShieldedOutput -> Maybe DecodedNote
decodeSaplingOutput key out =
case a_value decodedAction of
0 -> Nothing
_ -> Just decodedAction
where
decodedAction =
withPureBorshVarBuffer $ rustWrapperSaplingNoteDecode key out

View file

@ -153,15 +153,15 @@ instance FromJSON OrchardAction where
(decodeHexText cval)
(decodeHexText a)
-- | Type to represent a decoded Orchard Action
data OrchardDecodedAction = OrchardDecodedAction
-- | Type to represent a decoded note
data DecodedNote = DecodedNote
{ a_value :: Int64 -- ^ The amount of the transaction in _zatoshis_.
, a_recipient :: BS.ByteString -- ^ The recipient Orchard receiver.
, a_memo :: BS.ByteString -- ^ The decoded shielded memo field.
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardDecodedAction
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote
-- * Helpers
-- | Helper function to turn a hex-encoded string to bytestring

View file

@ -11,17 +11,18 @@ import Data.Word
import Test.Hspec
import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( isValidSaplingViewingKey
( decodeSaplingOutput
, isValidSaplingViewingKey
, isValidShieldedAddress
, matchSaplingAddress
)
import ZcashHaskell.Types
( BlockResponse(..)
, DecodedNote(..)
, OrchardAction(..)
, OrchardDecodedAction(..)
, RawData(..)
, RawTxResponse(..)
, ShieldedOutput(s_cmu)
, ShieldedOutput(ShieldedOutput, s_cmu)
, UnifiedFullViewingKey(..)
, decodeHexText
)
@ -313,7 +314,25 @@ main = do
describe "Decode Sapling tx" $ do
let svk =
"zxviews1qvapd723qqqqpqq09ldgykvyusthmkky2w062esx5xg3nz4m29qxcvndyx6grrhrdepu4ns88sjr3u6mfp2hhwj5hfd6y24r0f64uwq65vjrmsh9mr568kenk33fcumag6djcjywkm5v295egjuk3qdd47atprs0j33nhaaqep3uqspzp5kg4mthugvug0sc3gc83atkrgmguw9g7gkvh82tugrntf66lnvyeh6ufh4j2xt0xr2r4zujtm3qvrmd3vvnulycuwqtetg2jk384"
it "succeeds with correct key" pending
it "succeeds with correct key" $ do
let rawKey = decodeBech32 svk
let rawNote =
ShieldedOutput
(decodeHexText
"a2853a37316dfc32301dfcae2583797a6dab0aaba8b08e9bd052d2d65bc66c14")
(decodeHexText
"65dc83588d7fa63510391505b2b57455d8740a29bce1ad20f798a647b4163a5a")
(decodeHexText
"3c9748817d696cf1e540d0ffa759092740e23c2b07415d326f9d007ba1a43bea")
(decodeHexText
"62ffe037fc83aded21e4c91722b52520a2395c23e9c1a896f4b0f12d32ae8e31833d9d95adae40f6ecf7aff52af184efd390a4c1aa76b5fb1cab6003b1a8a004016f385926661f56d38273ec2c3df7775210310a65fff5fa9ac5509f0784eefea28bdcc67b0ff69eef930335f3b9768529e2bfe733024492101f642f989de8cbf04dd66638e9317780bce47085079675b772664c8007e96597dba83ea9af22ddf07ff1c212983d4a902914431245357527294e69ea5616e720ef1e9215bbfa33ba108b8d07efff2bad1850525d7725c681761c9b8c844a5548afabf176863de7b4cde3901defc3e83d31086d3c6e6af9a5fcc3cfb38b52ac7de84f91df5e0587f7603773401a62eeef10cd3ccf4d927ef42402c32f32280abbeaac33e73ceda52089820a186e9a1adfea81453998c6bbaa0deb41bc4f94586bfee80bad25fc71abe7c6dd44bcb1a6929a0112c7e4f8fcadb9745bde9422b954f72954c4d22db48719de61f383d620935b647337f73d119d79fd208e1d5a92f0855447df5782cd4764ba91efa65d9e4ebaa34e2eccb7aac93a5b0efe0c7664f3cd9384b3ff706ad3019c907cdcfa084351c9f6a0bfa8c78c91272ca66ac86dd6e1d0d6ba9704ea7dc54f71a053dce91f844c1ca62b5ddfe6b53834f4a816b1b01460810d9b87517659f4915adf4b84783a60ecf3bd71569259f1ff90a91a0b314bd4c77976d7893bf42e5d6ad0f8df95eb6d6c69d41490be8e39b2452df3bebfc297d5b0fc97f081890390fb0727a96898585f0120a7da9a798f2032590553f724d8756c67c5b0d1c0d233")
(decodeHexText
"01c4ed60fa283994fd712aab17ca6360256fd5aef0ebc48f0256e3eda5894b53981d0d46768aefdc85b48c1525b7f134dce5d4ec2d76c03c821513f1652d9671219d744bdce5e69b9a74ca0c7c837668")
(decodeHexText
"9534b3d594e1609b3bace18608750b35a066c57f85e291d194400cb351430bbbe212abba32be071e747b7310863bd5fd989855a6567a351b288144b6e9f838c6a517db94673246ef0010b65f9c0be8aca654f6f57b83d893663cfd389ab96ce50e8077fe588c16b1b5989c6cc262e6658efb9b88ac800e49e9e5999e2651b8fff28fa77071d63790df155ed8344e2581ac5205b31d4f17bd748fcf60e35a9d6048d23c94c7aca8d4e541fda497aa268df9c173af5877a5da56d8fa2a42166900")
print rawNote
let a = decodeSaplingOutput (bytes rawKey) rawNote
maybe 0 a_value a `shouldNotBe` 0
describe "Decode Orchard tx" $ do
let uvk =
"uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"