zcash-haskell/test/Spec.hs

388 lines
20 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
import C.Zcash (rustWrapperIsUA)
import Data.Aeson
import qualified Data.ByteString as BS
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import qualified Data.Text.Lazy.Encoding as LE
import qualified Data.Text.Lazy.IO as LTIO
import Data.Word
import Test.Hspec
import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( decodeSaplingOutput
, isValidSaplingViewingKey
, isValidShieldedAddress
, matchSaplingAddress
)
import ZcashHaskell.Types
( BlockResponse(..)
, DecodedNote(..)
, OrchardAction(..)
, RawData(..)
, RawTxResponse(..)
, ShieldedOutput(ShieldedOutput, s_cmu)
, UnifiedFullViewingKey(..)
, decodeHexText
)
import ZcashHaskell.Utils
main :: IO ()
main = do
hspec $ do
describe "Bech32" $ do
let s = "bech321qqqsyrhqy2a"
let decodedString = decodeBech32 s
it "hrp matches" $ do hrp decodedString `shouldBe` "bech32"
it "data matches" $ do
bytes decodedString `shouldBe` BS.pack ([0x00, 0x01, 0x02] :: [Word8])
describe "F4Jumble" $ do
it "jumble a string" $ do
let input =
[ 0x5d
, 0x7a
, 0x8f
, 0x73
, 0x9a
, 0x2d
, 0x9e
, 0x94
, 0x5b
, 0x0c
, 0xe1
, 0x52
, 0xa8
, 0x04
, 0x9e
, 0x29
, 0x4c
, 0x4d
, 0x6e
, 0x66
, 0xb1
, 0x64
, 0x93
, 0x9d
, 0xaf
, 0xfa
, 0x2e
, 0xf6
, 0xee
, 0x69
, 0x21
, 0x48
, 0x1c
, 0xdd
, 0x86
, 0xb3
, 0xcc
, 0x43
, 0x18
, 0xd9
, 0x61
, 0x4f
, 0xc8
, 0x20
, 0x90
, 0x5d
, 0x04
, 0x2b
] :: [Word8]
let out =
[ 0x03
, 0x04
, 0xd0
, 0x29
, 0x14
, 0x1b
, 0x99
, 0x5d
, 0xa5
, 0x38
, 0x7c
, 0x12
, 0x59
, 0x70
, 0x67
, 0x35
, 0x04
, 0xd6
, 0xc7
, 0x64
, 0xd9
, 0x1e
, 0xa6
, 0xc0
, 0x82
, 0x12
, 0x37
, 0x70
, 0xc7
, 0x13
, 0x9c
, 0xcd
, 0x88
, 0xee
, 0x27
, 0x36
, 0x8c
, 0xd0
, 0xc0
, 0x92
, 0x1a
, 0x04
, 0x44
, 0xc8
, 0xe5
, 0x85
, 0x8d
, 0x22
] :: [Word8]
BS.pack out `shouldBe` f4Jumble (BS.pack input)
it "unjumble a string" $ do
let input =
[ 0x5d
, 0x7a
, 0x8f
, 0x73
, 0x9a
, 0x2d
, 0x9e
, 0x94
, 0x5b
, 0x0c
, 0xe1
, 0x52
, 0xa8
, 0x04
, 0x9e
, 0x29
, 0x4c
, 0x4d
, 0x6e
, 0x66
, 0xb1
, 0x64
, 0x93
, 0x9d
, 0xaf
, 0xfa
, 0x2e
, 0xf6
, 0xee
, 0x69
, 0x21
, 0x48
, 0x1c
, 0xdd
, 0x86
, 0xb3
, 0xcc
, 0x43
, 0x18
, 0xd9
, 0x61
, 0x4f
, 0xc8
, 0x20
, 0x90
, 0x5d
, 0x04
, 0x2b
] :: [Word8]
let out =
[ 0x03
, 0x04
, 0xd0
, 0x29
, 0x14
, 0x1b
, 0x99
, 0x5d
, 0xa5
, 0x38
, 0x7c
, 0x12
, 0x59
, 0x70
, 0x67
, 0x35
, 0x04
, 0xd6
, 0xc7
, 0x64
, 0xd9
, 0x1e
, 0xa6
, 0xc0
, 0x82
, 0x12
, 0x37
, 0x70
, 0xc7
, 0x13
, 0x9c
, 0xcd
, 0x88
, 0xee
, 0x27
, 0x36
, 0x8c
, 0xd0
, 0xc0
, 0x92
, 0x1a
, 0x04
, 0x44
, 0xc8
, 0xe5
, 0x85
, 0x8d
, 0x22
] :: [Word8]
f4UnJumble (BS.pack out) `shouldBe` BS.pack input
describe "JSON parsing" $ do
it "block response" $ do
j <- LTIO.readFile "block.json"
let p = eitherDecode $ LE.encodeUtf8 j :: Either String BlockResponse
case p of
Left s -> s `shouldBe` ""
Right x -> bl_height x `shouldBe` 2196277
it "raw transaction response" $ do
j <- LTIO.readFile "tx.json"
let t = eitherDecode $ LE.encodeUtf8 j :: Either String RawTxResponse
case t of
Left s -> s `shouldBe` ""
Right x ->
rt_id x `shouldBe`
"5242b51f22a7d6fe9dee237137271cde704d306a5fff6a862bffaebb6f0e7e56"
describe "Sapling address" $ do
it "succeeds with valid address" $ do
let sa =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8"
isValidShieldedAddress sa `shouldBe` True
it "fails with invalid address" $ do
let sa =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvffake"
isValidShieldedAddress sa `shouldBe` False
describe "Decode Sapling VK" $ do
let vk =
"zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwcpjs"
let sa =
"zs1g2ne5w2r8kvalwzngsk3kfzppx3qcx5560pnfmw9rj5xfd3zfg9dkm7hyxnfyhc423fev5wuue4"
let sa' =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8"
let rawKey = decodeBech32 vk
let rawSa = decodeBech32 sa
let rawSa' = decodeBech32 sa'
it "is mainnet" $ do hrp rawKey `shouldBe` "zxviews"
it "is valid Sapling extended full viewing key" $ do
isValidSaplingViewingKey (bytes rawKey) `shouldBe` True
it "matches the right Sapling address" $ do
matchSaplingAddress (bytes rawKey) (bytes rawSa) `shouldBe` True
it "doesn't match the wrong Sapling address" $ do
matchSaplingAddress (bytes rawKey) (bytes rawSa') `shouldBe` False
describe "Decode invalid Sapling VK" $ do
let vk =
"zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwfake"
let rawKey = decodeBech32 vk
it "is not mainnet" $ do hrp rawKey `shouldBe` "fail"
describe "Unified address" $ do
it "succeeds with correct UA" $ do
let ua =
"u1salpdyefywvsg2dlmxg9589yznh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur"
isValidUnifiedAddress ua `shouldBe` True
it "fails with incorrect UA" $ do
let ua =
"u1salpdyefbreakingtheaddressh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur"
isValidUnifiedAddress ua `shouldBe` False
describe "Decode UVK from YWallet" $ do
let uvk =
"uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
let res = decodeUfvk uvk
it "is mainnet" $ do maybe 0 net res `shouldBe` 1
it "has Orchard key" $ do BS.length (maybe "" o_key res) `shouldBe` 96
it "has Sapling key" $ do BS.length (maybe "" s_key res) `shouldBe` 128
it "does not have Transparent key" $ do
BS.length (maybe "" t_key res) `shouldBe` 1
describe "Decode bad UVK" $ do
it "should fail" $ do
let fakeUvk =
"uview1u83changinga987bundchofch4ract3r5x8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
decodeUfvk fakeUvk `shouldBe` Nothing
describe "Decode Sapling tx" $ do
let svk =
"zxviews1qvapd723qqqqpqq09ldgykvyusthmkky2w062esx5xg3nz4m29qxcvndyx6grrhrdepu4ns88sjr3u6mfp2hhwj5hfd6y24r0f64uwq65vjrmsh9mr568kenk33fcumag6djcjywkm5v295egjuk3qdd47atprs0j33nhaaqep3uqspzp5kg4mthugvug0sc3gc83atkrgmguw9g7gkvh82tugrntf66lnvyeh6ufh4j2xt0xr2r4zujtm3qvrmd3vvnulycuwqtetg2jk384"
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"
let res = decodeUfvk uvk
let a =
OrchardAction
(decodeHexText
"248b16d98dfa33f7ba69a0610a63b606699da76c288840b81d7691ee42764416")
(decodeHexText
"17fcc27cce560733edaf91439a8020c4a029a4e7d5893ce024d5ff4b40bbd0a9")
(decodeHexText
"34796d541864832acca43f083892e98a46c912802d5643672d3f25bea177c61c")
(decodeHexText
"a6d2ca10e3fc7446e372266ef45ee3dc0ba373bd378e6bf3092519a7f272bd8c")
(decodeHexText
"08beafdf59110b5d045e4acc13731ef1a27bfa3a9cabe1d575640c18f18ee6697fbb132d36e982ae3eadf5f37fd35f42c2bb07def14759deab1fbe2f98dc1d5913e4a6ef388b714e2cfd6d89ba2302800e02ab5f45e0e02e3895448518cd8afd2c37bb48a66d8b988a37de9d0838d92876894a311bb9f314ba842e5c18ff7a3d8c7f0ff1a7209e2d661595db8f4a4aa267b9593258914bf63c09286eeda7c9b27ddbb4646208c0d03a8fbdc5d96633335a5a65316f5b25189bdce735bdea7e900de56d3b475ae51b7c35eb7ae79ba104baeb0a5a09d1cd8bb347ab34fb26d62ddbf024f5394710626ec0a665b9c917e65b00256db635145164a0329db7bc5358f435d573b2662adf8a6128801825ec8fb7d8aeef567d35c875ddd784fceb7620355e3f056a648b39b4b2d29a1f5e7b7c4ec5fd2b1874ff1e832b308f8644e83878d90582b9a6fd6c293e19dd3e24dbe1b4c15c96608169843d46551900a8cb787b15f0f1696b736dd4c8ebacf1e3288b14e469bdc004fa8557d6b1395700eaba59334906bb012f876e4cd7acd2157719ebd2e28bd0cd4ab4ac458f8848e1c30e729803dd47102200fe703932a15c3618862ec83b40d3aa0ec2343641bcb9afbf931ab21aa4afdbe7e51deca24283c2ccab0eef6e07aac5a4bf3a775bf7d2ddfc8d8766c3bf8e35df1435cf515d93c3b9549477bd9f53d133f05dd256fbcc0b13a63e3e7f8cce6301ab4f19c114f5af079f8c581537458e861b553218a890ea3e77fb99781c7088cd43c67c155ec611c1148721cab5fd0168e4a5ec390b506ec44145474c")
(decodeHexText
"1e40d33446d9f0f0fad40f8829c1ffe860c11c3439e2c15d37c6c40282f9e933dc01798c800e6c92edb4d20478b92559510eda67f3855f68f5ab22ca31e1885c7fa9d4c9ebfb62ceb5e73267bcad0ba7")
(decodeHexText
"63d0d6e8e92691f700bf8af246dcd4ae1041b13e3969f7a9d819a06e0f9429bc")
(decodeHexText
"fe362be160accf2794841c244e8d80bbeb80b9bc95bb653d297a98d32bddf5a05dd5f874891d55924a83f722f75f576f63796770c31074067694cffb2cce7a2a")
let b =
OrchardAction
(decodeHexText
"8921446787f1bd28fa0e4cc5c945ea7fc71165a25f40cd2a325dae0c4467d12c")
(decodeHexText
"240b08b7861aa78989c68cbedd0038af9b3e3456bdc7ff582d597df571d54da2")
(decodeHexText
"e1bc8ccba69ab9f429bf735417aa005cf439d27500b0d3086dbf1be764b42a36")
(decodeHexText
"c89c58ef8553e7d09ba4090654edd1a8c98763c44d3dfb9dad18286c7ef363ae")
(decodeHexText
"0eee1ca5a3a4959cd4b8bc277e6e633f950680c4acb978c14ad8d944a784f46771c9d666a203ca3ac693943d79dd23f8b76a734a62e81932cbe98e8c851f47a11aaef50249e53151f38f88262a4bae8cf26f5f8b2db1d165aff9b57b64713a677c167608585c038e34ca7bbe468e5f86475ccec0a4a8b9a43b56e342e77a6bd09415787c9f4a1c6f20599f57545f1ac32c3a338d7a5bb2d35456adb880cb65c1455969e10df5d94b8c74b244e7093b1a88cc10697a7c2f4d34b6eae3296e64b820573b4d52e06b4427af5b8f5d6722d3a93fd85da615fceac732976ad2c1be4150b4821c149521f5419ea0746fb132d47f593cfc8a3aab6b2b4480c12fadf21280ccd3142e7188d9e5aef3fcd8c5dc0c066dc975bead023ef7f89a486b615b146110ae68b703a8349a5fc225b26a08b2adaf36fb44c9ad1be59d7ced134eb84e3f0b4aec19b71b2d26e910628a11446b97c5e6bbf97e1befa4e04b5947f83c65161b92f58088d28e57adc2a2873e27008e29772c5803502842045cb355d1ea5a9d27c2683dcb38cb49d26af39625ba99b1342f700387b939e7ff6c129417ca8836fe1e96331e35c8bc0763879e8c17cd4535fbcb27a2785c0a47294e07cb54837bb997df34882ce0bececc6adca365c76fc7533cf0503458937dcfb6058b016dbbd399b9f0cca44cbc881016f4957b5e10daada3393d5b2a4cb15ed983506d4d264f9855ce2ef87a7d4a1fc03293a22c28a53c4455447d546813fa33008e5d2d81848825fae2f437ab9575ba99c230e78f4b23e575e7647beff0e4c4e2b0a1f7320e9460")
(decodeHexText
"d727aeec27bb0f7463c6ed4f5b3f4085cfd3e7218478db0dcebfca875e025320fb64bc4062251823859e963446cadd9924c559e5f981480df5a4f036daf5a8033d4c8241e128902aa1aeaf6adc149730")
(decodeHexText
"98e72813aeb6ea05347798e35379bc881d9cf2b37d38850496ee956fbecd8eab")
(decodeHexText
"cb9926f519041343c957a74f2f67900ed3d250c4dbcd26b9e2addd5247b841a9fde2219d2ef8c9ae8145fecc7792ca6770830c58c95648087f3c8a0a69369402")
let decryptedNote = decryptOrchardAction a =<< res
let decryptedNote2 = decryptOrchardAction b =<< res
describe "First action (sender)" $ do
it "Decryption fails " $ do decryptedNote `shouldBe` Nothing
describe "Second action (recipient)" $ do
it "Decryption succeeds" $ do decryptedNote2 `shouldNotBe` Nothing
it "Tx amount is validated" $ do
(a_value <$> decryptedNote2) `shouldBe` Just 3000
it "Memo is validated" $ do
let msg = maybe "" a_memo decryptedNote2
msg `shouldBe`
"Hello World!\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"