@laceio/sdk 0.1.1-canary.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{TransactionBuilder-CCjOn7NA.cjs → TransactionBuilder-DrB3NVTi.cjs} +0 -42
- package/dist/{TransactionBuilder-CIzdw18M.js → TransactionBuilder-V6aLx4SV.js} +1 -1
- package/dist/{_polyfill-buffer-patch-CM8X7JiY.cjs → _polyfill-buffer-patch-BnPVerlG.cjs} +2 -21
- package/dist/account-guards.stable-B1DSSh9D.cjs +10 -0
- package/dist/account-guards.stable-JRQ5qx2V.js +5 -0
- package/dist/{activity-type-Ch6ljxBy.cjs → activity-type.stable-BJEfeLxL.cjs} +1 -2
- package/dist/{activity-type-GsJGWs-3.js → activity-type.stable-C76_hWd7.js} +1 -2
- package/dist/{address-book-address-validator-ByIZOdO-.js → address-book-address-validator-DIUolI-s.js} +2 -2
- package/dist/{address-book-address-validator-BjOWerRE.cjs → address-book-address-validator-DNwQGlOB.cjs} +2 -2
- package/dist/bitcoin.cjs +0 -45
- package/dist/bitcoin.d.ts +1 -29782
- package/dist/bitcoin.js +0 -20
- package/dist/{blockfrost-tx-submit-provider-CLNQ5ycR.cjs → blockfrost-tx-submit-provider-BAIzV5o3.cjs} +167 -3
- package/dist/{blockfrost-tx-submit-provider-CBWQ0wK9.js → blockfrost-tx-submit-provider-DPTrapz-.js} +157 -5
- package/dist/{secret-box-DrjLZuS1.cjs → bytes.vo-Ck5u40RA.cjs} +35 -1
- package/dist/{secret-box-DUR0ACYz.js → bytes.vo-TgiwogWF.js} +25 -3
- package/dist/{cardano-network-id.vo-wfLiAxpY.js → cardano-network-id.vo-CK1tNQDg.js} +1 -1
- package/dist/{cardano-network-id.vo-BEvFd_74.cjs → cardano-network-id.vo-DSxrFk9S.cjs} +1 -1
- package/dist/cardano.cjs +18 -22
- package/dist/cardano.d.ts +3982 -18409
- package/dist/cardano.js +11 -16
- package/dist/codecs-B7X9pAsK.cjs +1945 -0
- package/dist/codecs-CC1Xjntz.js +1882 -0
- package/dist/{const-oMF-TLow.cjs → const-BDEg2Trq.cjs} +4 -4
- package/dist/{const-iwzZEE_w.cjs → const-BY8B3_0_.cjs} +1 -1
- package/dist/{const-CBGn60yu.js → const-C84qHkjp.js} +4 -4
- package/dist/{const-CjmfjBP3.js → const-DXk2tFyv.js} +1 -1
- package/dist/{delegation-tx-builder-BR6bt1fE.js → delegation-tx-builder-CGVqlaFE.js} +5 -5
- package/dist/{delegation-tx-builder-DVwGfiV0.cjs → delegation-tx-builder-CRpLP6jH.cjs} +6 -6
- package/dist/{deregistration-tx-builder-BhJRGj1M.js → deregistration-tx-builder-B-CrUyX6.js} +5 -5
- package/dist/{deregistration-tx-builder-Cv_09HGf.cjs → deregistration-tx-builder-DZGwB4cz.cjs} +6 -6
- package/dist/{earn-rewards-tx-builder-RYzKsZ6-.js → earn-rewards-tx-builder-BDbTDZmD.js} +5 -5
- package/dist/{earn-rewards-tx-builder-52gJiJYa.cjs → earn-rewards-tx-builder-D5sUT5v8.cjs} +6 -6
- package/dist/{group-cardano-addresses-by-account-D06r7daZ.cjs → group-cardano-addresses-by-account-B533oVXR.cjs} +35 -19
- package/dist/{group-cardano-addresses-by-account-CxCqFDrX.js → group-cardano-addresses-by-account-RHKoEskR.js} +35 -19
- package/dist/{guarded-transaction-signer-BhnTBjf6.cjs → guarded-transaction-signer-Bxl6_XNd.cjs} +18 -17
- package/dist/{guarded-transaction-signer-BSEx83R-.js → guarded-transaction-signer-CpKLj8Kp.js} +15 -8
- package/dist/{in-memory-wallet-integration-CH4rcg92.cjs → in-memory-wallet-integration-CnLYeX4-.cjs} +108 -17
- package/dist/{in-memory-wallet-integration-BKu985Jz.js → in-memory-wallet-integration-Croiuopx.js} +104 -7
- package/dist/{in-memory-wallet-integration-B0blZBxM.js → in-memory-wallet-integration-D-qn65Kx.js} +2 -5
- package/dist/{in-memory-wallet-integration-CwPCDMIe.cjs → in-memory-wallet-integration-DVh9sRXa.cjs} +2 -3
- package/dist/index.cjs +47 -84
- package/dist/index.d.ts +13618 -40268
- package/dist/index.js +24 -52
- package/dist/{init-DUF4ar8P.cjs → init-B64zCLxG.cjs} +4 -4
- package/dist/{init-Y3mkB4XS.js → init-BAJJzBza.js} +2 -2
- package/dist/{init-BTzOjA8x.cjs → init-BJVU54pI.cjs} +1 -1
- package/dist/{init-D6BxBs7e.cjs → init-BKhH6mVR.cjs} +1 -1
- package/dist/{init-EP0CUWNK.cjs → init-BsCBAGWf.cjs} +2 -2
- package/dist/{init-Dy-05ktr.cjs → init-BtzMZ7Pk.cjs} +1 -1
- package/dist/init-Bv53hpLM.js +19 -0
- package/dist/{init-BopV9ed4.cjs → init-C-jErzp5.cjs} +2 -2
- package/dist/{init-CjT-WZcW.cjs → init-C4Bkib9u.cjs} +59 -49
- package/dist/{init-BEjCRHxU.cjs → init-C7IeJwQR.cjs} +61 -258
- package/dist/{init-C-G32J12.cjs → init-CB__aLD7.cjs} +2 -2
- package/dist/{init-CyAY4Lzl.js → init-CFYsq82V.js} +1 -1
- package/dist/{init-0zzs9aqU.js → init-CLHuAMB-.js} +4 -4
- package/dist/{init-DA7zmptx.js → init-CNLGJJRx.js} +1 -1
- package/dist/{init-B-YE57gg.js → init-CR6tpw1B.js} +33 -25
- package/dist/init-CRbjI7p1.js +3494 -0
- package/dist/{init-CSDpdu0Q.cjs → init-CSHwHnfg.cjs} +2 -2
- package/dist/{init-CNM5m7M2.js → init-Cg9XdAru.js} +4 -4
- package/dist/{init-B3-1ghyx.js → init-CigestoM.js} +2 -2
- package/dist/{init-Dv3zKj0r.cjs → init-D6DBUsD3.cjs} +4 -4
- package/dist/init-DAna1KCg.cjs +3495 -0
- package/dist/{init-D74mn8nH2.js → init-DDdhguVC.js} +2 -2
- package/dist/init-DGe47B22.cjs +364 -0
- package/dist/init-DMtyvYfC.cjs +21 -0
- package/dist/{init-BrdVzxei.cjs → init-DX4WyB0R.cjs} +3 -3
- package/dist/{init-C8URA2OX.js → init-DcYY6zMD.js} +1 -1
- package/dist/{init-DFMiP2ZH.cjs → init-DkIc5nqF.cjs} +1 -1
- package/dist/{init-vqxOQNZi.js → init-Dku4b2lG.js} +1 -1
- package/dist/{init-NdhG_VrF.cjs → init-DxUHsN53.cjs} +6 -6
- package/dist/{init-COMH3Hfo.js → init-FMGDET-9.js} +60 -259
- package/dist/{init-DrkOb-wd.cjs → init-LfyMo-sS.cjs} +2 -2
- package/dist/{init-NCjgwCZy.js → init-Rxohwpmu.js} +2 -2
- package/dist/init-TkPDD750.js +363 -0
- package/dist/{init-C2RgrfZe.js → init-TkPq_Ny_.js} +1 -1
- package/dist/{init-5KcGcBb5.cjs → init-U8c9yHYx.cjs} +1 -1
- package/dist/{init-CX0AMQUn.js → init-aJHl-7AO.js} +2 -2
- package/dist/{init-rmt7-fQE.cjs → init-anYu6XB7.cjs} +2 -2
- package/dist/{init-Fw89ZmrR.js → init-w9GEs73W.js} +2 -2
- package/dist/{init-Dnsv155v.js → init-y2Ra0vZP.js} +3 -3
- package/dist/{init-DFjT8YvT.js → init-zSzCjoor.js} +4 -4
- package/dist/{is-retriable-error-BUiVJDwH.js → is-retriable-error-BU0e3hVI.js} +1 -1
- package/dist/{is-retriable-error-Dpt860if.cjs → is-retriable-error-CeeuHqDa.cjs} +1 -1
- package/dist/{lace-context-BZciB9c7.js → lace-context-CZcNHr4M.js} +4 -4
- package/dist/{lace-context-DgR6vLli.cjs → lace-context-fJTDghdp.cjs} +4 -4
- package/dist/{libsodium-wrappers-BhbOA8dQ.js → libsodium-wrappers-UH0LaOUE.js} +1647 -3
- package/dist/modules-cardano-BDvuL4Xx.js +5248 -0
- package/dist/modules-cardano-D2G3De2P.cjs +5432 -0
- package/dist/{retry-config-Dmgt28wM.js → retry-config-BcUmMS--.js} +1 -1
- package/dist/{retry-config-BaCfVC1H.cjs → retry-config-DC5IMaH7.cjs} +1 -1
- package/dist/{seam-cardano-B_etH4F4.js → seam-cardano-CATqBEyK.js} +1 -1
- package/dist/{signer-factory-B7PMe3lQ.js → signer-factory-9URFliA5.js} +3 -6
- package/dist/{signer-factory-DduwpsQj.cjs → signer-factory-BWGUmnvm.cjs} +3 -4
- package/dist/{slice-CqVNnz-j.js → slice-B2PhoWAg.js} +125 -316
- package/dist/{slice-DyNs7oWp.js → slice-BNu73oBa.js} +2 -2
- package/dist/{slice-GwiiJRoq.js → slice-CB4SnzMm.js} +3 -3
- package/dist/slice-CGVHXHDt.cjs +125 -0
- package/dist/{slice-CU80pbiu.js → slice-CnyWevM9.js} +3 -3
- package/dist/{slice-CwyYrh1H.js → slice-CobanYgn.js} +2 -2
- package/dist/{slice-7G2xxYKJ.cjs → slice-CuHUaPZx.cjs} +3 -3
- package/dist/{slice-DayJvMRD.cjs → slice-D11m2EKl.cjs} +1 -1
- package/dist/{slice-Bg6HEr3K.cjs → slice-DGcNrbPR.cjs} +4 -4
- package/dist/{slice-B4CdYoPu.cjs → slice-DZmQJNV8.cjs} +2 -2
- package/dist/slice-DhvF7L6X.js +107 -0
- package/dist/{slice-C9TT5KhY.cjs → slice-Do2R2CYW.cjs} +6 -6
- package/dist/{slice-Bs2aMERu.cjs → slice-DtaO72qY.cjs} +3 -3
- package/dist/{slice-C34iOOUH.cjs → slice-HeHCv3bl.cjs} +7 -7
- package/dist/{slice-D-9aPKG-.js → slice-d0p6-xj5.js} +6 -6
- package/dist/{slice-CK07EBCo.js → slice-eIwcwKHI.js} +4 -4
- package/dist/{slice-DBpJ2QGf.js → slice-maaskdUn.js} +6 -6
- package/dist/{slice-6sP0Qs5V.cjs → slice-mxLQ_FeY.cjs} +127 -343
- package/dist/{slice-DnG-yvpI.cjs → slice-oqm_r1cS.cjs} +2 -2
- package/dist/{slice-Da426sf9.js → slice-zJ9XVYjZ.js} +1 -1
- package/dist/{sync-operation-utils-KT5Y9yFK.js → sync-operation-utils-BYXLO80m.js} +52 -1
- package/dist/{sync-operation-utils-C9kcmjKH.cjs → sync-operation-utils-SdPbwFoD.cjs} +87 -0
- package/dist/{token-id-mapper-qx7XfH0b.js → token-id-mapper-B2ivlLv3.js} +2 -2
- package/dist/{token-id-mapper-IApjwuJb.cjs → token-id-mapper-DsPAL6Nh.cjs} +2 -2
- package/dist/{util-D1e0skzp.js → util-BaLzwa32.js} +1 -1
- package/dist/{util-BeG7WXzM.cjs → util-CGzqQ8IV.cjs} +1 -1
- package/dist/{utils-DsElZgCI.cjs → utils-5JvjbqH3.cjs} +1 -7
- package/dist/{utils-BfBEUvWA.js → utils-o8aMjfln.js} +2 -2
- package/dist/{vote-delegation-tx-builder-CtdIgaIt.cjs → vote-delegation-tx-builder-BpEMSti4.cjs} +6 -6
- package/dist/{vote-delegation-tx-builder-CQjGvVNZ.js → vote-delegation-tx-builder-CmKv6GoG.js} +5 -5
- package/package.json +4 -96
- package/dist/_polyfill-buffer-patch-yNxN4d5E.js +0 -1467
- package/dist/_web-crypto-random-bytes-Bo-GTcRS.js +0 -197
- package/dist/acc-module-4iIUv6My.cjs +0 -27654
- package/dist/acc-module-Cmj8EsIb.js +0 -27624
- package/dist/account-BlYkEmcs.cjs +0 -11
- package/dist/account-DVX0F5_M.js +0 -6
- package/dist/account-call-D68oF6bh.js +0 -267
- package/dist/account-call-DIIa2rK_.cjs +0 -314
- package/dist/account-call-NgLhYein.cjs +0 -4
- package/dist/account-call-n-Nkp35e.js +0 -3
- package/dist/account-guards-BtjTGxDZ.cjs +0 -42
- package/dist/account-guards-D9Lu5T3W.js +0 -36
- package/dist/account-tokens-CtN2BaFy.js +0 -16
- package/dist/account-tokens-DXr8DlpW.cjs +0 -21
- package/dist/address-B9ntKL8K.js +0 -13761
- package/dist/address-BXp-mY8H.cjs +0 -11270
- package/dist/address-book-address-validator-DK0dnsRS.cjs +0 -41
- package/dist/address-book-address-validator-Di_ywo8M.js +0 -39
- package/dist/artefact-loader-54uK5RAa.js +0 -160
- package/dist/artefact-loader-DZPYrq-6.cjs +0 -183
- package/dist/auto-dismiss-failure-on-success-C-qoJuid.cjs +0 -37
- package/dist/auto-dismiss-failure-on-success-s8MGDEIu.js +0 -31
- package/dist/bip32-D6wd_QMn.cjs +0 -3851
- package/dist/bitcoin-data-provider-BYPSmuZS.js +0 -21
- package/dist/bitcoin-data-provider-w1oEDSU3.cjs +0 -26
- package/dist/bn-d3nSOIf7.cjs +0 -2553
- package/dist/browser-B1eMbDwh.js +0 -7468
- package/dist/bytes.vo-BB7_4JK6.js +0 -23
- package/dist/bytes.vo-BNRquqaO.cjs +0 -34
- package/dist/cardano-DfPwhwm-.js +0 -3
- package/dist/client-DXBLHk7K.cjs +0 -808
- package/dist/codecs-Cswkg7Tm.cjs +0 -4051
- package/dist/codecs-Dc1Krpbt.js +0 -3903
- package/dist/const-BfEnY8fg.js +0 -153
- package/dist/const-BhYOdznB.cjs +0 -42
- package/dist/const-CUnCl5U0.js +0 -13
- package/dist/const-DLrX5SD9.cjs +0 -31995
- package/dist/contract-C_CBwwiC.cjs +0 -189
- package/dist/contract-s_x-9-lz.js +0 -171
- package/dist/create-add-accounts-Brqk84vF.js +0 -102
- package/dist/create-add-accounts-DzXIh7Ot.cjs +0 -114
- package/dist/device-commitment-hex.vo-5wFtQvDS.js +0 -26
- package/dist/device-commitment-hex.vo-CT3RmAug.cjs +0 -31
- package/dist/device-readers-70i7tQqU.cjs +0 -8
- package/dist/device-readers-B7--ezKM.js +0 -4
- package/dist/device-readers-BWOmz9eD.js +0 -145
- package/dist/device-readers-CIgYZN7f.cjs +0 -168
- package/dist/dist-BMi5ZjEq.cjs +0 -43175
- package/dist/dist-BtIq4XFn.cjs +0 -535
- package/dist/dist-C3SHC_Ho.cjs +0 -1394
- package/dist/dist-CykEjYEX.cjs +0 -3814
- package/dist/dist-Dcsxh3be.cjs +0 -3452
- package/dist/dist-DqJfSlTd.cjs +0 -4413
- package/dist/dist-nTZ70qj7.cjs +0 -1306
- package/dist/enc-key-circuits-C8fGAMEF.cjs +0 -24
- package/dist/enc-key-circuits-DA2Xs3vZ.js +0 -13
- package/dist/english-Byb9M0E_.cjs +0 -2057
- package/dist/envelopes-BkH1Qdxk.js +0 -1620
- package/dist/envelopes-ETZEJS_O.cjs +0 -1721
- package/dist/errors-BZ8hnhCM.cjs +0 -584
- package/dist/errors-DIJTAsLa.js +0 -392
- package/dist/esm-HBSUQw3w.js +0 -384
- package/dist/esm-LZuaZjMf.cjs +0 -389
- package/dist/experimental.cjs +0 -7073
- package/dist/experimental.d.ts +0 -84347
- package/dist/experimental.js +0 -6915
- package/dist/gated-call-Boy6rXdC.js +0 -3
- package/dist/gated-call-DJWY299W.cjs +0 -736
- package/dist/gated-call-DjoRPqn1.js +0 -695
- package/dist/gated-call-SomdJ5nj.cjs +0 -4
- package/dist/grant-circuits-_1x--moV.cjs +0 -49
- package/dist/grant-circuits-swuzEdUl.js +0 -26
- package/dist/grant-rules-7Zqi_3BN.cjs +0 -7114
- package/dist/grant-rules-_xJHAT2M.js +0 -6833
- package/dist/grant-status-B2y49iIu.js +0 -2688
- package/dist/grant-status-DOr3t_u8.cjs +0 -2820
- package/dist/grants--b0LQQzZ.cjs +0 -266
- package/dist/grants-BIs6qm3b.js +0 -213
- package/dist/grants-CSgfYlOO.js +0 -5
- package/dist/grants-D61131nK.cjs +0 -17
- package/dist/hex-D9ITYbwt.cjs +0 -263
- package/dist/hex-mkXcV3pN.js +0 -191
- package/dist/http-client-B3N-TMxu.cjs +0 -167
- package/dist/http-client-TRlhDZGh.js +0 -155
- package/dist/identity-BMLuufva.cjs +0 -263
- package/dist/identity-DII6f-h7.js +0 -233
- package/dist/in-memory-wallet-integration-B2xzxORj.cjs +0 -20741
- package/dist/in-memory-wallet-integration-CUKesGG1.js +0 -130
- package/dist/in-memory-wallet-integration-CoPx69lQ.cjs +0 -240
- package/dist/in-memory-wallet-integration-TM9bBphA.js +0 -20738
- package/dist/init-B5X_sGj9.cjs +0 -6
- package/dist/init-BJn-8_hf.js +0 -141
- package/dist/init-Bp6yJk31.js +0 -1142
- package/dist/init-Bu4iXcTH.cjs +0 -34
- package/dist/init-C00gw1zi.js +0 -422
- package/dist/init-CIjZaQvL.js +0 -3584
- package/dist/init-CR7BfztQ.cjs +0 -18
- package/dist/init-Cp8EJGbm.js +0 -5
- package/dist/init-CpIadSUb.js +0 -33
- package/dist/init-D5UiYI12.cjs +0 -106
- package/dist/init-DAikR9qU.js +0 -1177
- package/dist/init-DGjXwWs2.cjs +0 -143
- package/dist/init-DHrCbpkW.cjs +0 -121
- package/dist/init-DU5yYX5u.js +0 -2374
- package/dist/init-DWvkFDkc.cjs +0 -1176
- package/dist/init-DY1unoGb.cjs +0 -1141
- package/dist/init-DlqJuHhl.js +0 -119
- package/dist/init-DuYENxci.js +0 -17
- package/dist/init-E7c40ddW.cjs +0 -3585
- package/dist/init-GsT2AEa4.cjs +0 -424
- package/dist/init-KQnIdm39.js +0 -104
- package/dist/init-ZjNDDC-T.cjs +0 -2375
- package/dist/jubjub-authoriser-BxE3oN2y.js +0 -86
- package/dist/jubjub-authoriser-C6SDEP9I.cjs +0 -87
- package/dist/key-source-5oUBFiC7.cjs +0 -583
- package/dist/key-source-B08HukZg.js +0 -2550
- package/dist/keyDerivation-D3k9XyHg.js +0 -4077
- package/dist/keyDerivation-u0nBA93T.cjs +0 -274
- package/dist/lib--iqvW4O_.js +0 -2354
- package/dist/lib-Yk6Upl5D.cjs +0 -2359
- package/dist/midnight-account-id.vo-0x9X-r3l.cjs +0 -10
- package/dist/midnight-account-id.vo-DjCAGcqc.js +0 -5
- package/dist/midnight-engine-DpemplKO.js +0 -5
- package/dist/midnight-engine-xU1wotne.cjs +0 -6
- package/dist/midnight-wallet-DIsAlSn6.js +0 -18
- package/dist/midnight-wallet-XTZc2li7.cjs +0 -24
- package/dist/midnight.cjs +0 -1406
- package/dist/midnight.d.ts +0 -28586
- package/dist/midnight.js +0 -1350
- package/dist/modules-cardano-CtWLntet.cjs +0 -1108
- package/dist/modules-cardano-D2dkeKHY.js +0 -995
- package/dist/node-relay-Cv0cNknM.js +0 -48
- package/dist/node-relay-DblH5fKv.cjs +0 -49
- package/dist/outputs-Bd2yZVjx.js +0 -720
- package/dist/outputs-CJoQBIA2.cjs +0 -990
- package/dist/pbkdf2-CIW-oWrm.cjs +0 -1337
- package/dist/pending-operation-B0_P78Jo.cjs +0 -26
- package/dist/pending-operation-BIS6mpjB.js +0 -21
- package/dist/providers-BLgJ0uQs.cjs +0 -366
- package/dist/providers-CwnT4vEE.js +0 -343
- package/dist/redacted-BmE2aXqr.js +0 -81
- package/dist/redacted-CRZJLS3w.cjs +0 -86
- package/dist/safe-buffer-B_vMqJWU.js +0 -43
- package/dist/safe-buffer-DYOwEfHB.cjs +0 -48
- package/dist/sdk-DIcjlgLF.js +0 -1241
- package/dist/sdk-Dgal10kG.cjs +0 -1439
- package/dist/sdk-init-BKkqFtWZ.cjs +0 -3791
- package/dist/sdk-init-DNd-oQXp.js +0 -1044
- package/dist/send-proof-progress-DTK3ZuYB.js +0 -22
- package/dist/send-proof-progress-dDoqIohG.cjs +0 -33
- package/dist/side-effects-BnqYz7Nl.js +0 -768
- package/dist/side-effects-CngaeXi0.cjs +0 -804
- package/dist/sign-bitcoin-transaction-with-seed-D4z62-jV.js +0 -147
- package/dist/sign-bitcoin-transaction-with-seed-DsLYXnCY.cjs +0 -158
- package/dist/signer-3fk8lZtE.cjs +0 -7
- package/dist/signer-CsL28DrK.cjs +0 -121
- package/dist/signer-DGluibtn.js +0 -92
- package/dist/signer-RqB5pu3K.js +0 -2
- package/dist/signer-factory-BA7WFJVZ.js +0 -282
- package/dist/signer-factory-CORIG4Yl.cjs +0 -130
- package/dist/signer-factory-Cd2TwD50.js +0 -129
- package/dist/signer-factory-Cwd6o-lG.cjs +0 -281
- package/dist/slice-BOyAAaj0.cjs +0 -378
- package/dist/slice-BnTJeMOb.js +0 -354
- package/dist/slice-C4uXUT7S.js +0 -9013
- package/dist/slice-CxKWDuBP.js +0 -87
- package/dist/slice-DQv_RhsI.cjs +0 -9031
- package/dist/slice-HjLXJyQG2.cjs +0 -105
- package/dist/src-BDzYIksE.cjs +0 -20637
- package/dist/src-BFoefPv_.js +0 -20755
- package/dist/store-B1Lq3cEf.cjs +0 -66
- package/dist/store-DuBG8qh5.js +0 -37
- package/dist/submit-BBYjnZu8.cjs +0 -87
- package/dist/submit-D_yxYM_T.js +0 -70
- package/dist/submit-bitcoin-tx-BkZIeF4b.js +0 -983
- package/dist/submit-bitcoin-tx-DZNQgihl.cjs +0 -1049
- package/dist/token-id-mapper-Bnds3xKH.cjs +0 -18
- package/dist/token-id-mapper-CnKHQ8DV.js +0 -16
- package/dist/tokens-Cevp7rdU.cjs +0 -62
- package/dist/tokens-Diw4MLRM.js +0 -44
- package/dist/types-B8uiJn-X.js +0 -12
- package/dist/types-CJT6XF2c.js +0 -10
- package/dist/types-CLRUYpfe.cjs +0 -23
- package/dist/types-DWbVZpnF.cjs +0 -15
- package/dist/types-T0cjaR75.js +0 -33
- package/dist/types-Zj78LtYu.cjs +0 -50
- package/dist/unwrap-provider-result-BZSiwabU.js +0 -32
- package/dist/unwrap-provider-result-ZqfBsTkk.cjs +0 -39
- package/dist/utils-BfMdr_1w.cjs +0 -112
- package/dist/utils-CvAW5Yw7.js +0 -65
- package/dist/v8Signatures-CO_Uloy7.cjs +0 -71
- package/dist/watch-Cz1J42um.cjs +0 -52205
- package/dist/watch-Z53L4yoK.js +0 -13054
- /package/dist/{account-id.vo-DwqFjK5u.cjs → account-id.vo-LvCBVn5r.cjs} +0 -0
- /package/dist/{account-id.vo-OPz2v-JZ.js → account-id.vo-MYSI7YFm.js} +0 -0
- /package/dist/{actions-iLXdgL5Y.js → actions-9QSQR-GB.js} +0 -0
- /package/dist/{actions-B2BJ4RTy.cjs → actions-DdGbj7c7.cjs} +0 -0
- /package/dist/{apply-vkey-witnesses-ZnFS0jnJ.cjs → apply-vkey-witnesses-CLzVSWxl.cjs} +0 -0
- /package/dist/{apply-vkey-witnesses-BKx3lcs0.js → apply-vkey-witnesses-ClZbpk5w.js} +0 -0
- /package/dist/{asyncoption-CruEYOBI.js → asyncoption-Cw07zPH7.js} +0 -0
- /package/dist/{asyncoption-i0VbqVTq.cjs → asyncoption-DYoGuQfP.cjs} +0 -0
- /package/dist/{bignum.vo-CEXOIew0.js → bignum.vo-B0rK_PQ1.js} +0 -0
- /package/dist/{bignum.vo-hx2kuqlA.cjs → bignum.vo-DsG3kNiZ.cjs} +0 -0
- /package/dist/{bip32ed25519-B7Yat217.cjs → bip32ed25519-1ApqeAZk.cjs} +0 -0
- /package/dist/{bip32ed25519-FQ8qvdcq.js → bip32ed25519-DetAdGwD.js} +0 -0
- /package/dist/{blake2b-DEeVUdKR.cjs → blake2b-B9tftbvG.cjs} +0 -0
- /package/dist/{blake2b-BiQTw3GC.js → blake2b-DOEVc57D.js} +0 -0
- /package/dist/{blockchain-network-id.vo-BAKEwGjG.cjs → blockchain-network-id.vo-BXpZhvvQ.cjs} +0 -0
- /package/dist/{blockchain-network-id.vo-CiVykuqG.js → blockchain-network-id.vo-DNRkuT5B.js} +0 -0
- /package/dist/{blockingWithLatestFrom-Cd8c8EPG.js → blockingWithLatestFrom-C5wp_uA-.js} +0 -0
- /package/dist/{blockingWithLatestFrom-D8kBPBMX.cjs → blockingWithLatestFrom-DY8OPg0w.cjs} +0 -0
- /package/dist/{cardano-network-id-to-network-type-C0ufJOyE.cjs → cardano-network-id-to-network-type-BDTF-HmK.cjs} +0 -0
- /package/dist/{cardano-network-id-to-network-type-BWwzeEtM.js → cardano-network-id-to-network-type-CrZfUcyP.js} +0 -0
- /package/dist/{cardano-tx-id.vo-D3ASTIMd.cjs → cardano-tx-id.vo-BPhV-AmZ.cjs} +0 -0
- /package/dist/{cardano-tx-id.vo-BXLo1b7Q.js → cardano-tx-id.vo-DF9NzZXk.js} +0 -0
- /package/dist/{composite-signer-factory-m5cVs9h_.cjs → composite-signer-factory-BpF1mIYA.cjs} +0 -0
- /package/dist/{composite-signer-factory-CR8YHA1y.js → composite-signer-factory-_46QsNBz.js} +0 -0
- /package/dist/{error-DIZOY2Lw.js → error-BCp9NkPi.js} +0 -0
- /package/dist/{error-Cb3EnD0u.cjs → error-CNIquqT5.cjs} +0 -0
- /package/dist/{esm-browser-MG97cre9.js → esm-browser-C93Q-EOI.js} +0 -0
- /package/dist/{esm-browser-bweqfBlL.cjs → esm-browser-DTc9IK3D.cjs} +0 -0
- /package/dist/{feature-flag-key.vo-RlfHL3hq.cjs → feature-flag-key.vo-BPz-ZAp6.cjs} +0 -0
- /package/dist/{feature-flag-key.vo-DN_-4-sG.js → feature-flag-key.vo-ofrB6ZVQ.js} +0 -0
- /package/dist/{first-state-of-status-unPynOuM.js → first-state-of-status-DQnMT3ya.js} +0 -0
- /package/dist/{first-state-of-status-CbT7hPa4.cjs → first-state-of-status-OROkXcRv.cjs} +0 -0
- /package/dist/{init-zUoRG_EC2.cjs → init-C5CEZ1m0.cjs} +0 -0
- /package/dist/{init-DHaMxHM1.js → init-CKipPE_c.js} +0 -0
- /package/dist/{init-Atisw4x2.cjs → init-DFQQ9dTW.cjs} +0 -0
- /package/dist/{init-829HRYcz.js → init-Es3wHW7D.js} +0 -0
- /package/dist/{init-BgNobbNq.cjs → init-IAk_CkGa.cjs} +0 -0
- /package/dist/{init-atCFur0v.js → init-cqN3uX2j.js} +0 -0
- /package/dist/{is-redux-persist-state-BCFnSm36.cjs → is-redux-persist-state-CTn3WZVh.cjs} +0 -0
- /package/dist/{is-redux-persist-state-yBfq0h_E.js → is-redux-persist-state-Cxcd0_bM.js} +0 -0
- /package/dist/{key-value-store--W3SrMaw.js → key-value-store-8cMzeNyG.js} +0 -0
- /package/dist/{key-value-store-C2Xt5Emp.cjs → key-value-store-DUAepXQf.cjs} +0 -0
- /package/dist/{repo-slice-RxFm0zDZ.js → repo-slice-DwyMhCR1.js} +0 -0
- /package/dist/{repo-slice-7X4AYYVm.cjs → repo-slice-WxetRhMY.cjs} +0 -0
- /package/dist/{reselect-CGoLzEPv.js → reselect-C06X06oX.js} +0 -0
- /package/dist/{reselect-Bd94u0K-.cjs → reselect-CRDqmQY9.cjs} +0 -0
- /package/dist/{serializable-BHdKIgk3.js → serializable-B8GahgE1.js} +0 -0
- /package/dist/{serializable-BxHkoPdk.cjs → serializable-CRdnFaLZ.cjs} +0 -0
- /package/dist/{slice-obCSLm1x.js → slice-B87b52iM.js} +0 -0
- /package/dist/{slice-Bk0Sv05J.js → slice-Be7hj4Iy.js} +0 -0
- /package/dist/{slice-BiRsDKOK.cjs → slice-D11FHQuQ.cjs} +0 -0
- /package/dist/{slice-Dqw1ecum.cjs → slice-FioKeHtA.cjs} +0 -0
- /package/dist/{src-Cqp0B2M_.cjs → src-BTPIvlDI.cjs} +0 -0
- /package/dist/{src-BlxEp6Mm.js → src-vq1wS9sZ.js} +0 -0
- /package/dist/{time.vo-D3wWRw9L.cjs → time.vo-CQ8pSXz2.cjs} +0 -0
- /package/dist/{time.vo-tNheinXm.js → time.vo-DkYYGYp6.js} +0 -0
- /package/dist/{toEmpty-8Zax1MPd.js → toEmpty-CgAl3097.js} +0 -0
- /package/dist/{toEmpty-CYAbupM4.cjs → toEmpty-DAMmNB5r.cjs} +0 -0
- /package/dist/{token-id.vo-DXv5uS9n.cjs → token-id.vo-BxG9TG0O.cjs} +0 -0
- /package/dist/{token-id.vo-Bvwhr3ZE.js → token-id.vo-ClsroxHO.js} +0 -0
- /package/dist/{util-CzeB3hfa.cjs → util-CmdAhheh.cjs} +0 -0
- /package/dist/{util-C8a4fA9p.js → util-DxzlYNS5.js} +0 -0
- /package/dist/{util-selector-BWPH68_l.cjs → util-selector-BcAncU4L.cjs} +0 -0
- /package/dist/{util-selector-BSTsxgne.js → util-selector-CcK-GAdE.js} +0 -0
- /package/dist/{while-active-B0lWy_6l.js → while-active-BpppWuCs.js} +0 -0
- /package/dist/{while-active-Dsowp0Fi.cjs → while-active-Dhc2LwOm.cjs} +0 -0
package/dist/init-DU5yYX5u.js
DELETED
|
@@ -1,2374 +0,0 @@
|
|
|
1
|
-
import "./_polyfill-buffer-patch-yNxN4d5E.js";
|
|
2
|
-
import "./codecs-Dc1Krpbt.js";
|
|
3
|
-
import { n as HexBytes, t as ByteArray } from "./bytes.vo-BB7_4JK6.js";
|
|
4
|
-
import { G as GrantScopeSalt, K as GrantId, U as validateGrantScope, W as GrantSlot, i as granteePoint, r as circuitScope } from "./grant-rules-_xJHAT2M.js";
|
|
5
|
-
import { i as AccAddress, t as createMidnightProviders } from "./providers-CwnT4vEE.js";
|
|
6
|
-
import { a as resumeAccountDeployment, i as deployAccount, o as DeviceEpoch, r as resolveUseCounter, s as UseCounter } from "./gated-call-DjoRPqn1.js";
|
|
7
|
-
import { i as accManifest } from "./artefact-loader-54uK5RAa.js";
|
|
8
|
-
import { n as TokenColor, t as storedGranteeScheme } from "./types-T0cjaR75.js";
|
|
9
|
-
import { A as RecordUnreadableError, C as GrantScopeInvalidError, E as NotAuthorisedError, O as ReadUnavailableError, S as GrantRosterUnsettledError, T as NoAccountError, _ as GrantNotFoundError, b as GrantRevocationPendingError, h as GrantIssueRejectedError, i as AccountNotFoundError, j as RemovalTargetNotFoundError, k as RecordCorruptedError, m as EncryptionKeyMismatchError, n as AccountCreationPendingError, p as DeviceRosterInvalidError, r as AccountExistsError, t as AccountContractMissingError, u as CreationLockMismatchError, w as LastDeviceError, x as GrantRevokedError, y as GrantOriginMissingError } from "./errors-DIJTAsLa.js";
|
|
10
|
-
import { a as isDeviceRegistration, i as isAddressedTo, n as deviceEnvelopes, o as isLaceRecord, r as encodeDevicePublicKey } from "./envelopes-BkH1Qdxk.js";
|
|
11
|
-
import { n as ROTATE_ENC_KEY_CIRCUIT, t as APPEND_INBOX_CIRCUIT } from "./enc-key-circuits-DA2Xs3vZ.js";
|
|
12
|
-
import { t as GRANTEE_WITHDRAWAL_CIRCUITS } from "./grant-circuits-swuzEdUl.js";
|
|
13
|
-
import { i as ledger } from "./acc-module-Cmj8EsIb.js";
|
|
14
|
-
import { i as resolveLiveReaders, t as assertJoiningDevice } from "./device-readers-BWOmz9eD.js";
|
|
15
|
-
import { a as prepareIssueGrant, i as originHash, n as grantObjectCommit, o as prepareRevokeAllGrants, s as prepareRevokeGrant, t as deriveGrantId } from "./grants-BIs6qm3b.js";
|
|
16
|
-
import { a as readAccountLedger, c as createIndexerClient, i as queryAccountOperations, n as isNotAuthorisedAbort, o as submitAccountCall, r as queryAccountLedger, s as withRefreshedLocalDevice } from "./account-call-D68oF6bh.js";
|
|
17
|
-
import { EMPTY, Observable, catchError, concat, concatMap, defaultIfEmpty, defer, exhaustMap, filter, firstValueFrom, map, merge, mergeMap, of, withLatestFrom } from "rxjs";
|
|
18
|
-
//#region ../../packages/module/passport-account/src/acc/device-circuits.ts
|
|
19
|
-
/**
|
|
20
|
-
* The gated circuits the device flows authorise, named once so the flow
|
|
21
|
-
* that submits a call and the signer that scopes a key session to it
|
|
22
|
-
* agree on the circuit.
|
|
23
|
-
*/
|
|
24
|
-
const ADD_DEVICE_CIRCUIT = "add_device_with_jubjub";
|
|
25
|
-
const REMOVE_DEVICE_CIRCUIT = "remove_device_with_jubjub";
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region ../../packages/module/passport-account/src/acc/pending-deployment.ts
|
|
28
|
-
/** Whether a record's pending creation carries the whole deployment, not only its marker. */
|
|
29
|
-
const isPendingDeployment = (pending) => "authorityKey" in pending;
|
|
30
|
-
/**
|
|
31
|
-
* The refusal of a creation that resumes an unfinished one under another
|
|
32
|
-
* lock choice than `recorded`, the one it was started with, which the
|
|
33
|
-
* message names so a retry can repeat it.
|
|
34
|
-
*/
|
|
35
|
-
const lockChoiceMismatch = (recorded) => new CreationLockMismatchError(`The unfinished Passport account creation was started with lockAccount ${recorded}; resume it with the same lock choice.`);
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region ../../packages/module/passport-account/src/store/envelope.ts
|
|
38
|
-
/** AES-GCM is specified for 96-bit IVs; each seal draws a fresh one. */
|
|
39
|
-
const IV_LENGTH = 12;
|
|
40
|
-
const utf8 = (value) => new TextEncoder().encode(value);
|
|
41
|
-
/** Whether a stored value has the sealed envelope layout. */
|
|
42
|
-
const isSealedRecord = (value) => {
|
|
43
|
-
if (typeof value !== "object" || value === null) return false;
|
|
44
|
-
const candidate = value;
|
|
45
|
-
return candidate.v === 1 && typeof candidate.iv === "string" && typeof candidate.ciphertext === "string";
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Encrypts a record into a {@link SealedRecord} under the given key,
|
|
49
|
-
* marking the envelope pending when asked.
|
|
50
|
-
*/
|
|
51
|
-
const sealRecord = async (key, record, { pending = false } = {}) => {
|
|
52
|
-
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
53
|
-
const ciphertext = await crypto.subtle.encrypt({
|
|
54
|
-
name: "AES-GCM",
|
|
55
|
-
iv
|
|
56
|
-
}, key, utf8(JSON.stringify(record)));
|
|
57
|
-
return {
|
|
58
|
-
v: 1,
|
|
59
|
-
iv: HexBytes.fromByteArray(iv),
|
|
60
|
-
ciphertext: HexBytes.fromByteArray(new Uint8Array(ciphertext)),
|
|
61
|
-
...pending ? { pending: true } : {}
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* Decrypts a {@link SealedRecord} back into the record it sealed. Any
|
|
66
|
-
* failure to open - a tampered ciphertext, a mismatched key, or a
|
|
67
|
-
* malformed envelope - throws RecordCorruptedError.
|
|
68
|
-
*/
|
|
69
|
-
const openRecord = async (key, sealed) => {
|
|
70
|
-
try {
|
|
71
|
-
const plaintext = await crypto.subtle.decrypt({
|
|
72
|
-
name: "AES-GCM",
|
|
73
|
-
iv: ByteArray.fromHex(HexBytes(sealed.iv))
|
|
74
|
-
}, key, ByteArray.fromHex(HexBytes(sealed.ciphertext)));
|
|
75
|
-
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
76
|
-
} catch {
|
|
77
|
-
throw new RecordCorruptedError();
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region ../../packages/module/passport-account/src/store/account-record.ts
|
|
82
|
-
const COLLECTION_ID = "passport-account";
|
|
83
|
-
const ACCOUNT_RECORD_KEY = "account";
|
|
84
|
-
/**
|
|
85
|
-
* An {@link AccountRecordStore} over the platform key-value storage,
|
|
86
|
-
* namespaced under its own collection. With a `keyProvider` the record is
|
|
87
|
-
* sealed at rest and only sealed envelopes are readable. What a sealed
|
|
88
|
-
* record reads as without a `keyProvider` is the caller's choice:
|
|
89
|
-
* 'skip' (the default) reads it as undefined, for the ceremony-free
|
|
90
|
-
* restore peek that cannot open it by design; 'error' raises
|
|
91
|
-
* RecordUnreadableError, for the flows where a missing storage key is a
|
|
92
|
-
* persistence misconfiguration that must not look like an absent
|
|
93
|
-
* account. Without a `keyProvider` (the dev authoriser) the record
|
|
94
|
-
* persists in plaintext, which holds no secret once the account is
|
|
95
|
-
* complete; while a creation is unfinished it holds the contract's
|
|
96
|
-
* maintenance authority key (see {@link PassportAccountRecord}), so the
|
|
97
|
-
* dev authoriser is for development and tests only.
|
|
98
|
-
*/
|
|
99
|
-
const createAccountRecordStore = (createKeyValueStorage, keyProvider, { onSealedWithoutKey = "skip" } = {}) => {
|
|
100
|
-
const storage = createKeyValueStorage({ collectionId: COLLECTION_ID });
|
|
101
|
-
const open = async (keyOf, value) => isSealedRecord(value) ? openRecord(await keyOf(), value) : void 0;
|
|
102
|
-
const stored = () => storage.getValues([ACCOUNT_RECORD_KEY]).pipe(map(([value]) => value), defaultIfEmpty(void 0));
|
|
103
|
-
const isPending = (record) => record.deployment !== void 0;
|
|
104
|
-
const presenceOf = (value) => {
|
|
105
|
-
if (value === void 0) return "absent";
|
|
106
|
-
return (isSealedRecord(value) ? value.pending === true : isPending(value)) ? "pending" : "complete";
|
|
107
|
-
};
|
|
108
|
-
return {
|
|
109
|
-
read: () => keyProvider ? stored().pipe(concatMap(async (value) => open(keyProvider, value))) : stored().pipe(map((value) => {
|
|
110
|
-
if (!isSealedRecord(value)) return value;
|
|
111
|
-
if (onSealedWithoutKey === "error") throw new RecordUnreadableError();
|
|
112
|
-
})),
|
|
113
|
-
write: (record) => keyProvider ? defer(async () => sealRecord(await keyProvider(), record, { pending: isPending(record) })).pipe(mergeMap((sealed) => storage.setValue(ACCOUNT_RECORD_KEY, sealed))) : storage.setValue(ACCOUNT_RECORD_KEY, record),
|
|
114
|
-
presence: () => stored().pipe(map(presenceOf))
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* The promise view of an {@link AccountRecordStore}, for the flows. A
|
|
119
|
-
* storage that completes without emitting (destroyed mid-flow) reads as
|
|
120
|
-
* absent and writes as done, mirroring the stream contract so a flow
|
|
121
|
-
* never hangs on it.
|
|
122
|
-
*/
|
|
123
|
-
const toAccountRecords = (store) => ({
|
|
124
|
-
read: async () => firstValueFrom(store.read(), { defaultValue: void 0 }),
|
|
125
|
-
write: async (record) => firstValueFrom(store.write(record), { defaultValue: void 0 }),
|
|
126
|
-
presence: async () => firstValueFrom(store.presence(), { defaultValue: "absent" })
|
|
127
|
-
});
|
|
128
|
-
//#endregion
|
|
129
|
-
//#region ../../packages/module/passport-account/src/acc/inbox-walk.ts
|
|
130
|
-
const defaultLoadLedger = async () => import("@midnightntwrk/ledger-v9");
|
|
131
|
-
/** The entries the contract holds, in inbox order, the Lace records included. */
|
|
132
|
-
const inboxEntries = (ledgerState) => {
|
|
133
|
-
const entries = [];
|
|
134
|
-
for (let ordinal = 0n; ordinal < ledgerState.inbox_count; ordinal += 1n) if (ledgerState.inbox.member(ordinal)) entries.push(ledgerState.inbox.lookup(ordinal));
|
|
135
|
-
return entries;
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* The commitment the chain carries for a coin the account received,
|
|
139
|
-
* recomputed with the ledger's own output construction: a contract-owned
|
|
140
|
-
* output of this coin towards the account address, which is exactly what
|
|
141
|
-
* a depositor builds and the contract claims. A description the ledger
|
|
142
|
-
* refuses to commit to is no candidate.
|
|
143
|
-
*/
|
|
144
|
-
const toCandidate = ({ ZswapOutput }, address, { coin, entry }) => {
|
|
145
|
-
try {
|
|
146
|
-
return {
|
|
147
|
-
coin,
|
|
148
|
-
commitment: ZswapOutput.newContractOwned({
|
|
149
|
-
type: HexBytes.fromByteArray(coin.color),
|
|
150
|
-
nonce: HexBytes.fromByteArray(coin.nonce),
|
|
151
|
-
value: coin.value
|
|
152
|
-
}, void 0, address).commitment.toLowerCase(),
|
|
153
|
-
entry
|
|
154
|
-
};
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
/**
|
|
160
|
-
* The tree index of every shielded output the chain created for the
|
|
161
|
-
* account, by commitment, read from the ledger events of the transactions
|
|
162
|
-
* that acted on the contract. A deposit is one such action, so its
|
|
163
|
-
* transaction is among them.
|
|
164
|
-
*/
|
|
165
|
-
const receivedOutputs = async (address, indexer) => {
|
|
166
|
-
const actions = await indexer.enumerateContractActions(address);
|
|
167
|
-
const hashes = [...new Set(actions.map((action) => action.txHash))];
|
|
168
|
-
const outputs = await Promise.all(hashes.map(async (hash) => indexer.queryShieldedOutputs(hash)));
|
|
169
|
-
const recipient = address.toLowerCase();
|
|
170
|
-
return new Map(outputs.flat().filter((output) => output.contractAddress === recipient).map((output) => [output.commitment, output.index]));
|
|
171
|
-
};
|
|
172
|
-
/**
|
|
173
|
-
* Walks the account's inbox the way the custody standard prescribes:
|
|
174
|
-
* every entry other than the Lace records, which hold the account keys
|
|
175
|
-
* and the grant bookkeeping rather than coins, is opened with the
|
|
176
|
-
* encryption secrets, each coin that opens is verified by recomputing
|
|
177
|
-
* its commitment and finding it among the outputs the chain created for
|
|
178
|
-
* the account, and the index that output took in the commitment tree is
|
|
179
|
-
* captured with it. Anyone can append any
|
|
180
|
-
* bytes to the inbox, so an entry that does not open, or opens to a coin
|
|
181
|
-
* the chain never created for this account, is skipped as noise and
|
|
182
|
-
* counted, never raised. Two entries describing the same coin, as a
|
|
183
|
-
* re-encryption after a key rotation leaves behind, yield one holding,
|
|
184
|
-
* read from the first entry that described it. An empty inbox, or one
|
|
185
|
-
* with nothing that opens, is answered without loading the ledger or
|
|
186
|
-
* reading the chain's history. A ledger binding that fails to load is a
|
|
187
|
-
* failure of the walk itself, not of any entry, so it is raised rather
|
|
188
|
-
* than reported as an inbox of noise.
|
|
189
|
-
*/
|
|
190
|
-
const walkInbox = async ({ address, ledgerState, indexer, openEntries, loadLedger = defaultLoadLedger }) => {
|
|
191
|
-
const entries = inboxEntries(ledgerState).filter((entry) => !isLaceRecord(entry));
|
|
192
|
-
const nothing = (skippedEntries) => ({
|
|
193
|
-
holdings: [],
|
|
194
|
-
holdingEntries: [],
|
|
195
|
-
skippedEntries
|
|
196
|
-
});
|
|
197
|
-
if (entries.length === 0) return nothing(0);
|
|
198
|
-
const coins = await openEntries(entries);
|
|
199
|
-
if (coins.every((coin) => coin === null)) return nothing(entries.length);
|
|
200
|
-
const ledger = await loadLedger();
|
|
201
|
-
const candidates = coins.map((coin, ordinal) => coin && toCandidate(ledger, address, {
|
|
202
|
-
coin,
|
|
203
|
-
entry: entries[ordinal]
|
|
204
|
-
}));
|
|
205
|
-
if (candidates.every((candidate) => candidate === null)) return nothing(entries.length);
|
|
206
|
-
const outputs = await receivedOutputs(address, indexer);
|
|
207
|
-
const holdings = /* @__PURE__ */ new Map();
|
|
208
|
-
const holdingEntries = /* @__PURE__ */ new Map();
|
|
209
|
-
let skippedEntries = 0;
|
|
210
|
-
for (const candidate of candidates) {
|
|
211
|
-
const index = candidate && outputs.get(candidate.commitment);
|
|
212
|
-
if (!candidate || index === void 0) {
|
|
213
|
-
skippedEntries += 1;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (holdings.has(candidate.commitment)) continue;
|
|
217
|
-
holdings.set(candidate.commitment, {
|
|
218
|
-
color: TokenColor(HexBytes.fromByteArray(candidate.coin.color)),
|
|
219
|
-
value: candidate.coin.value.toString(),
|
|
220
|
-
commitment: candidate.commitment,
|
|
221
|
-
index: Number(index)
|
|
222
|
-
});
|
|
223
|
-
holdingEntries.set(candidate.commitment, candidate.entry);
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
holdings: [...holdings.values()],
|
|
227
|
-
holdingEntries: [...holdingEntries.values()],
|
|
228
|
-
skippedEntries
|
|
229
|
-
};
|
|
230
|
-
};
|
|
231
|
-
//#endregion
|
|
232
|
-
//#region ../../packages/module/passport-account/src/flows/key-session.ts
|
|
233
|
-
/**
|
|
234
|
-
* Runs an operation under a single key ceremony, so a flow that reaches
|
|
235
|
-
* for the device key several times costs one user prompt instead of one
|
|
236
|
-
* per authoriser call. `flow` names the flow for an authoriser that
|
|
237
|
-
* presents it before the ceremony. An authoriser with no interactive
|
|
238
|
-
* ceremony exposes no session, and the operation runs directly. The
|
|
239
|
-
* session function is used detached, so implementations must not rely on
|
|
240
|
-
* `this`.
|
|
241
|
-
*/
|
|
242
|
-
const inKeySession = async (authoriser, flow, operation) => {
|
|
243
|
-
const { withKeySession } = authoriser;
|
|
244
|
-
return withKeySession ? withKeySession(operation, flow) : operation();
|
|
245
|
-
};
|
|
246
|
-
//#endregion
|
|
247
|
-
//#region ../../packages/module/passport-account/src/acc/enc-key-rotation.ts
|
|
248
|
-
/** Prepares `rotate_enc_key_with_jubjub` over the raw 32-byte X25519 key `enc_key` is replaced with. */
|
|
249
|
-
const prepareRotateEncKey = (newKey) => ({
|
|
250
|
-
circuit: ROTATE_ENC_KEY_CIRCUIT,
|
|
251
|
-
args: [newKey]
|
|
252
|
-
});
|
|
253
|
-
/** Prepares `append_inbox_with_jubjub` over one inbox entry: a re-encrypted coin or a Lace record. */
|
|
254
|
-
const prepareAppendInbox = (entry) => ({
|
|
255
|
-
circuit: APPEND_INBOX_CIRCUIT,
|
|
256
|
-
args: [entry]
|
|
257
|
-
});
|
|
258
|
-
//#endregion
|
|
259
|
-
//#region ../../packages/module/passport-account/src/flows/rotation.ts
|
|
260
|
-
/**
|
|
261
|
-
* How long an accepted call is given to show in the indexer's view of the
|
|
262
|
-
* ledger, which can lag the block the call landed in.
|
|
263
|
-
*/
|
|
264
|
-
const LEDGER_POLL_ATTEMPTS = 30;
|
|
265
|
-
const LEDGER_POLL_DELAY_MS = 1e3;
|
|
266
|
-
const sleep$1 = async (ms) => new Promise((resolve) => {
|
|
267
|
-
setTimeout(resolve, ms);
|
|
268
|
-
});
|
|
269
|
-
/**
|
|
270
|
-
* The encryption key's view members, which a rotation and a read grant's
|
|
271
|
-
* issue need: the re-encryption a rotation moves the holdings through
|
|
272
|
-
* and the sealing a grantee receives the secret by.
|
|
273
|
-
*
|
|
274
|
-
* @throws {@link ReadUnavailableError} when the key offers either not,
|
|
275
|
-
* as a key that only opens entries does.
|
|
276
|
-
*/
|
|
277
|
-
const viewCapability = (encryptionKey) => {
|
|
278
|
-
const { reencryptInbox, sealView } = encryptionKey;
|
|
279
|
-
if (!reencryptInbox || !sealView) throw new ReadUnavailableError();
|
|
280
|
-
return {
|
|
281
|
-
reencryptInbox,
|
|
282
|
-
sealView
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
/** Resolves this device's reader and device keys, inside the caller's key session. */
|
|
286
|
-
const readLocalDevice = async ({ passportAuthoriser, passportEncryptionKey, passportNetwork }) => ({
|
|
287
|
-
readerPublicKey: await passportEncryptionKey.readerPublicKey(passportNetwork.networkId),
|
|
288
|
-
devicePublicKey: encodeDevicePublicKey(await passportAuthoriser.devicePublicKey())
|
|
289
|
-
});
|
|
290
|
-
/** Reads the inbox and this device's keyring of the account from its ledger. */
|
|
291
|
-
const readAccountInbox = (account, ledgerState, readerPublicKey) => {
|
|
292
|
-
const entries = inboxEntries(ledgerState);
|
|
293
|
-
return {
|
|
294
|
-
entries,
|
|
295
|
-
keyring: {
|
|
296
|
-
account: AccAddress(account),
|
|
297
|
-
encKey: ledgerState.enc_key,
|
|
298
|
-
envelopes: deviceEnvelopes(entries, readerPublicKey)
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
};
|
|
302
|
-
/**
|
|
303
|
-
* The device entries the ledger holds, as lowercase hex, less `except`:
|
|
304
|
-
* the devices a rotation may seal the new secret to, which a key session
|
|
305
|
-
* names to the user before it opens.
|
|
306
|
-
*
|
|
307
|
-
* @throws {@link DeviceRosterInvalidError} when there are more than
|
|
308
|
-
* {@link MAX_DEVICE_ENTRIES}, which no signer would accept.
|
|
309
|
-
*/
|
|
310
|
-
const liveDeviceEntries = (ledgerState, except) => {
|
|
311
|
-
const entries = [...ledgerState.devices].map((entry) => HexBytes.fromByteArray(entry)).filter((entry) => entry !== except?.toLowerCase());
|
|
312
|
-
if (entries.length > 64) throw new DeviceRosterInvalidError(`The account holds ${entries.length} device entries, more than the 64 a rotation names.`);
|
|
313
|
-
return entries;
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* The ledger once the indexer reflects a call that advanced the auth
|
|
317
|
-
* nonce past `authNonce`. A chain of gated calls must have each bind the
|
|
318
|
-
* nonce the previous one left behind, which the indexer can serve a
|
|
319
|
-
* moment late.
|
|
320
|
-
*
|
|
321
|
-
* @throws {@link GrantRosterUnsettledError} when the indexer never
|
|
322
|
-
* reflects the call within the polling window.
|
|
323
|
-
*/
|
|
324
|
-
const awaitAdvancedLedger = async (indexer, address, authNonce) => {
|
|
325
|
-
for (let attempt = 0; attempt < LEDGER_POLL_ATTEMPTS; attempt++) {
|
|
326
|
-
const ledgerState = await queryAccountLedger(indexer, address);
|
|
327
|
-
if (ledgerState.auth_nonce > authNonce) return ledgerState;
|
|
328
|
-
await sleep$1(LEDGER_POLL_DELAY_MS);
|
|
329
|
-
}
|
|
330
|
-
throw new GrantRosterUnsettledError(`The ledger of ${address} never reflected the accepted call.`);
|
|
331
|
-
};
|
|
332
|
-
/**
|
|
333
|
-
* Starts a chain of gated calls at `start`. Each accepted call reports
|
|
334
|
-
* the counter it advanced this device to, then waits for the indexer to
|
|
335
|
-
* reflect it, so the next call binds the auth nonce it left; the local
|
|
336
|
-
* roster row follows the entry each call leaves it on.
|
|
337
|
-
*/
|
|
338
|
-
const callChain = ({ account, start, indexer, seams, onProgress, onAdvanced }) => {
|
|
339
|
-
let current = start;
|
|
340
|
-
const submit = async (call) => {
|
|
341
|
-
const { advancedCounter, localCommitmentHex } = await submitAccountCall({
|
|
342
|
-
circuit: call.circuit,
|
|
343
|
-
args: call.args,
|
|
344
|
-
account,
|
|
345
|
-
localUseCounter: current.localUseCounter,
|
|
346
|
-
ledgerState: current.ledgerState,
|
|
347
|
-
onStage: onProgress
|
|
348
|
-
}, seams);
|
|
349
|
-
const localUseCounter = advancedCounter.toString();
|
|
350
|
-
await onAdvanced(localUseCounter);
|
|
351
|
-
current = {
|
|
352
|
-
ledgerState: await awaitAdvancedLedger(indexer, account.address, current.ledgerState.auth_nonce),
|
|
353
|
-
localUseCounter,
|
|
354
|
-
devices: withRefreshedLocalDevice(current.devices, localCommitmentHex)
|
|
355
|
-
};
|
|
356
|
-
};
|
|
357
|
-
return {
|
|
358
|
-
submit,
|
|
359
|
-
append: async (stage, entries) => {
|
|
360
|
-
if (entries.length > 0) onProgress(stage);
|
|
361
|
-
for (const entry of entries) await submit(prepareAppendInbox(entry));
|
|
362
|
-
},
|
|
363
|
-
position: () => current
|
|
364
|
-
};
|
|
365
|
-
};
|
|
366
|
-
/** The registrations of an inbox, by hex. */
|
|
367
|
-
const registrationsOf = (entries) => new Set(entries.filter(isDeviceRegistration).map((entry) => HexBytes.fromByteArray(entry)));
|
|
368
|
-
/**
|
|
369
|
-
* The roster a rotation seals to: the registrations the live roster key
|
|
370
|
-
* opens, resolved against the ledger's device set so only a registration
|
|
371
|
-
* whose device key derives a live entry counts, and only one whose entry
|
|
372
|
-
* is among `devices`. A registration naming nobody's live device is
|
|
373
|
-
* passed over, and named devices left without a reader, by a conflict or
|
|
374
|
-
* for want of a registration, are reported dropped, see
|
|
375
|
-
* {@link resolveLiveReaders}; this device, sealed to regardless, is never
|
|
376
|
-
* reported dropped. The registration whose live entry is `target`, the
|
|
377
|
-
* entry a removal takes out, is named so no reader paired with its device
|
|
378
|
-
* is sealed to.
|
|
379
|
-
*/
|
|
380
|
-
const sealedRoster = async ({ account, encryptionKey, networkId, local, ledgerState, inbox, devices, target, localEntry }) => {
|
|
381
|
-
const registered = (await encryptionKey.openInbox(networkId, inbox.keyring, inbox.entries.filter(isDeviceRegistration))).flatMap((opened) => opened?.kind === "device" ? [opened.device] : []);
|
|
382
|
-
const self = HexBytes.fromByteArray(local.readerPublicKey);
|
|
383
|
-
const { readers, dropped, outside } = resolveLiveReaders({
|
|
384
|
-
scope: {
|
|
385
|
-
address: account.address,
|
|
386
|
-
epoch: ledgerState.device_epoch
|
|
387
|
-
},
|
|
388
|
-
registered,
|
|
389
|
-
isLive: (entryHex) => ledgerState.devices.member(ByteArray.fromHex(HexBytes(entryHex))),
|
|
390
|
-
allowed: new Set(devices),
|
|
391
|
-
target
|
|
392
|
-
});
|
|
393
|
-
const removed = outside.find(({ entryHex }) => entryHex === target);
|
|
394
|
-
return {
|
|
395
|
-
readers: readers.filter(({ reader }) => reader.readerPublicKey !== self).map(({ reader }) => reader),
|
|
396
|
-
dropped: dropped.filter((entry) => entry !== localEntry),
|
|
397
|
-
...removed ? { removed: {
|
|
398
|
-
devicePublicKey: removed.reader.devicePublicKey,
|
|
399
|
-
epoch: removed.reader.epoch,
|
|
400
|
-
counter: removed.reader.counter
|
|
401
|
-
} } : {}
|
|
402
|
-
};
|
|
403
|
-
};
|
|
404
|
-
/**
|
|
405
|
-
* Rotates the account encryption key to a fresh random secret and
|
|
406
|
-
* re-encrypts every live holding towards it, as the scoped grants
|
|
407
|
-
* standard requires before a read grant is issued and after one is
|
|
408
|
-
* revoked, and as a device removal requires so the removed device reads
|
|
409
|
-
* nothing newer. The holdings are walked with the keyring of the ledger's
|
|
410
|
-
* `enc_key` from the position the chain is at, a keyring this device's
|
|
411
|
-
* envelopes do not open rejecting with EncryptionKeyMismatchError before
|
|
412
|
-
* anything is submitted. The encryption key draws the new secret and
|
|
413
|
-
* seals it, with the roster key, to this device and to every registered
|
|
414
|
-
* device that is enrolled now and among `devices`, and answers the
|
|
415
|
-
* ciphertexts alone: one envelope per device and, when the roster key is
|
|
416
|
-
* fresh, one registration each. Should any registration the live roster
|
|
417
|
-
* key opens land while the rotation runs, as another device's enrolment
|
|
418
|
-
* does, the rotation refuses with DeviceRosterInvalidError before
|
|
419
|
-
* `enc_key` is replaced, so a device enrolled meanwhile is never left
|
|
420
|
-
* without the new key; registrations that open under no roster key are
|
|
421
|
-
* noise and do not stop it. Devices whose registrations conflict are
|
|
422
|
-
* left out and reported in the result. The holdings are
|
|
423
|
-
* re-encrypted towards the new key, the envelopes and registrations are
|
|
424
|
-
* appended, `enc_key` is replaced through `rotate_enc_key_with_jubjub`,
|
|
425
|
-
* and each re-encrypted holding is appended, one gated call each, all in
|
|
426
|
-
* the key session the caller holds. The envelopes land ahead of the
|
|
427
|
-
* rotation, so no device is left without the key the chain then holds;
|
|
428
|
-
* a rotation interrupted after it leaves holdings under the previous
|
|
429
|
-
* key, which every device still opens from its older envelopes and the
|
|
430
|
-
* next rotation re-encrypts. The previous secret is never derived again
|
|
431
|
-
* by anyone: entries sealed towards it stay readable to whoever held it,
|
|
432
|
-
* and nothing newer is.
|
|
433
|
-
*/
|
|
434
|
-
const rotateEncKey = async ({ account, encryptionKey, view, local, chain, indexer, networkId, onProgress, devices, rotateRoster, target }) => {
|
|
435
|
-
const { ledgerState } = chain.position();
|
|
436
|
-
const inbox = readAccountInbox(account.address, ledgerState, local.readerPublicKey);
|
|
437
|
-
const { entries, keyring } = inbox;
|
|
438
|
-
const { holdingEntries } = await walkInbox({
|
|
439
|
-
address: account.address,
|
|
440
|
-
ledgerState,
|
|
441
|
-
indexer,
|
|
442
|
-
openEntries: async (coinEntries) => (await encryptionKey.openInbox(networkId, keyring, coinEntries)).map((opened) => opened?.kind === "coin" ? opened.coin : null)
|
|
443
|
-
});
|
|
444
|
-
const { readers, dropped, removed } = await sealedRoster({
|
|
445
|
-
account,
|
|
446
|
-
encryptionKey,
|
|
447
|
-
networkId,
|
|
448
|
-
local,
|
|
449
|
-
ledgerState,
|
|
450
|
-
inbox,
|
|
451
|
-
devices,
|
|
452
|
-
target,
|
|
453
|
-
localEntry: chain.position().devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase()
|
|
454
|
-
});
|
|
455
|
-
const keys = await encryptionKey.createKeys(networkId, {
|
|
456
|
-
devicePublicKey: local.devicePublicKey,
|
|
457
|
-
keyring,
|
|
458
|
-
registrations: entries.filter(isDeviceRegistration),
|
|
459
|
-
readers,
|
|
460
|
-
...rotateRoster === void 0 ? {} : { rotateRoster },
|
|
461
|
-
...removed ? { removed } : {}
|
|
462
|
-
});
|
|
463
|
-
const reencrypted = (await view.reencryptInbox(networkId, {
|
|
464
|
-
keyring,
|
|
465
|
-
to: keys.encKey
|
|
466
|
-
}, holdingEntries)).filter((entry) => entry !== null);
|
|
467
|
-
onProgress("rotating");
|
|
468
|
-
for (const entry of [...keys.envelopes, ...keys.registrations]) await chain.submit(prepareAppendInbox(entry));
|
|
469
|
-
const before = registrationsOf(entries);
|
|
470
|
-
const appended = registrationsOf(keys.registrations);
|
|
471
|
-
const landed = inboxEntries(chain.position().ledgerState).filter((entry) => isDeviceRegistration(entry) && !before.has(HexBytes.fromByteArray(entry)) && !appended.has(HexBytes.fromByteArray(entry)));
|
|
472
|
-
if (landed.length > 0 && (await encryptionKey.openInbox(networkId, keyring, landed)).some((opened) => opened?.kind === "device")) throw new DeviceRosterInvalidError(`Registrations landed in the inbox of ${account.address} while its keys rotated; rotate again.`);
|
|
473
|
-
await chain.submit(prepareRotateEncKey(keys.encKey));
|
|
474
|
-
await chain.append("reencrypting", reencrypted);
|
|
475
|
-
return {
|
|
476
|
-
encKey: keys.encKey,
|
|
477
|
-
dropped,
|
|
478
|
-
keyring: {
|
|
479
|
-
account: AccAddress(account.address),
|
|
480
|
-
encKey: keys.encKey,
|
|
481
|
-
envelopes: keys.envelopes.filter((envelope) => isAddressedTo(envelope, local.readerPublicKey))
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
};
|
|
485
|
-
/**
|
|
486
|
-
* Seals the live secret of `keyring` to each read grant's read key, with
|
|
487
|
-
* the grant record that keeps each seal on chain for this device. A
|
|
488
|
-
* grant the key refuses to seal to, an expired key session included, is
|
|
489
|
-
* listed unsealed rather than failing a rotation that already landed, or
|
|
490
|
-
* as revocation pending when the key refuses it with
|
|
491
|
-
* GrantRevocationPendingError.
|
|
492
|
-
*/
|
|
493
|
-
const sealViews = async ({ view, networkId, keyring, account, targets }) => {
|
|
494
|
-
const sealedEncKey = HexBytes.fromByteArray(keyring.encKey);
|
|
495
|
-
const result = {
|
|
496
|
-
sealed: [],
|
|
497
|
-
unsealed: [],
|
|
498
|
-
revocationPending: [],
|
|
499
|
-
entries: []
|
|
500
|
-
};
|
|
501
|
-
for (const target of targets) {
|
|
502
|
-
let refusal;
|
|
503
|
-
const delivery = await view.sealView(networkId, keyring, {
|
|
504
|
-
...target,
|
|
505
|
-
account
|
|
506
|
-
}).catch((error) => {
|
|
507
|
-
refusal = error;
|
|
508
|
-
});
|
|
509
|
-
if (!delivery) {
|
|
510
|
-
(refusal instanceof GrantRevocationPendingError ? result.revocationPending : result.unsealed).push(target.grantId);
|
|
511
|
-
continue;
|
|
512
|
-
}
|
|
513
|
-
result.sealed.push({
|
|
514
|
-
...target,
|
|
515
|
-
seal: HexBytes.fromByteArray(delivery.seal),
|
|
516
|
-
sealedEncKey
|
|
517
|
-
});
|
|
518
|
-
result.entries.push(delivery.entry);
|
|
519
|
-
}
|
|
520
|
-
return result;
|
|
521
|
-
};
|
|
522
|
-
//#endregion
|
|
523
|
-
//#region ../../packages/module/passport-account/src/flows/stored-grants.ts
|
|
524
|
-
/**
|
|
525
|
-
* The slot each stored grant was derived under, keyed by id, as the
|
|
526
|
-
* roster decoder takes them.
|
|
527
|
-
*/
|
|
528
|
-
const storedGrantSlots = (stored) => new Map(Object.entries(stored ?? {}).map(([id, { slot }]) => [GrantId(id), GrantSlot(slot)]));
|
|
529
|
-
/**
|
|
530
|
-
* The opening of each stored grant whose object fields were kept, keyed by
|
|
531
|
-
* id, as the roster decoder attaches them. A read grant whose seal this
|
|
532
|
-
* client holds carries it in the opening's view, with the `enc_key` whose
|
|
533
|
-
* secret it seals.
|
|
534
|
-
*/
|
|
535
|
-
const storedGrantOpenings = (stored) => new Map(Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.object ? [[GrantId(id), {
|
|
536
|
-
scopeSalt: GrantScopeSalt(grant.scopeSalt),
|
|
537
|
-
granteeScheme: storedGranteeScheme(grant),
|
|
538
|
-
granteePublicKey: grant.granteePublicKey,
|
|
539
|
-
origin: grant.origin,
|
|
540
|
-
color: TokenColor(grant.object.color),
|
|
541
|
-
recipient: grant.object.recipient,
|
|
542
|
-
maxCoinValue: grant.object.maxCoinValue,
|
|
543
|
-
...grant.view?.seal === void 0 || grant.view.sealedEncKey === void 0 ? {} : { view: {
|
|
544
|
-
readPublicKey: grant.view.readPublicKey,
|
|
545
|
-
seal: grant.view.seal,
|
|
546
|
-
sealedEncKey: grant.view.sealedEncKey
|
|
547
|
-
} }
|
|
548
|
-
}]] : []));
|
|
549
|
-
/**
|
|
550
|
-
* The stored grants with the view of one grant replaced: its read key
|
|
551
|
-
* and, when given, the seal towards it of the secret of `sealedEncKey`.
|
|
552
|
-
*/
|
|
553
|
-
const withStoredGrantView = (stored, grantId, view) => {
|
|
554
|
-
const grant = stored[grantId];
|
|
555
|
-
if (!grant) return stored;
|
|
556
|
-
return {
|
|
557
|
-
...stored,
|
|
558
|
-
[grantId]: {
|
|
559
|
-
...grant,
|
|
560
|
-
view
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
};
|
|
564
|
-
/**
|
|
565
|
-
* The read grants this client holds the read key of and the chain still
|
|
566
|
-
* lists active, other than `except`: the grants a rotation seals the new
|
|
567
|
-
* secret to again. A read grant issued by another client has no stored
|
|
568
|
-
* key here, so it is not among them.
|
|
569
|
-
*/
|
|
570
|
-
const liveStoredReadGrants = (stored, roster, except) => {
|
|
571
|
-
const active = new Set(roster.filter((record) => record.active && record.scope.read).map(({ id }) => id));
|
|
572
|
-
return Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.view && active.has(GrantId(id)) && id !== except ? [{
|
|
573
|
-
grantId: GrantId(id),
|
|
574
|
-
readPublicKey: grant.view.readPublicKey
|
|
575
|
-
}] : []);
|
|
576
|
-
};
|
|
577
|
-
/**
|
|
578
|
-
* The active read grants this client holds the read key of whose stored
|
|
579
|
-
* seal does not hold the secret of `encKey`, the ledger's current one:
|
|
580
|
-
* their grantees read nothing newer until a rotation seals to them again
|
|
581
|
-
* or they are issued again. A read grant issued by another client has no
|
|
582
|
-
* stored key here, so it is not among them.
|
|
583
|
-
*/
|
|
584
|
-
const unsealedStoredReadGrants = (stored, roster, encKey) => liveStoredReadGrants(stored, roster).filter(({ grantId }) => stored?.[grantId]?.view?.sealedEncKey !== encKey).map(({ grantId }) => grantId);
|
|
585
|
-
/** The stored grants with one more entry, under its grant id. */
|
|
586
|
-
const withStoredGrant = (stored, grantId, grant) => ({
|
|
587
|
-
...stored,
|
|
588
|
-
[grantId]: grant
|
|
589
|
-
});
|
|
590
|
-
/**
|
|
591
|
-
* The record a completed account persists: its identity, this device's
|
|
592
|
-
* use counter, the grant bookkeeping when there is any, and the roster of
|
|
593
|
-
* devices the account keys were sealed to when it is known. It never
|
|
594
|
-
* carries a deployment, so a write of it completes a pending record.
|
|
595
|
-
*/
|
|
596
|
-
const completedRecord = ({ address, bindingVersion }, localUseCounter, { grants }) => ({
|
|
597
|
-
address,
|
|
598
|
-
bindingVersion,
|
|
599
|
-
localUseCounter,
|
|
600
|
-
...grants ? { grants } : {}
|
|
601
|
-
});
|
|
602
|
-
/**
|
|
603
|
-
* The stored grants with the seals of a rotation applied: each sealed
|
|
604
|
-
* grant's view carries its new seal and the `enc_key` it holds, and each
|
|
605
|
-
* unsealed one keeps its read key alone.
|
|
606
|
-
*/
|
|
607
|
-
const withSealedViews = (stored, { sealed, unsealed }) => {
|
|
608
|
-
if (!stored) return stored;
|
|
609
|
-
let next = stored;
|
|
610
|
-
for (const { grantId, readPublicKey, seal, sealedEncKey } of sealed) next = withStoredGrantView(next, grantId, {
|
|
611
|
-
readPublicKey,
|
|
612
|
-
seal,
|
|
613
|
-
sealedEncKey
|
|
614
|
-
});
|
|
615
|
-
for (const grantId of unsealed) {
|
|
616
|
-
const readPublicKey = next[grantId]?.view?.readPublicKey;
|
|
617
|
-
if (readPublicKey !== void 0) next = withStoredGrantView(next, grantId, { readPublicKey });
|
|
618
|
-
}
|
|
619
|
-
return next;
|
|
620
|
-
};
|
|
621
|
-
/** Each grant's parts by id, the later of two parts of a kind winning. */
|
|
622
|
-
const partsByGrant = (parts) => {
|
|
623
|
-
const byGrant = /* @__PURE__ */ new Map();
|
|
624
|
-
for (const part of parts) {
|
|
625
|
-
const held = byGrant.get(part.grantId) ?? {};
|
|
626
|
-
byGrant.set(part.grantId, {
|
|
627
|
-
...held,
|
|
628
|
-
[part.part]: part
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
return byGrant;
|
|
632
|
-
};
|
|
633
|
-
/**
|
|
634
|
-
* Whether a grant's bookkeeping opens its on-chain commitments under the
|
|
635
|
-
* account at `address`: the id derives from the account, the grantee
|
|
636
|
-
* key under its scheme, the origin and the slot, the object commitment
|
|
637
|
-
* from the salt and the object fields, and the read key hashes to the
|
|
638
|
-
* one the contract holds, or is absent for a grant that does not read.
|
|
639
|
-
* Anyone can append any bytes to the inbox, and another client of the
|
|
640
|
-
* account issuing to the same grantee from the same origin lands on the
|
|
641
|
-
* same id with its own salt, so this check, not where the bookkeeping
|
|
642
|
-
* came from, is what makes it trusted.
|
|
643
|
-
*/
|
|
644
|
-
const opensOnChain = async (record, fields, address) => {
|
|
645
|
-
try {
|
|
646
|
-
const grantee = {
|
|
647
|
-
scheme: fields.granteeScheme,
|
|
648
|
-
publicKey: fields.granteePublicKey
|
|
649
|
-
};
|
|
650
|
-
const grantId = deriveGrantId({
|
|
651
|
-
contractAddress: ByteArray.fromHex(HexBytes(address)),
|
|
652
|
-
granteePublicKey: granteePoint(grantee),
|
|
653
|
-
originHash: await originHash(fields.origin),
|
|
654
|
-
slot: GrantSlot(fields.slot)
|
|
655
|
-
});
|
|
656
|
-
const encoded = circuitScope({
|
|
657
|
-
grantee,
|
|
658
|
-
operations: [],
|
|
659
|
-
color: TokenColor(fields.object.color),
|
|
660
|
-
recipient: fields.object.recipient,
|
|
661
|
-
maxCoinValue: fields.object.maxCoinValue,
|
|
662
|
-
perCallCap: "0",
|
|
663
|
-
cap: "0",
|
|
664
|
-
expiresAt: null,
|
|
665
|
-
...fields.readPublicKey === void 0 ? {} : { readPublicKey: fields.readPublicKey }
|
|
666
|
-
});
|
|
667
|
-
return grantId === record.id && grantObjectCommit(ByteArray.fromHex(HexBytes(fields.scopeSalt)), encoded) === record.scope.objectCommit && HexBytes.fromByteArray(encoded.readPkHash) === record.scope.readPkHash && fields.readPublicKey !== void 0 === record.scope.read;
|
|
668
|
-
} catch {
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
/**
|
|
673
|
-
* The stored grants narrowed to the ones that still open what the chain
|
|
674
|
-
* holds under their id, or undefined once none is left. A grant the
|
|
675
|
-
* chain no longer holds is dropped, and so is one whose kept fields do
|
|
676
|
-
* not open its commitments: the entry an issue wrote before a call that
|
|
677
|
-
* never landed, once another client of the account issued over the same
|
|
678
|
-
* id, or an entry written before the object fields were kept, which
|
|
679
|
-
* cannot be checked. A revoked grant stays in the contract's map as a
|
|
680
|
-
* tombstone, so its slot and salt are kept until a revoke-all clears the
|
|
681
|
-
* map or a new grant overwrites the id.
|
|
682
|
-
*/
|
|
683
|
-
const retainStoredGrants = async (stored, roster, address) => {
|
|
684
|
-
if (!stored) return void 0;
|
|
685
|
-
const retained = {};
|
|
686
|
-
for (const record of roster) {
|
|
687
|
-
const grant = stored[record.id];
|
|
688
|
-
if (!grant?.object) continue;
|
|
689
|
-
if (await opensOnChain(record, {
|
|
690
|
-
...grant,
|
|
691
|
-
granteeScheme: storedGranteeScheme(grant),
|
|
692
|
-
object: grant.object,
|
|
693
|
-
...grant.view === void 0 ? {} : { readPublicKey: grant.view.readPublicKey }
|
|
694
|
-
}, address)) retained[record.id] = grant;
|
|
695
|
-
}
|
|
696
|
-
return Object.keys(retained).length > 0 ? retained : void 0;
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
|
-
* The bookkeeping of grants this device issued, rebuilt from the grant
|
|
700
|
-
* records it opened in the inbox, for the grants the chain still holds:
|
|
701
|
-
* each grant whose parts open its on-chain commitments under this
|
|
702
|
-
* account and origin, with its read key and, when the newest view record
|
|
703
|
-
* holds the seal of the ledger's `enc_key`, that seal. A grant whose
|
|
704
|
-
* parts do not check out, or whose grant or object part is missing, is
|
|
705
|
-
* left out. Undefined when nothing is rebuilt.
|
|
706
|
-
*/
|
|
707
|
-
const rebuildStoredGrants = async (parts, roster, scope) => {
|
|
708
|
-
const byGrant = partsByGrant(parts);
|
|
709
|
-
const rebuilt = {};
|
|
710
|
-
for (const record of roster) {
|
|
711
|
-
const held = byGrant.get(record.id);
|
|
712
|
-
if (!held?.grant || !held.object) continue;
|
|
713
|
-
const { grant, object, view } = held;
|
|
714
|
-
if (!await opensOnChain(record, {
|
|
715
|
-
...grant,
|
|
716
|
-
origin: scope.origin,
|
|
717
|
-
object: object.object
|
|
718
|
-
}, scope.address)) continue;
|
|
719
|
-
const readPublicKey = grant.readPublicKey;
|
|
720
|
-
const isCurrent = view !== void 0 && scope.encKey.startsWith(view.encKeyPrefix);
|
|
721
|
-
rebuilt[record.id] = {
|
|
722
|
-
slot: grant.slot,
|
|
723
|
-
scopeSalt: grant.scopeSalt,
|
|
724
|
-
granteeScheme: grant.granteeScheme,
|
|
725
|
-
granteePublicKey: grant.granteePublicKey,
|
|
726
|
-
origin: scope.origin,
|
|
727
|
-
object: object.object,
|
|
728
|
-
...readPublicKey === void 0 ? {} : { view: isCurrent ? {
|
|
729
|
-
readPublicKey,
|
|
730
|
-
seal: view.seal,
|
|
731
|
-
sealedEncKey: scope.encKey
|
|
732
|
-
} : { readPublicKey } }
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
return Object.keys(rebuilt).length > 0 ? rebuilt : void 0;
|
|
736
|
-
};
|
|
737
|
-
/**
|
|
738
|
-
* The record's bookkeeping with every rebuilt grant it lacks added: what
|
|
739
|
-
* the record holds is kept, so a record that survived is never
|
|
740
|
-
* overwritten by the chain's copy, except for a view whose seal is not of
|
|
741
|
-
* `encKey`, the ledger's current one, when the rebuilt view's seal is:
|
|
742
|
-
* the record write after a rotation can be lost while its seal reached
|
|
743
|
-
* the inbox. Run over the bookkeeping {@link retainStoredGrants} checked,
|
|
744
|
-
* a rebuilt grant takes the place of an entry that did not open.
|
|
745
|
-
*/
|
|
746
|
-
const withRebuiltGrants = (stored, rebuilt, encKey) => {
|
|
747
|
-
if (!rebuilt) return stored;
|
|
748
|
-
const merged = {
|
|
749
|
-
...rebuilt,
|
|
750
|
-
...stored
|
|
751
|
-
};
|
|
752
|
-
for (const [id, fromChain] of Object.entries(rebuilt)) {
|
|
753
|
-
const held = stored?.[id];
|
|
754
|
-
if (held && held.view?.sealedEncKey !== encKey && fromChain.view?.sealedEncKey === encKey) merged[id] = {
|
|
755
|
-
...held,
|
|
756
|
-
view: fromChain.view
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
return merged;
|
|
760
|
-
};
|
|
761
|
-
//#endregion
|
|
762
|
-
//#region ../../packages/module/passport-account/src/flows/create-account.ts
|
|
763
|
-
/** The initial device entry is inserted at epoch 0 with use counter 0. */
|
|
764
|
-
const INITIAL_DEVICE_EPOCH = DeviceEpoch(0n);
|
|
765
|
-
const INITIAL_USE_COUNTER_VALUE = "0";
|
|
766
|
-
/**
|
|
767
|
-
* The record of a creation left unfinished on this device, read inside
|
|
768
|
-
* the session once presence reported one. A record that opens without a
|
|
769
|
-
* pending deployment, or that this authoriser cannot open, refuses with
|
|
770
|
-
* AccountExistsError: it names an account that must not be created over.
|
|
771
|
-
* A record that carries only the marker of its creation, which a hosted
|
|
772
|
-
* signer shows no flow but the creation, cannot be resumed and is
|
|
773
|
-
* refused, as is one started under another lock choice than
|
|
774
|
-
* `lockAccount`, with CreationLockMismatchError naming the recorded one.
|
|
775
|
-
*/
|
|
776
|
-
const pendingRecord = async (accountRecords, lockAccount) => {
|
|
777
|
-
const record = await accountRecords.read();
|
|
778
|
-
if (!record?.deployment) throw new AccountExistsError();
|
|
779
|
-
if (!isPendingDeployment(record.deployment)) throw new Error(`The unfinished creation of the account at ${record.address} was read without its deployment, so it cannot be resumed.`);
|
|
780
|
-
if (record.deployment.lockAccount !== lockAccount) throw lockChoiceMismatch(record.deployment.lockAccount);
|
|
781
|
-
return {
|
|
782
|
-
...record,
|
|
783
|
-
deployment: record.deployment
|
|
784
|
-
};
|
|
785
|
-
};
|
|
786
|
-
/**
|
|
787
|
-
* Runs the deployment: a fresh one, which draws the account keys first,
|
|
788
|
-
* deploys with their `enc_key` and records the pending deployment, with
|
|
789
|
-
* this device's envelope and registration, as soon as the deploy lands
|
|
790
|
-
* so an interruption can be resumed; or the remaining waves of a
|
|
791
|
-
* recorded one, with the key entries it recorded.
|
|
792
|
-
*/
|
|
793
|
-
const deploy = async ({ providers, lockAccount, pending, local, context: { seams, accountRecords } }) => {
|
|
794
|
-
const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
|
|
795
|
-
if (pending) return {
|
|
796
|
-
deployed: await resumeAccountDeployment({
|
|
797
|
-
providers,
|
|
798
|
-
authoriser: passportAuthoriser,
|
|
799
|
-
address: pending.address,
|
|
800
|
-
pending: pending.deployment
|
|
801
|
-
}),
|
|
802
|
-
pending: pending.deployment
|
|
803
|
-
};
|
|
804
|
-
const keys = await passportEncryptionKey.createKeys(passportNetwork.networkId, { devicePublicKey: local.devicePublicKey });
|
|
805
|
-
const keyEntries = [...keys.envelopes, ...keys.registrations].map((entry) => HexBytes.fromByteArray(entry));
|
|
806
|
-
const progress = {};
|
|
807
|
-
const deployed = await deployAccount({
|
|
808
|
-
providers,
|
|
809
|
-
authoriser: passportAuthoriser,
|
|
810
|
-
encPublicKey: keys.encKey,
|
|
811
|
-
lockAccount,
|
|
812
|
-
onDeployed: async (address, deployment) => {
|
|
813
|
-
progress.recorded = {
|
|
814
|
-
...deployment,
|
|
815
|
-
keyEntries
|
|
816
|
-
};
|
|
817
|
-
await accountRecords.write({
|
|
818
|
-
address,
|
|
819
|
-
bindingVersion: accManifest.bindingVersion,
|
|
820
|
-
localUseCounter: INITIAL_USE_COUNTER_VALUE,
|
|
821
|
-
deployment: progress.recorded
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
if (!progress.recorded) throw new Error("The deployment landed without reporting its progress.");
|
|
826
|
-
return {
|
|
827
|
-
deployed,
|
|
828
|
-
pending: progress.recorded
|
|
829
|
-
};
|
|
830
|
-
};
|
|
831
|
-
/**
|
|
832
|
-
* Creates a Passport account: the passkey ceremony (implicit in the first
|
|
833
|
-
* authoriser use), the account keys drawn through the encryption key
|
|
834
|
-
* seam, the Account Custody Contract deployment in waves with sponsored
|
|
835
|
-
* fees, the initial device activation, the delivery of the account keys
|
|
836
|
-
* to this device's inbox, and the local persistence of the account
|
|
837
|
-
* record. Every step runs inside one key session, so the whole flow costs
|
|
838
|
-
* a single ceremony rather than one per authoriser call. The flow never
|
|
839
|
-
* holds the encryption secret: the seam draws it at random, seals it to
|
|
840
|
-
* this device's reader key and hands back only its public key, which the
|
|
841
|
-
* constructor takes as `enc_key`, with this device's envelope and its
|
|
842
|
-
* registration under a fresh roster key; once the device is active, both
|
|
843
|
-
* are appended to the inbox, one gated call each, so the account keys
|
|
844
|
-
* live on chain from the start.
|
|
845
|
-
*
|
|
846
|
-
* Progress is reported through `onProgress`: 'deploying' once the
|
|
847
|
-
* authoriser has produced the device key, 'proving' and 'sponsoring' from
|
|
848
|
-
* inside each transaction submission (a transaction proves first, then
|
|
849
|
-
* the sponsor balances it), 'activating' before the activation call and
|
|
850
|
-
* 'recording' before the key entries are appended. The record is written
|
|
851
|
-
* before the promise resolves, so a process that dies right after still
|
|
852
|
-
* recognises the account on next startup.
|
|
853
|
-
*
|
|
854
|
-
* The flow runs to completion once started: cancelling mid-deploy could
|
|
855
|
-
* orphan a contract the sponsor already paid for. The caller's flow gate
|
|
856
|
-
* is what keeps a second request from starting one. Should the process
|
|
857
|
-
* die between waves anyway, the record written when the deploy landed
|
|
858
|
-
* names the contract, what it still needs and the key entries, and the
|
|
859
|
-
* next create request on this device resumes from the operations the
|
|
860
|
-
* chain holds, activates once every wave is in and appends whichever key
|
|
861
|
-
* entry the inbox still lacks; a creation that died after its activation
|
|
862
|
-
* landed resumes from there. A resumed creation finishes exactly as it
|
|
863
|
-
* began, with the keys recorded then: a request under another lock choice
|
|
864
|
-
* than the recorded one is refused with CreationLockMismatchError, and a
|
|
865
|
-
* build pinning other verifier keys than the recorded deployment with
|
|
866
|
-
* CreationArtefactMismatchError, both before any wave. The session names
|
|
867
|
-
* the lock choice, so an authoriser that asks consent first shows it; a
|
|
868
|
-
* hosted signer refuses the same lock mismatch itself, and a resume
|
|
869
|
-
* started from another origin than the creation it resumes.
|
|
870
|
-
*
|
|
871
|
-
* A device already holding a completed account record refuses with
|
|
872
|
-
* AccountExistsError before the ceremony runs: the record is the only
|
|
873
|
-
* pointer to the deployed contract and there is no recovery path, so
|
|
874
|
-
* creating over it would orphan the existing account. Presence is read
|
|
875
|
-
* without opening the record, and it tells a completed record from an
|
|
876
|
-
* unfinished one, so a sealed record blocks the flow without costing a
|
|
877
|
-
* prompt and only a record the flow may resume is opened, inside the
|
|
878
|
-
* session, where it costs no extra prompt.
|
|
879
|
-
*/
|
|
880
|
-
const createAccount = async ({ lockAccount }, context) => {
|
|
881
|
-
const { seams, accountRecords, onProgress } = context;
|
|
882
|
-
const presence = await accountRecords.presence();
|
|
883
|
-
if (presence === "complete") throw new AccountExistsError();
|
|
884
|
-
const { passportAuthoriser, passportNetwork, passportProver, passportSponsor } = seams;
|
|
885
|
-
return inKeySession(passportAuthoriser, {
|
|
886
|
-
kind: "create-account",
|
|
887
|
-
lockAccount
|
|
888
|
-
}, async () => {
|
|
889
|
-
const local = await readLocalDevice(seams);
|
|
890
|
-
const pending = presence === "pending" ? await pendingRecord(accountRecords, lockAccount) : void 0;
|
|
891
|
-
onProgress("deploying");
|
|
892
|
-
const { deployed, pending: deployment } = await deploy({
|
|
893
|
-
providers: await createMidnightProviders({
|
|
894
|
-
network: passportNetwork,
|
|
895
|
-
prover: passportProver,
|
|
896
|
-
sponsor: passportSponsor,
|
|
897
|
-
onStage: onProgress
|
|
898
|
-
}),
|
|
899
|
-
lockAccount,
|
|
900
|
-
pending,
|
|
901
|
-
local,
|
|
902
|
-
context
|
|
903
|
-
});
|
|
904
|
-
if (!deployed.isActivated) {
|
|
905
|
-
onProgress("activating");
|
|
906
|
-
await deployed.activate();
|
|
907
|
-
}
|
|
908
|
-
const account = {
|
|
909
|
-
address: deployed.address,
|
|
910
|
-
bindingVersion: accManifest.bindingVersion,
|
|
911
|
-
status: "ready",
|
|
912
|
-
networkId: passportNetwork.networkId
|
|
913
|
-
};
|
|
914
|
-
const startCounter = pending?.localUseCounter ?? INITIAL_USE_COUNTER_VALUE;
|
|
915
|
-
const { indexer, ledgerState } = await readAccountLedger(deployed.address, passportNetwork);
|
|
916
|
-
const held = new Set(inboxEntries(ledgerState).map((entry) => HexBytes.fromByteArray(entry)));
|
|
917
|
-
const chain = callChain({
|
|
918
|
-
account,
|
|
919
|
-
start: {
|
|
920
|
-
ledgerState,
|
|
921
|
-
localUseCounter: startCounter,
|
|
922
|
-
devices: [{
|
|
923
|
-
commitmentHex: await passportAuthoriser.deviceCommitment(AccAddress(deployed.address), INITIAL_DEVICE_EPOCH, UseCounter(BigInt(startCounter))),
|
|
924
|
-
isLocal: true
|
|
925
|
-
}]
|
|
926
|
-
},
|
|
927
|
-
indexer,
|
|
928
|
-
seams,
|
|
929
|
-
onProgress,
|
|
930
|
-
onAdvanced: async (localUseCounter) => accountRecords.write({
|
|
931
|
-
address: account.address,
|
|
932
|
-
bindingVersion: account.bindingVersion,
|
|
933
|
-
localUseCounter,
|
|
934
|
-
deployment
|
|
935
|
-
})
|
|
936
|
-
});
|
|
937
|
-
await chain.append("recording", deployment.keyEntries.filter((entry) => !held.has(entry)).map((entry) => ByteArray.fromHex(HexBytes(entry))));
|
|
938
|
-
const { localUseCounter, devices } = chain.position();
|
|
939
|
-
const counter = localUseCounter ?? startCounter;
|
|
940
|
-
await accountRecords.write(completedRecord(account, counter, {}));
|
|
941
|
-
return {
|
|
942
|
-
account,
|
|
943
|
-
devices,
|
|
944
|
-
localUseCounter: counter,
|
|
945
|
-
grants: [],
|
|
946
|
-
encKey: HexBytes.fromByteArray(ledgerState.enc_key)
|
|
947
|
-
};
|
|
948
|
-
});
|
|
949
|
-
};
|
|
950
|
-
//#endregion
|
|
951
|
-
//#region ../../packages/module/passport-account/src/acc/read-grants.ts
|
|
952
|
-
const NO_SLOTS = /* @__PURE__ */ new Map();
|
|
953
|
-
const NO_OPENINGS = /* @__PURE__ */ new Map();
|
|
954
|
-
/** The slot reported for a grant whose slot this client never learned. */
|
|
955
|
-
const UNKNOWN_SLOT = GrantSlot(0);
|
|
956
|
-
const toNumber = (field, value) => {
|
|
957
|
-
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`Grant field ${field} exceeds the safe integer range`);
|
|
958
|
-
return Number(value);
|
|
959
|
-
};
|
|
960
|
-
const toOperations = (scope) => {
|
|
961
|
-
const operations = [];
|
|
962
|
-
if (scope.op_withdraw_unshielded) operations.push("withdraw-unshielded");
|
|
963
|
-
if (scope.op_withdraw_shielded) operations.push("withdraw-shielded");
|
|
964
|
-
if (scope.op_withdraw_shielded_to_contract) operations.push("withdraw-shielded-to-contract");
|
|
965
|
-
return operations;
|
|
966
|
-
};
|
|
967
|
-
const toScope = (scope) => ({
|
|
968
|
-
operations: toOperations(scope),
|
|
969
|
-
read: scope.read,
|
|
970
|
-
objectCommit: HexBytes.fromByteArray(scope.object_commit),
|
|
971
|
-
perCallCap: scope.per_call_cap.toString(),
|
|
972
|
-
cap: scope.cap.toString(),
|
|
973
|
-
expiresAt: scope.expires_at === 0n ? null : toNumber("expires_at", scope.expires_at),
|
|
974
|
-
rpCommit: HexBytes.fromByteArray(scope.rp_commit),
|
|
975
|
-
readPkHash: HexBytes.fromByteArray(scope.read_pk_hash),
|
|
976
|
-
window: scope.window_len === 0n ? null : {
|
|
977
|
-
lengthSeconds: toNumber("window_len", scope.window_len),
|
|
978
|
-
cap: scope.window_cap.toString()
|
|
979
|
-
},
|
|
980
|
-
callerCommit: HexBytes.fromByteArray(scope.caller_commit)
|
|
981
|
-
});
|
|
982
|
-
const toRecord = (id, record, { slot, opening }) => ({
|
|
983
|
-
id,
|
|
984
|
-
slot,
|
|
985
|
-
active: record.active,
|
|
986
|
-
epoch: toNumber("epoch", record.epoch),
|
|
987
|
-
generation: toNumber("gen", record.gen),
|
|
988
|
-
issuedAt: toNumber("issued_at", record.issued_at),
|
|
989
|
-
scope: toScope(record.scope),
|
|
990
|
-
spentWindow: {
|
|
991
|
-
start: toNumber("window_start", record.window_start),
|
|
992
|
-
spent: record.window_spent.toString()
|
|
993
|
-
},
|
|
994
|
-
...opening ? { opening } : {}
|
|
995
|
-
});
|
|
996
|
-
/**
|
|
997
|
-
* Decodes the account's grant roster from the ledger's grants map, in the
|
|
998
|
-
* map's order. Plain fields decode to the read model's numbers and
|
|
999
|
-
* decimal strings, commitments stay as hex, and an expiry or window of
|
|
1000
|
-
* zero reads as none. A revoked grant stays in the map as an inactive
|
|
1001
|
-
* tombstone and is listed as such. The contract stores no slot: the
|
|
1002
|
-
* caller supplies the slots of the grants it issued, and a grant issued by
|
|
1003
|
-
* another client of the account reports slot 0. The openings the caller
|
|
1004
|
-
* holds are attached to their grants.
|
|
1005
|
-
*/
|
|
1006
|
-
const readGrants = (ledgerState, slots = NO_SLOTS, openings = NO_OPENINGS) => Array.from(ledgerState.grants, ([rawId, record]) => {
|
|
1007
|
-
const id = GrantId(HexBytes.fromByteArray(rawId));
|
|
1008
|
-
return toRecord(id, record, {
|
|
1009
|
-
slot: slots.get(id) ?? UNKNOWN_SLOT,
|
|
1010
|
-
opening: openings.get(id)
|
|
1011
|
-
});
|
|
1012
|
-
});
|
|
1013
|
-
//#endregion
|
|
1014
|
-
//#region ../../packages/module/passport-account/src/flows/devices.ts
|
|
1015
|
-
/**
|
|
1016
|
-
* Contract aborts meaning the removal would leave the account without an
|
|
1017
|
-
* authorising device: the caller rule and the redundant count floor.
|
|
1018
|
-
*/
|
|
1019
|
-
const LAST_DEVICE_ABORTS = ["failed assert: cannot remove the authorising device", "failed assert: cannot remove last device"];
|
|
1020
|
-
/**
|
|
1021
|
-
* Types a raw contract abort by the assert message the Account Custody
|
|
1022
|
-
* Contract raises; anything unrecognised passes through unchanged.
|
|
1023
|
-
* An unknown-entry abort on the remove circuit is attributed to the
|
|
1024
|
-
* removal target, not the caller: the circuit checks the caller first,
|
|
1025
|
-
* and the caller's entry was resolved against live ledger state inside
|
|
1026
|
-
* the same gate, so only a mid-flight roll of the caller's own entry
|
|
1027
|
-
* could produce it, while a stale target commitment always does.
|
|
1028
|
-
*/
|
|
1029
|
-
const classifyContractAbort$1 = (error, circuit) => {
|
|
1030
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1031
|
-
if (LAST_DEVICE_ABORTS.some((abort) => message.includes(abort))) return new LastDeviceError(message);
|
|
1032
|
-
if (message.includes("failed assert: unknown device entry")) return circuit === "remove_device_with_jubjub" ? new RemovalTargetNotFoundError(message) : new NotAuthorisedError(message);
|
|
1033
|
-
if (isNotAuthorisedAbort(message)) return new NotAuthorisedError(message);
|
|
1034
|
-
return error;
|
|
1035
|
-
};
|
|
1036
|
-
/**
|
|
1037
|
-
* Whether any read grant of the account is live: its grantees hold the
|
|
1038
|
-
* live account secret, so a device handed the keys while one is live
|
|
1039
|
-
* gets a fresh secret first.
|
|
1040
|
-
*/
|
|
1041
|
-
const hasLiveReadGrant = (ledgerState) => readGrants(ledgerState).some((record) => record.active && record.scope.read);
|
|
1042
|
-
/** The devices list with the enrolled commitment appended. */
|
|
1043
|
-
const withDevice = (devices, commitmentHex) => [...devices, {
|
|
1044
|
-
commitmentHex,
|
|
1045
|
-
isLocal: false
|
|
1046
|
-
}];
|
|
1047
|
-
/** The devices list with the revoked commitment dropped. */
|
|
1048
|
-
const withoutDevice = (devices, commitmentHex) => devices.filter((device) => device.commitmentHex !== commitmentHex);
|
|
1049
|
-
/** A device's entry as the call that targets it takes it. */
|
|
1050
|
-
const entryArgument = (commitmentHex) => ByteArray.fromHex(HexBytes(commitmentHex.toLowerCase()));
|
|
1051
|
-
/** The counter the chain left this device at, or the one it started from. */
|
|
1052
|
-
const chainCounter = (chain, input) => chain.position().localUseCounter ?? input.localUseCounter ?? "0";
|
|
1053
|
-
/**
|
|
1054
|
-
* Opens what a device flow runs on inside its key session: the grant
|
|
1055
|
-
* bookkeeping of the record, read where it costs no extra prompt so every
|
|
1056
|
-
* write keeps it, and a chain of gated calls from the ledger read ahead
|
|
1057
|
-
* of the session, each persisting the counter it advanced this device to
|
|
1058
|
-
* before the next call. The bookkeeping is checked against the ledger
|
|
1059
|
-
* first, so an entry that does not open the grant the chain holds under
|
|
1060
|
-
* its id, as one an issue wrote before a call that never landed, is
|
|
1061
|
-
* neither re-sealed to nor kept.
|
|
1062
|
-
*/
|
|
1063
|
-
const openDeviceSession = async (input, { ledgerState, indexer }, { seams, accountRecords, onProgress }) => {
|
|
1064
|
-
const { account, devices, localUseCounter } = input;
|
|
1065
|
-
let grants = await retainStoredGrants((await accountRecords.read())?.grants, readGrants(ledgerState), account.address);
|
|
1066
|
-
const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants }));
|
|
1067
|
-
const chain = callChain({
|
|
1068
|
-
account,
|
|
1069
|
-
start: {
|
|
1070
|
-
ledgerState,
|
|
1071
|
-
localUseCounter,
|
|
1072
|
-
devices
|
|
1073
|
-
},
|
|
1074
|
-
indexer,
|
|
1075
|
-
seams,
|
|
1076
|
-
onProgress,
|
|
1077
|
-
onAdvanced: write
|
|
1078
|
-
});
|
|
1079
|
-
return {
|
|
1080
|
-
grants,
|
|
1081
|
-
chain,
|
|
1082
|
-
keep: async (next) => {
|
|
1083
|
-
grants = next;
|
|
1084
|
-
await write(chainCounter(chain, input));
|
|
1085
|
-
}
|
|
1086
|
-
};
|
|
1087
|
-
};
|
|
1088
|
-
/**
|
|
1089
|
-
* Enrols a new device key on the account. `commitmentHex` carries the new
|
|
1090
|
-
* device's entry commitment, derived out of band by the joining device;
|
|
1091
|
-
* the local device authorises `add_device_with_jubjub` through the
|
|
1092
|
-
* gated-call pattern. The roster gains the commitment on success only,
|
|
1093
|
-
* marked as remote, and the same result moves the local device's own row
|
|
1094
|
-
* to the successor entry the call left it on, so the roster and the use
|
|
1095
|
-
* counter never disagree about where this device stands.
|
|
1096
|
-
*
|
|
1097
|
-
* When `reader` names the joining device's reader and device keys, the
|
|
1098
|
-
* account keys are delivered to it in the same session: its device key
|
|
1099
|
-
* must derive the enrolled commitment at use counter 0 and its reader key
|
|
1100
|
-
* must be one the account seals to, both checked before any prompt. The
|
|
1101
|
-
* encryption key seals the live secret and the roster key to the reader
|
|
1102
|
-
* key, and the envelope and the device's registration under the roster
|
|
1103
|
-
* key are appended to the inbox, one gated call each, ahead of the
|
|
1104
|
-
* enrolment, so an enrolment that lands never leaves the device without
|
|
1105
|
-
* its keys; until it lands the registration names no live entry and a
|
|
1106
|
-
* rotation passes over it. While any read grant is live its grantees
|
|
1107
|
-
* hold the live secret, and one could seal it to the joining reader with
|
|
1108
|
-
* a roster key of its own ahead of the genuine envelope, so the account
|
|
1109
|
-
* keys are rotated first in the same session, with a fresh roster key,
|
|
1110
|
-
* as a removal rotates them, the joining device handed the new secret no
|
|
1111
|
-
* grantee has seen, and the live read grants this client holds the read
|
|
1112
|
-
* key of sealed it after; with no live read grant only enrolled devices
|
|
1113
|
-
* hold the secret, and the live keys are handed over as they are. The
|
|
1114
|
-
* joining device then signs in and reads the holdings with its own
|
|
1115
|
-
* passkey alone. Without `reader` the
|
|
1116
|
-
* device can authorise but reads nothing until some device delivers it
|
|
1117
|
-
* the keys.
|
|
1118
|
-
*
|
|
1119
|
-
* The advanced counter is persisted to the account record after every
|
|
1120
|
-
* call, before the roster is returned, so the next flow rescans from a
|
|
1121
|
-
* fresh anchor. A failure leaves the roster unreturned.
|
|
1122
|
-
*
|
|
1123
|
-
* A rejected authorisation (bad signature, malformed key, or a caller
|
|
1124
|
-
* entry the on-chain set no longer holds) rejects with NotAuthorisedError;
|
|
1125
|
-
* a record whose address holds no live contract rejects with
|
|
1126
|
-
* AccountContractMissingError; joining keys that do not match the entry
|
|
1127
|
-
* reject with DeviceReaderInvalidError. The transactions' proving and
|
|
1128
|
-
* sponsoring stages and 'recording' are reported through `onProgress`.
|
|
1129
|
-
*/
|
|
1130
|
-
const addDevice = async ({ reader, ...input }, context) => {
|
|
1131
|
-
const { seams, onProgress } = context;
|
|
1132
|
-
const { account, commitmentHex } = input;
|
|
1133
|
-
const { networkId } = seams.passportNetwork;
|
|
1134
|
-
try {
|
|
1135
|
-
const ledger = await readAccountLedger(account.address, seams.passportNetwork);
|
|
1136
|
-
if (reader) assertJoiningDevice({
|
|
1137
|
-
address: account.address,
|
|
1138
|
-
epoch: ledger.ledgerState.device_epoch
|
|
1139
|
-
}, reader, commitmentHex);
|
|
1140
|
-
const view = reader && hasLiveReadGrant(ledger.ledgerState) ? viewCapability(seams.passportEncryptionKey) : void 0;
|
|
1141
|
-
const devices = view ? liveDeviceEntries(ledger.ledgerState) : void 0;
|
|
1142
|
-
return await inKeySession(seams.passportAuthoriser, {
|
|
1143
|
-
kind: "add-device",
|
|
1144
|
-
commitmentHex,
|
|
1145
|
-
account: AccAddress(account.address),
|
|
1146
|
-
...reader ? { reader } : {},
|
|
1147
|
-
...devices ? { devices } : {}
|
|
1148
|
-
}, async () => {
|
|
1149
|
-
const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
|
|
1150
|
-
let rotated;
|
|
1151
|
-
if (reader) {
|
|
1152
|
-
const local = await readLocalDevice(seams);
|
|
1153
|
-
if (view && devices) rotated = await rotateEncKey({
|
|
1154
|
-
account,
|
|
1155
|
-
encryptionKey: seams.passportEncryptionKey,
|
|
1156
|
-
view,
|
|
1157
|
-
local,
|
|
1158
|
-
chain,
|
|
1159
|
-
indexer: ledger.indexer,
|
|
1160
|
-
networkId,
|
|
1161
|
-
onProgress,
|
|
1162
|
-
devices,
|
|
1163
|
-
rotateRoster: true
|
|
1164
|
-
});
|
|
1165
|
-
const delivery = await seams.passportEncryptionKey.sealToDevice(networkId, rotated?.keyring ?? readAccountInbox(account.address, chain.position().ledgerState, local.readerPublicKey).keyring, {
|
|
1166
|
-
...reader,
|
|
1167
|
-
epoch: ledger.ledgerState.device_epoch,
|
|
1168
|
-
counter: 0n
|
|
1169
|
-
});
|
|
1170
|
-
await chain.append("recording", [delivery.envelope, delivery.registration]);
|
|
1171
|
-
if (view && rotated) {
|
|
1172
|
-
const views = await sealViews({
|
|
1173
|
-
view,
|
|
1174
|
-
networkId,
|
|
1175
|
-
keyring: rotated.keyring,
|
|
1176
|
-
account: AccAddress(account.address),
|
|
1177
|
-
targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
|
|
1178
|
-
});
|
|
1179
|
-
await keep(withSealedViews(grants, views));
|
|
1180
|
-
await chain.append("recording", views.entries);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
await chain.submit({
|
|
1184
|
-
circuit: ADD_DEVICE_CIRCUIT,
|
|
1185
|
-
args: [entryArgument(commitmentHex)]
|
|
1186
|
-
});
|
|
1187
|
-
return {
|
|
1188
|
-
devices: withDevice(chain.position().devices, commitmentHex),
|
|
1189
|
-
localUseCounter: chainCounter(chain, input),
|
|
1190
|
-
...rotated ? {
|
|
1191
|
-
encKey: HexBytes.fromByteArray(rotated.encKey),
|
|
1192
|
-
droppedDevices: rotated.dropped
|
|
1193
|
-
} : {}
|
|
1194
|
-
};
|
|
1195
|
-
});
|
|
1196
|
-
} catch (error) {
|
|
1197
|
-
throw classifyContractAbort$1(error, ADD_DEVICE_CIRCUIT);
|
|
1198
|
-
}
|
|
1199
|
-
};
|
|
1200
|
-
/**
|
|
1201
|
-
* Refuses a removal the contract would refuse before any prompt: a
|
|
1202
|
-
* target the on-chain device set does not hold, and the local device,
|
|
1203
|
-
* which cannot remove itself. The rotation a removal runs comes first,
|
|
1204
|
-
* so a removal refused only after it would have cost the target device
|
|
1205
|
-
* its keys for nothing.
|
|
1206
|
-
*
|
|
1207
|
-
* @throws {@link RemovalTargetNotFoundError} when the target is gone.
|
|
1208
|
-
* @throws {@link LastDeviceError} when the target is this device.
|
|
1209
|
-
*/
|
|
1210
|
-
const assertRemovable = ({ devices, commitmentHex }, { ledgerState }) => {
|
|
1211
|
-
if (!ledgerState.devices.member(entryArgument(commitmentHex))) throw new RemovalTargetNotFoundError(`The account holds no device entry ${commitmentHex}.`);
|
|
1212
|
-
if (devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase() === commitmentHex.toLowerCase()) throw new LastDeviceError("The device that authorises a removal cannot remove itself.");
|
|
1213
|
-
};
|
|
1214
|
-
/**
|
|
1215
|
-
* Revokes a device key from the account and rotates the account keys so
|
|
1216
|
-
* the removed device reads nothing newer. `commitmentHex` names the
|
|
1217
|
-
* target by its current entry commitment, which is the literal set
|
|
1218
|
-
* element the contract removes; the local device authorises
|
|
1219
|
-
* `remove_device_with_jubjub` through the gated-call pattern.
|
|
1220
|
-
*
|
|
1221
|
-
* Ahead of the removal, in the same session, the encryption key is
|
|
1222
|
-
* rotated with a fresh roster key and sealed only to this device and to
|
|
1223
|
-
* the registered devices whose device key derives a live entry other
|
|
1224
|
-
* than the target, which the key session names to the user before it
|
|
1225
|
-
* opens; the live holdings are re-encrypted towards it, and it is sealed
|
|
1226
|
-
* again to every live read grant this client holds the read key of, each
|
|
1227
|
-
* seal kept on chain in a grant record. The removed device's envelopes
|
|
1228
|
-
* stay in the inbox and keep opening the secrets they held, but none
|
|
1229
|
-
* opens the new one, and the registrations under the old roster key no
|
|
1230
|
-
* longer count, any it planted for itself included. A roster in which
|
|
1231
|
-
* two readers claim one live device, or one reader two, refuses with
|
|
1232
|
-
* DeviceRosterInvalidError rather than guess.
|
|
1233
|
-
*
|
|
1234
|
-
* The roster drops the commitment on success only, and the same result
|
|
1235
|
-
* moves the local device's own row to the successor entry the call left
|
|
1236
|
-
* it on. The advanced counter is persisted to the account record after
|
|
1237
|
-
* every call. The contract's own guards surface as typed errors: a
|
|
1238
|
-
* removal that would strand the account (the authorising device, or the
|
|
1239
|
-
* count floor) rejects with LastDeviceError, a rejected authorisation
|
|
1240
|
-
* (bad signature or malformed key) with NotAuthorisedError, and a target
|
|
1241
|
-
* entry that is no longer in the on-chain set with
|
|
1242
|
-
* RemovalTargetNotFoundError; the target's presence and the local device
|
|
1243
|
-
* are checked before any prompt, and an encryption key that cannot
|
|
1244
|
-
* rotate refuses with ReadUnavailableError before any prompt too. The
|
|
1245
|
-
* rotation's stages, 'recording' and the transactions' proving and
|
|
1246
|
-
* sponsoring are reported through `onProgress`.
|
|
1247
|
-
*/
|
|
1248
|
-
const removeDevice = async (input, context) => {
|
|
1249
|
-
const { seams, onProgress } = context;
|
|
1250
|
-
const { account, commitmentHex } = input;
|
|
1251
|
-
const { networkId } = seams.passportNetwork;
|
|
1252
|
-
try {
|
|
1253
|
-
const view = viewCapability(seams.passportEncryptionKey);
|
|
1254
|
-
const ledger = await readAccountLedger(account.address, seams.passportNetwork);
|
|
1255
|
-
assertRemovable(input, ledger);
|
|
1256
|
-
const devices = liveDeviceEntries(ledger.ledgerState, commitmentHex);
|
|
1257
|
-
return await inKeySession(seams.passportAuthoriser, {
|
|
1258
|
-
kind: "remove-device",
|
|
1259
|
-
commitmentHex,
|
|
1260
|
-
account: AccAddress(account.address),
|
|
1261
|
-
devices
|
|
1262
|
-
}, async () => {
|
|
1263
|
-
const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
|
|
1264
|
-
const rotated = await rotateEncKey({
|
|
1265
|
-
account,
|
|
1266
|
-
encryptionKey: seams.passportEncryptionKey,
|
|
1267
|
-
view,
|
|
1268
|
-
local: await readLocalDevice(seams),
|
|
1269
|
-
chain,
|
|
1270
|
-
indexer: ledger.indexer,
|
|
1271
|
-
networkId,
|
|
1272
|
-
onProgress,
|
|
1273
|
-
devices,
|
|
1274
|
-
rotateRoster: true,
|
|
1275
|
-
target: commitmentHex.toLowerCase()
|
|
1276
|
-
});
|
|
1277
|
-
const views = await sealViews({
|
|
1278
|
-
view,
|
|
1279
|
-
networkId,
|
|
1280
|
-
keyring: rotated.keyring,
|
|
1281
|
-
account: AccAddress(account.address),
|
|
1282
|
-
targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
|
|
1283
|
-
});
|
|
1284
|
-
await keep(withSealedViews(grants, views));
|
|
1285
|
-
await chain.append("recording", views.entries);
|
|
1286
|
-
await chain.submit({
|
|
1287
|
-
circuit: REMOVE_DEVICE_CIRCUIT,
|
|
1288
|
-
args: [entryArgument(commitmentHex)]
|
|
1289
|
-
});
|
|
1290
|
-
return {
|
|
1291
|
-
devices: withoutDevice(chain.position().devices, commitmentHex),
|
|
1292
|
-
localUseCounter: chainCounter(chain, input),
|
|
1293
|
-
encKey: HexBytes.fromByteArray(rotated.encKey),
|
|
1294
|
-
droppedDevices: rotated.dropped
|
|
1295
|
-
};
|
|
1296
|
-
});
|
|
1297
|
-
} catch (error) {
|
|
1298
|
-
throw classifyContractAbort$1(error, REMOVE_DEVICE_CIRCUIT);
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
//#endregion
|
|
1302
|
-
//#region ../../packages/module/passport-account/src/flows/grants.ts
|
|
1303
|
-
/** Contract abort naming a grant the grants map does not hold. */
|
|
1304
|
-
const UNKNOWN_GRANT_ABORT = "failed assert: unknown grant";
|
|
1305
|
-
/** Contract abort refusing a revoke over a grant already inactive. */
|
|
1306
|
-
const GRANT_NOT_LIVE_ABORT = "failed assert: grant not live";
|
|
1307
|
-
/** Contract abort refusing an issue over a grant id that is still active. */
|
|
1308
|
-
const GRANT_ACTIVE_ABORT = "failed assert: grant already active";
|
|
1309
|
-
/**
|
|
1310
|
-
* How long an accepted call is given to show in the indexer's view of the
|
|
1311
|
-
* grants map, which can lag the block the call landed in.
|
|
1312
|
-
*/
|
|
1313
|
-
const ROSTER_POLL_ATTEMPTS = 30;
|
|
1314
|
-
const ROSTER_POLL_DELAY_MS = 1e3;
|
|
1315
|
-
const MILLISECONDS_PER_SECOND = 1e3;
|
|
1316
|
-
const sleep = async (ms) => new Promise((resolve) => {
|
|
1317
|
-
setTimeout(resolve, ms);
|
|
1318
|
-
});
|
|
1319
|
-
const isActive = (roster, grantId) => roster.some((record) => record.id === grantId && record.active);
|
|
1320
|
-
const activeCount = (roster) => roster.filter((record) => record.active).length;
|
|
1321
|
-
const nowSeconds = () => Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
|
|
1322
|
-
/**
|
|
1323
|
-
* Types a raw contract abort by the assert message the Account Custody
|
|
1324
|
-
* Contract raises; anything unrecognised passes through unchanged. An
|
|
1325
|
-
* unknown-entry abort can only come from the caller check here, since no
|
|
1326
|
-
* grant circuit names a device entry.
|
|
1327
|
-
*/
|
|
1328
|
-
const classifyContractAbort = (error) => {
|
|
1329
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1330
|
-
if (message.includes(UNKNOWN_GRANT_ABORT)) return new GrantNotFoundError(message);
|
|
1331
|
-
if (message.includes(GRANT_NOT_LIVE_ABORT)) return new GrantRevokedError(message);
|
|
1332
|
-
if (message.includes(GRANT_ACTIVE_ABORT)) return new GrantIssueRejectedError(message);
|
|
1333
|
-
if (message.includes("failed assert: unknown device entry") || isNotAuthorisedAbort(message)) return new NotAuthorisedError(message);
|
|
1334
|
-
return error;
|
|
1335
|
-
};
|
|
1336
|
-
/**
|
|
1337
|
-
* Refuses a device whose storage holds no account record before any
|
|
1338
|
-
* ceremony. The gate only runs a call over an account in state, so an
|
|
1339
|
-
* absent record means the storage was cleared underneath it.
|
|
1340
|
-
*/
|
|
1341
|
-
const assertRecordPresent = async (accountRecords) => {
|
|
1342
|
-
if (await accountRecords.presence() === "absent") throw new AccountNotFoundError("No Passport account record is stored on this device.");
|
|
1343
|
-
};
|
|
1344
|
-
/**
|
|
1345
|
-
* The stored record of the account a grant call acts on, read inside the
|
|
1346
|
-
* session where it costs no extra prompt. Presence was checked ahead of
|
|
1347
|
-
* the session, so a missing record here vanished in between.
|
|
1348
|
-
*/
|
|
1349
|
-
const storedRecord = async (accountRecords) => {
|
|
1350
|
-
const record = await accountRecords.read();
|
|
1351
|
-
if (!record) throw new AccountNotFoundError("No Passport account record is stored on this device.");
|
|
1352
|
-
return record;
|
|
1353
|
-
};
|
|
1354
|
-
/**
|
|
1355
|
-
* The ledger state once the indexer reflects the accepted call, with the
|
|
1356
|
-
* roster read from it. The submission settled in a block, but the
|
|
1357
|
-
* indexer can serve the state from before it for a moment, and a roster
|
|
1358
|
-
* read then would deny the call its own result.
|
|
1359
|
-
*
|
|
1360
|
-
* @throws {@link GrantRosterUnsettledError} when the indexer never
|
|
1361
|
-
* reflects the call within the polling window.
|
|
1362
|
-
*/
|
|
1363
|
-
const awaitSettledRoster = async ({ indexer, address, slots, openings, isSettled }) => {
|
|
1364
|
-
for (let attempt = 0; attempt < ROSTER_POLL_ATTEMPTS; attempt++) {
|
|
1365
|
-
const ledgerState = await queryAccountLedger(indexer, address);
|
|
1366
|
-
const roster = readGrants(ledgerState, slots, openings);
|
|
1367
|
-
if (isSettled(roster)) return {
|
|
1368
|
-
ledgerState,
|
|
1369
|
-
roster
|
|
1370
|
-
};
|
|
1371
|
-
await sleep(ROSTER_POLL_DELAY_MS);
|
|
1372
|
-
}
|
|
1373
|
-
throw new GrantRosterUnsettledError(`The grant roster of ${address} never reflected the accepted call.`);
|
|
1374
|
-
};
|
|
1375
|
-
/**
|
|
1376
|
-
* Runs one gated grant call inside a single key session, so the use
|
|
1377
|
-
* counter rescan and the authorisation cost one ceremony rather than one
|
|
1378
|
-
* per authoriser call. A device whose storage holds no record is refused
|
|
1379
|
-
* ahead of the session. The record is read inside it, where it costs no
|
|
1380
|
-
* extra prompt, its grant bookkeeping narrowed to the grants whose kept
|
|
1381
|
-
* fields open what the ledger read before the call holds under their id,
|
|
1382
|
-
* so an entry an issue wrote before a call that never landed is neither
|
|
1383
|
-
* an opening nor a re-seal target. It is rewritten with the advanced
|
|
1384
|
-
* counter and the grant bookkeeping after every call, so a roster is
|
|
1385
|
-
* never published ahead of the record that anchors the next rescan and
|
|
1386
|
-
* names the slots. A call
|
|
1387
|
-
* that keeps its bookkeeping ahead of the submission writes it once more
|
|
1388
|
-
* before it, under the counter the record holds. A call that rotates the
|
|
1389
|
-
* encryption key does so in the same session ahead of its own circuit.
|
|
1390
|
-
* The other live read grants are sealed the new secret right after the
|
|
1391
|
-
* rotation lands, their seals kept in the record and on chain before the
|
|
1392
|
-
* call is submitted, so they keep reading whatever the call's outcome.
|
|
1393
|
-
* Once the roster shows an issued grant active, its bookkeeping is
|
|
1394
|
-
* appended to the inbox, sealed to this device alone, and the grantee of
|
|
1395
|
-
* a read grant is sealed the new secret, the seal kept the same way. The
|
|
1396
|
-
* wait for the indexer is reported as 'confirming' and the appends as
|
|
1397
|
-
* 'recording'. The contract's own aborts surface as typed errors;
|
|
1398
|
-
* anything else, and a failing on-chain status in particular, rejects
|
|
1399
|
-
* unchanged with the counter unmoved.
|
|
1400
|
-
*/
|
|
1401
|
-
const runGrantCall = async ({ config: { call, flow, nextStoredGrants, writeBeforeCall, isSettled, rotation, reseal, issued }, input: { account, devices, localUseCounter }, ledger: { indexer, ledgerState }, roster: listed, context: { seams, accountRecords, onProgress } }) => {
|
|
1402
|
-
const { networkId } = seams.passportNetwork;
|
|
1403
|
-
const accountAddress = AccAddress(account.address);
|
|
1404
|
-
try {
|
|
1405
|
-
await assertRecordPresent(accountRecords);
|
|
1406
|
-
const rotationDevices = rotation ? liveDeviceEntries(ledgerState) : void 0;
|
|
1407
|
-
return await inKeySession(seams.passportAuthoriser, rotationDevices ? {
|
|
1408
|
-
...flow,
|
|
1409
|
-
devices: rotationDevices
|
|
1410
|
-
} : flow, async () => {
|
|
1411
|
-
const record = await storedRecord(accountRecords);
|
|
1412
|
-
let stored = nextStoredGrants(await retainStoredGrants(record.grants, listed, account.address));
|
|
1413
|
-
const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants: stored }));
|
|
1414
|
-
if (writeBeforeCall) await write(record.localUseCounter);
|
|
1415
|
-
const chain = callChain({
|
|
1416
|
-
account,
|
|
1417
|
-
start: {
|
|
1418
|
-
ledgerState,
|
|
1419
|
-
localUseCounter,
|
|
1420
|
-
devices
|
|
1421
|
-
},
|
|
1422
|
-
indexer,
|
|
1423
|
-
seams,
|
|
1424
|
-
onProgress,
|
|
1425
|
-
onAdvanced: write
|
|
1426
|
-
});
|
|
1427
|
-
const counter = () => chain.position().localUseCounter ?? record.localUseCounter;
|
|
1428
|
-
const roster = (state) => readGrants(state, storedGrantSlots(stored), storedGrantOpenings(stored));
|
|
1429
|
-
let rotated;
|
|
1430
|
-
let unsealedGrantIds = [];
|
|
1431
|
-
let revocationPendingGrantIds = [];
|
|
1432
|
-
if (rotation) {
|
|
1433
|
-
rotated = await rotateEncKey({
|
|
1434
|
-
account,
|
|
1435
|
-
encryptionKey: seams.passportEncryptionKey,
|
|
1436
|
-
view: rotation,
|
|
1437
|
-
local: await readLocalDevice(seams),
|
|
1438
|
-
chain,
|
|
1439
|
-
indexer,
|
|
1440
|
-
networkId,
|
|
1441
|
-
onProgress,
|
|
1442
|
-
devices: rotationDevices ?? []
|
|
1443
|
-
});
|
|
1444
|
-
if (reseal) {
|
|
1445
|
-
const views = await sealViews({
|
|
1446
|
-
view: rotation,
|
|
1447
|
-
networkId,
|
|
1448
|
-
keyring: rotated.keyring,
|
|
1449
|
-
account: accountAddress,
|
|
1450
|
-
targets: liveStoredReadGrants(stored, roster(chain.position().ledgerState), reseal.except)
|
|
1451
|
-
});
|
|
1452
|
-
stored = withSealedViews(stored, views);
|
|
1453
|
-
unsealedGrantIds = views.unsealed;
|
|
1454
|
-
revocationPendingGrantIds = views.revocationPending;
|
|
1455
|
-
await write(counter());
|
|
1456
|
-
await chain.append("recording", views.entries);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
await chain.submit(call);
|
|
1460
|
-
onProgress("confirming");
|
|
1461
|
-
const settled = await awaitSettledRoster({
|
|
1462
|
-
indexer,
|
|
1463
|
-
address: account.address,
|
|
1464
|
-
slots: storedGrantSlots(stored),
|
|
1465
|
-
openings: storedGrantOpenings(stored),
|
|
1466
|
-
isSettled
|
|
1467
|
-
});
|
|
1468
|
-
let grants = settled.roster;
|
|
1469
|
-
if (issued) {
|
|
1470
|
-
const entries = await seams.passportEncryptionKey.sealGrant(networkId, accountAddress, issued.bookkeeping);
|
|
1471
|
-
if (rotation && rotated && issued.readPublicKey !== void 0) {
|
|
1472
|
-
const views = await sealViews({
|
|
1473
|
-
view: rotation,
|
|
1474
|
-
networkId,
|
|
1475
|
-
keyring: rotated.keyring,
|
|
1476
|
-
account: accountAddress,
|
|
1477
|
-
targets: [{
|
|
1478
|
-
grantId: issued.bookkeeping.grantId,
|
|
1479
|
-
readPublicKey: issued.readPublicKey
|
|
1480
|
-
}]
|
|
1481
|
-
});
|
|
1482
|
-
stored = withSealedViews(stored, views);
|
|
1483
|
-
unsealedGrantIds = [...unsealedGrantIds, ...views.unsealed];
|
|
1484
|
-
revocationPendingGrantIds = [...revocationPendingGrantIds, ...views.revocationPending];
|
|
1485
|
-
entries.push(...views.entries);
|
|
1486
|
-
await write(counter());
|
|
1487
|
-
grants = roster(settled.ledgerState);
|
|
1488
|
-
}
|
|
1489
|
-
await chain.append("recording", entries);
|
|
1490
|
-
}
|
|
1491
|
-
return {
|
|
1492
|
-
grants,
|
|
1493
|
-
devices: chain.position().devices,
|
|
1494
|
-
localUseCounter: counter(),
|
|
1495
|
-
...rotated ? {
|
|
1496
|
-
encKey: HexBytes.fromByteArray(rotated.encKey),
|
|
1497
|
-
droppedDevices: rotated.dropped
|
|
1498
|
-
} : {},
|
|
1499
|
-
unsealedGrantIds,
|
|
1500
|
-
revocationPendingGrantIds
|
|
1501
|
-
};
|
|
1502
|
-
});
|
|
1503
|
-
} catch (error) {
|
|
1504
|
-
throw classifyContractAbort(error);
|
|
1505
|
-
}
|
|
1506
|
-
};
|
|
1507
|
-
/**
|
|
1508
|
-
* Refuses a grant its grantee could never spend: one naming a scheme
|
|
1509
|
-
* whose withdrawals the account was deployed without. Installing them
|
|
1510
|
-
* takes a maintenance update, which a locked account can no longer sign
|
|
1511
|
-
* and an unlocked one no longer holds the key for, so the issue is
|
|
1512
|
-
* refused before the owner is asked to authorise it.
|
|
1513
|
-
*
|
|
1514
|
-
* @throws {@link GrantScopeInvalidError} when a withdrawal of the
|
|
1515
|
-
* grantee's scheme is not installed on the account.
|
|
1516
|
-
*/
|
|
1517
|
-
const requireGranteeWithdrawals = async (indexer, address, scheme) => {
|
|
1518
|
-
const installed = new Set(await queryAccountOperations(indexer, address));
|
|
1519
|
-
const missing = GRANTEE_WITHDRAWAL_CIRCUITS[scheme].filter((circuit) => !installed.has(circuit));
|
|
1520
|
-
if (missing.length > 0) throw new GrantScopeInvalidError(`the account cannot serve a ${scheme} grantee, it lacks ${missing.join(", ")}`);
|
|
1521
|
-
};
|
|
1522
|
-
/**
|
|
1523
|
-
* Issues a scoped grant to an agent key. The scope is checked against the
|
|
1524
|
-
* rules the contract asserts before any prompt: a shielded operation
|
|
1525
|
-
* needs read, a read grant needs a canonical read public key, a rolling
|
|
1526
|
-
* window is refused because the contract reserves its bounds, and a
|
|
1527
|
-
* grant beyond sixteen active ones is refused as Lace's limit. Each
|
|
1528
|
-
* refusal is a GrantScopeInvalidError or GrantLimitReachedError and costs
|
|
1529
|
-
* no ceremony; a host with no relying party id is refused with
|
|
1530
|
-
* GrantOriginMissingError before the chain is read, and a read grant
|
|
1531
|
-
* with ReadUnavailableError when the encryption key held here cannot
|
|
1532
|
-
* share the viewing secret. The grant id is derived from the account,
|
|
1533
|
-
* the grantee key, the hash of this host's relying party id and the
|
|
1534
|
-
* first slot free for that pair, and the scope salt is drawn fresh; the
|
|
1535
|
-
* local device then authorises `issue_grant_with_jubjub` over the id and
|
|
1536
|
-
* the salted scope digest, which binds the hash of the read key. The key
|
|
1537
|
-
* session is opened with the account, the slot, the salt and the digest
|
|
1538
|
-
* beside the id and the scope, so an authoriser that does not share this
|
|
1539
|
-
* flow's memory can re-derive both before it signs.
|
|
1540
|
-
*
|
|
1541
|
-
* A read grant delivers the account's viewing secret, and read is total:
|
|
1542
|
-
* the grantee sees every shielded holding, current and future, until the
|
|
1543
|
-
* key is rotated again. So the key is rotated first, in the same session:
|
|
1544
|
-
* a fresh random secret is sealed to every device, the live holdings are
|
|
1545
|
-
* re-encrypted towards it, its public key replaces `enc_key`, and only
|
|
1546
|
-
* then is the grant issued, so the grantee reads nothing of the
|
|
1547
|
-
* account's spent history. Right after the rotation lands the new secret
|
|
1548
|
-
* is sealed to every other live read grant this client holds the key of,
|
|
1549
|
-
* so they keep reading even should the issue fail, and once the grant
|
|
1550
|
-
* shows active it is sealed to the grantee's read key; the seals join
|
|
1551
|
-
* the bookkeeping, and the grantee's reaches it through the agent
|
|
1552
|
-
* policy, never the chain. A grantee seal the key seam refuses after the
|
|
1553
|
-
* grant landed leaves the grant active and unsealed, named among the
|
|
1554
|
-
* result's unsealed grants, rather than failing the issue.
|
|
1555
|
-
*
|
|
1556
|
-
* The slot and salt are kept in the account record, since the contract
|
|
1557
|
-
* stores neither, together with the object fields its commitment hides
|
|
1558
|
-
* (colour, recipient and coin bound) and the read key, which the grantee
|
|
1559
|
-
* needs to open it and which reach the roster as the grant's opening.
|
|
1560
|
-
* They are written before the call is submitted: a grant that lands
|
|
1561
|
-
* without its salt could never be opened for its grantee. A call that
|
|
1562
|
-
* never lands leaves a stale entry, which the next sign-in prunes against
|
|
1563
|
-
* the chain and a later issue over the same id overwrites. Once the
|
|
1564
|
-
* grant shows active the same bookkeeping, and each seal, is appended to
|
|
1565
|
-
* the inbox in grant records only this device opens, so a sign-in after
|
|
1566
|
-
* the record is lost rebuilds it. The roster is read back from chain
|
|
1567
|
-
* once the grant shows active in it.
|
|
1568
|
-
*
|
|
1569
|
-
* A grant id the contract still holds active rejects with
|
|
1570
|
-
* GrantIssueRejectedError, and a rejected authorisation with
|
|
1571
|
-
* NotAuthorisedError. The transaction's proving and sponsoring stages,
|
|
1572
|
-
* the rotation, the wait for the indexer and the appends are reported
|
|
1573
|
-
* through `onProgress`.
|
|
1574
|
-
*/
|
|
1575
|
-
const issueGrant = async ({ scope, ...input }, context) => {
|
|
1576
|
-
const { rpId, seams } = context;
|
|
1577
|
-
if (rpId === void 0) throw new GrantOriginMissingError();
|
|
1578
|
-
const view = scope.read ? viewCapability(seams.passportEncryptionKey) : null;
|
|
1579
|
-
const { account } = input;
|
|
1580
|
-
const ledger = await readAccountLedger(account.address, seams.passportNetwork);
|
|
1581
|
-
await requireGranteeWithdrawals(ledger.indexer, account.address, scope.grantee.scheme);
|
|
1582
|
-
const roster = readGrants(ledger.ledgerState);
|
|
1583
|
-
validateGrantScope(scope, {
|
|
1584
|
-
nowSeconds: nowSeconds(),
|
|
1585
|
-
grantCount: activeCount(roster)
|
|
1586
|
-
});
|
|
1587
|
-
const { grantId, slot, scopeSalt, scopeDigest, call } = prepareIssueGrant({
|
|
1588
|
-
contractAddress: ByteArray.fromHex(HexBytes(account.address)),
|
|
1589
|
-
scope,
|
|
1590
|
-
originHash: await originHash(rpId),
|
|
1591
|
-
roster
|
|
1592
|
-
});
|
|
1593
|
-
const readPublicKey = view ? scope.readPublicKey : void 0;
|
|
1594
|
-
const object = {
|
|
1595
|
-
color: scope.color,
|
|
1596
|
-
recipient: scope.recipient,
|
|
1597
|
-
maxCoinValue: scope.maxCoinValue
|
|
1598
|
-
};
|
|
1599
|
-
return {
|
|
1600
|
-
grantId,
|
|
1601
|
-
...await runGrantCall({
|
|
1602
|
-
config: {
|
|
1603
|
-
call,
|
|
1604
|
-
flow: {
|
|
1605
|
-
kind: "issue-grant",
|
|
1606
|
-
grantId,
|
|
1607
|
-
scope,
|
|
1608
|
-
account: AccAddress(account.address),
|
|
1609
|
-
slot,
|
|
1610
|
-
scopeSalt,
|
|
1611
|
-
scopeDigest
|
|
1612
|
-
},
|
|
1613
|
-
nextStoredGrants: (stored) => withStoredGrant(stored, grantId, {
|
|
1614
|
-
slot,
|
|
1615
|
-
scopeSalt,
|
|
1616
|
-
granteeScheme: scope.grantee.scheme,
|
|
1617
|
-
granteePublicKey: scope.grantee.publicKey,
|
|
1618
|
-
origin: rpId,
|
|
1619
|
-
object,
|
|
1620
|
-
...readPublicKey === void 0 ? {} : { view: { readPublicKey } }
|
|
1621
|
-
}),
|
|
1622
|
-
writeBeforeCall: true,
|
|
1623
|
-
isSettled: (settled) => isActive(settled, grantId),
|
|
1624
|
-
...view && readPublicKey !== void 0 ? {
|
|
1625
|
-
rotation: view,
|
|
1626
|
-
reseal: { except: grantId }
|
|
1627
|
-
} : {},
|
|
1628
|
-
issued: {
|
|
1629
|
-
bookkeeping: {
|
|
1630
|
-
grantId,
|
|
1631
|
-
slot,
|
|
1632
|
-
scopeSalt,
|
|
1633
|
-
granteeScheme: scope.grantee.scheme,
|
|
1634
|
-
granteePublicKey: scope.grantee.publicKey,
|
|
1635
|
-
object,
|
|
1636
|
-
...readPublicKey === void 0 ? {} : { readPublicKey }
|
|
1637
|
-
},
|
|
1638
|
-
...readPublicKey === void 0 ? {} : { readPublicKey }
|
|
1639
|
-
}
|
|
1640
|
-
},
|
|
1641
|
-
input,
|
|
1642
|
-
ledger,
|
|
1643
|
-
roster,
|
|
1644
|
-
context
|
|
1645
|
-
})
|
|
1646
|
-
};
|
|
1647
|
-
};
|
|
1648
|
-
/**
|
|
1649
|
-
* The stored grants with the seal of one grant dropped, its read key
|
|
1650
|
-
* kept: a revoked read grant's seal holds a secret the rotation that
|
|
1651
|
-
* follows retires.
|
|
1652
|
-
*/
|
|
1653
|
-
const withoutSeal = (stored, grantId) => {
|
|
1654
|
-
const view = stored?.[grantId]?.view;
|
|
1655
|
-
return stored && view ? withStoredGrantView(stored, grantId, { readPublicKey: view.readPublicKey }) : stored;
|
|
1656
|
-
};
|
|
1657
|
-
/**
|
|
1658
|
-
* Revokes one grant by its id. A grant the chain does not hold is refused
|
|
1659
|
-
* with GrantNotFoundError before any prompt, and one it lists inactive
|
|
1660
|
-
* with GrantRevokedError; the same errors type the contract's own aborts
|
|
1661
|
-
* should the grant vanish or be revoked elsewhere in between. The local
|
|
1662
|
-
* device authorises `revoke_grant_with_jubjub`. The grant stays in the
|
|
1663
|
-
* roster as an inactive tombstone, so its stored slot and salt are kept,
|
|
1664
|
-
* and the roster is read back from chain once the grant shows inactive.
|
|
1665
|
-
*
|
|
1666
|
-
* Revoking a read grant also rotates the encryption key, in the same
|
|
1667
|
-
* session ahead of the revocation: a fresh random secret is sealed to
|
|
1668
|
-
* every device and the live holdings are re-encrypted towards it, so the
|
|
1669
|
-
* revoked grantee keeps what it already read and nothing newer, and
|
|
1670
|
-
* right after the rotation lands the new secret is sealed again to every
|
|
1671
|
-
* other read grant this client holds the read key of, ahead of the
|
|
1672
|
-
* revocation. A read grant is refused with ReadUnavailableError before
|
|
1673
|
-
* any prompt when the encryption key held here cannot re-encrypt and
|
|
1674
|
-
* seal.
|
|
1675
|
-
*/
|
|
1676
|
-
const revokeGrant = async ({ grantId, ...input }, context) => {
|
|
1677
|
-
const { account } = input;
|
|
1678
|
-
const ledger = await readAccountLedger(account.address, context.seams.passportNetwork);
|
|
1679
|
-
const roster = readGrants(ledger.ledgerState);
|
|
1680
|
-
const listed = roster.find((record) => record.id === grantId);
|
|
1681
|
-
if (!listed) throw new GrantNotFoundError(`The account at ${account.address} holds no grant ${grantId}.`);
|
|
1682
|
-
if (!listed.active) throw new GrantRevokedError(`The grant ${grantId} of the account at ${account.address} is already revoked.`);
|
|
1683
|
-
const rotation = listed.scope.read ? viewCapability(context.seams.passportEncryptionKey) : void 0;
|
|
1684
|
-
return runGrantCall({
|
|
1685
|
-
config: {
|
|
1686
|
-
call: prepareRevokeGrant(grantId),
|
|
1687
|
-
flow: {
|
|
1688
|
-
kind: "revoke-grant",
|
|
1689
|
-
grantId,
|
|
1690
|
-
account: AccAddress(account.address),
|
|
1691
|
-
...rotation ? { read: true } : {}
|
|
1692
|
-
},
|
|
1693
|
-
nextStoredGrants: (stored) => rotation ? withoutSeal(stored, grantId) : stored,
|
|
1694
|
-
writeBeforeCall: false,
|
|
1695
|
-
isSettled: (settled) => !isActive(settled, grantId),
|
|
1696
|
-
...rotation ? {
|
|
1697
|
-
rotation,
|
|
1698
|
-
reseal: { except: grantId }
|
|
1699
|
-
} : {}
|
|
1700
|
-
},
|
|
1701
|
-
input,
|
|
1702
|
-
ledger,
|
|
1703
|
-
roster,
|
|
1704
|
-
context
|
|
1705
|
-
});
|
|
1706
|
-
};
|
|
1707
|
-
/**
|
|
1708
|
-
* Revokes every grant the account has issued: the local device
|
|
1709
|
-
* authorises `revoke_all_grants_with_jubjub`, which clears the grants
|
|
1710
|
-
* map and advances the grant generation. The record's grant bookkeeping
|
|
1711
|
-
* is dropped with the map, and the roster is read back from chain once
|
|
1712
|
-
* it lists nothing, tombstones included. While any read grant is live,
|
|
1713
|
-
* the encryption key is rotated in the same session ahead of the call
|
|
1714
|
-
* and the live holdings are re-encrypted, so no revoked grantee reads
|
|
1715
|
-
* on; with every grant gone there is nobody to re-seal to. The rotation
|
|
1716
|
-
* is refused with ReadUnavailableError before any prompt when the
|
|
1717
|
-
* encryption key held here cannot re-encrypt.
|
|
1718
|
-
*/
|
|
1719
|
-
const revokeAllGrants = async (input, context) => {
|
|
1720
|
-
const { account } = input;
|
|
1721
|
-
const ledger = await readAccountLedger(account.address, context.seams.passportNetwork);
|
|
1722
|
-
const roster = readGrants(ledger.ledgerState);
|
|
1723
|
-
const rotation = roster.some((record) => record.active && record.scope.read) ? viewCapability(context.seams.passportEncryptionKey) : void 0;
|
|
1724
|
-
return runGrantCall({
|
|
1725
|
-
config: {
|
|
1726
|
-
call: prepareRevokeAllGrants(),
|
|
1727
|
-
flow: {
|
|
1728
|
-
kind: "revoke-all-grants",
|
|
1729
|
-
account: AccAddress(account.address),
|
|
1730
|
-
...rotation ? { read: true } : {}
|
|
1731
|
-
},
|
|
1732
|
-
nextStoredGrants: () => void 0,
|
|
1733
|
-
writeBeforeCall: false,
|
|
1734
|
-
isSettled: (settled) => settled.length === 0,
|
|
1735
|
-
...rotation ? { rotation } : {}
|
|
1736
|
-
},
|
|
1737
|
-
input,
|
|
1738
|
-
ledger,
|
|
1739
|
-
roster,
|
|
1740
|
-
context
|
|
1741
|
-
});
|
|
1742
|
-
};
|
|
1743
|
-
//#endregion
|
|
1744
|
-
//#region ../../packages/module/passport-account/src/flows/holdings.ts
|
|
1745
|
-
/**
|
|
1746
|
-
* Reads the account's shielded holdings from public chain data: the
|
|
1747
|
-
* contract's inbox is walked with the account secrets this device's own
|
|
1748
|
-
* envelopes deliver, the one deriving the ledger's `enc_key` first and
|
|
1749
|
-
* the older ones after it, so a coin a racing sender or an interrupted
|
|
1750
|
-
* rotation left under an earlier key is still found, and each coin that
|
|
1751
|
-
* opens is kept only once its commitment is found among the outputs the
|
|
1752
|
-
* chain created for the account, with the tree index it took. The
|
|
1753
|
-
* envelopes are opened inside one key session, so the walk costs one
|
|
1754
|
-
* ceremony however many entries the inbox holds, and none at all when
|
|
1755
|
-
* it holds no coin to open; only the opened coin descriptions reach this
|
|
1756
|
-
* flow, never a secret. Nothing is written: no record, no chain call.
|
|
1757
|
-
*
|
|
1758
|
-
* A record whose address holds no live contract rejects with
|
|
1759
|
-
* AccountContractMissingError, and an inbox holding no envelope of this
|
|
1760
|
-
* device that opens the ledger's `enc_key` with EncryptionKeyMismatchError
|
|
1761
|
-
* rather than an inbox read as empty. An entry that fails to open or
|
|
1762
|
-
* describes a coin the chain never created is skipped and counted, never
|
|
1763
|
-
* an error; the account keys and grant bookkeeping the inbox also holds
|
|
1764
|
-
* are not coins and are not counted.
|
|
1765
|
-
*
|
|
1766
|
-
* Reports no intermediate stage: the ceremony and the indexer reads all
|
|
1767
|
-
* settle before there is anything to report.
|
|
1768
|
-
*/
|
|
1769
|
-
const readShieldedHoldings = async ({ account }, { seams }) => {
|
|
1770
|
-
const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
|
|
1771
|
-
const indexer = createIndexerClient({
|
|
1772
|
-
indexerUrl: passportNetwork.indexerUrl,
|
|
1773
|
-
indexerWsUrl: passportNetwork.indexerWsUrl
|
|
1774
|
-
});
|
|
1775
|
-
const ledgerState = await queryAccountLedger(indexer, account.address);
|
|
1776
|
-
const { holdings, skippedEntries } = await walkInbox({
|
|
1777
|
-
address: account.address,
|
|
1778
|
-
ledgerState,
|
|
1779
|
-
indexer,
|
|
1780
|
-
openEntries: async (entries) => inKeySession(passportAuthoriser, { kind: "read-holdings" }, async () => {
|
|
1781
|
-
const { keyring } = readAccountInbox(account.address, ledgerState, await passportEncryptionKey.readerPublicKey(passportNetwork.networkId));
|
|
1782
|
-
return (await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries)).map((entry) => entry?.kind === "coin" ? entry.coin : null);
|
|
1783
|
-
})
|
|
1784
|
-
});
|
|
1785
|
-
return {
|
|
1786
|
-
holdings,
|
|
1787
|
-
skippedEntries
|
|
1788
|
-
};
|
|
1789
|
-
};
|
|
1790
|
-
//#endregion
|
|
1791
|
-
//#region ../../packages/module/passport-account/src/flows/sign-in.ts
|
|
1792
|
-
/**
|
|
1793
|
-
* The account's whole device roster, read from the on-chain device set.
|
|
1794
|
-
* Entries are opaque rolling commitments, so a remote entry carries no
|
|
1795
|
-
* identity beyond its commitment: only the local device can be named, by
|
|
1796
|
-
* the commitment its own rescan resolved.
|
|
1797
|
-
*/
|
|
1798
|
-
const toDeviceRoster = (entries, localCommitmentHex) => Array.from(entries, (entry) => {
|
|
1799
|
-
const commitmentHex = HexBytes.fromByteArray(entry);
|
|
1800
|
-
return {
|
|
1801
|
-
commitmentHex,
|
|
1802
|
-
isLocal: commitmentHex === localCommitmentHex
|
|
1803
|
-
};
|
|
1804
|
-
});
|
|
1805
|
-
/**
|
|
1806
|
-
* Refuses a record whose creation is still unfinished on chain. A record
|
|
1807
|
-
* that carries a pending deployment names a contract that may still miss
|
|
1808
|
-
* verifier keys, its initial device or the entries that deliver the
|
|
1809
|
-
* account keys, which only the create flow can complete; but a creation
|
|
1810
|
-
* that appended its key entries and then died before its final record
|
|
1811
|
-
* write left a usable account behind, and the chain's boot flag and inbox
|
|
1812
|
-
* tell the two apart.
|
|
1813
|
-
*/
|
|
1814
|
-
const assertCreationComplete = (record, isActivated, entries) => {
|
|
1815
|
-
const { deployment } = record;
|
|
1816
|
-
if (deployment === void 0) return;
|
|
1817
|
-
const held = new Set(entries.map((entry) => HexBytes.fromByteArray(entry)));
|
|
1818
|
-
if (!isActivated || deployment.keyEntries.some((entry) => !held.has(entry))) throw new AccountCreationPendingError(`The account at ${record.address} is deployed but not yet ready; resume the creation on this device, from the site that started it.`);
|
|
1819
|
-
};
|
|
1820
|
-
/** The grant record parts the Lace records of an inbox opened to. */
|
|
1821
|
-
const grantPartsOf = (opened) => opened.flatMap((entry) => entry?.kind === "grant" ? [entry.record] : []);
|
|
1822
|
-
/**
|
|
1823
|
-
* Recognises an existing Passport account on this device: the passkey
|
|
1824
|
-
* ceremony (implicit in the first authoriser use) proves the device key,
|
|
1825
|
-
* the persisted account record names the Account Custody Contract, the
|
|
1826
|
-
* indexer confirms the contract is live, this device's own envelopes in
|
|
1827
|
-
* the contract's inbox open a secret deriving the `enc_key` the contract
|
|
1828
|
-
* advertises, and the use-counter rescan restores this device's roster
|
|
1829
|
-
* position from the on-chain device set. The account secret is never
|
|
1830
|
-
* read from the record or derived from the passkey: it is random, and
|
|
1831
|
-
* this device reaches it only through an envelope sealed to its reader
|
|
1832
|
-
* key, so a device enrolled with its own passkey signs in without the
|
|
1833
|
-
* record of the device that created the account. The roster is rebuilt
|
|
1834
|
-
* from every entry in the device set, so a device enrolled elsewhere
|
|
1835
|
-
* survives a reload; only the local entry is named, the rest are opaque
|
|
1836
|
-
* commitments. The grant roster is read from the contract's grants map
|
|
1837
|
-
* the same way, with the slots and openings the record kept for the
|
|
1838
|
-
* grants this client issued; a grant the record lacks, as after the
|
|
1839
|
-
* record was lost, is rebuilt from the grant records this device keeps in
|
|
1840
|
-
* the inbox once they open its on-chain commitments under this host's
|
|
1841
|
-
* relying party id, its seal kept only while it holds the live secret.
|
|
1842
|
-
* A grant the record holds is kept only while its fields open the
|
|
1843
|
-
* commitments the chain holds under its id, so an entry an issue wrote
|
|
1844
|
-
* before a call that never landed gives way to the grant another client
|
|
1845
|
-
* of the account issued over the same id, and a rebuilt grant takes its
|
|
1846
|
-
* place when there is one. The live read grants whose kept seal does not
|
|
1847
|
-
* hold the live secret, as after an issue that failed before its seal,
|
|
1848
|
-
* are reported as unsealed.
|
|
1849
|
-
* The devices registered under the live roster key are kept in the
|
|
1850
|
-
* record as the roster the next rotation seals to. The whole chain runs
|
|
1851
|
-
* inside one key session, so it costs a single ceremony rather than one
|
|
1852
|
-
* per authoriser call.
|
|
1853
|
-
*
|
|
1854
|
-
* The healed counter is written back to the persisted record before this
|
|
1855
|
-
* resolves, so the next sign-in rescans from a fresh anchor instead of the
|
|
1856
|
-
* creation-time one. That write also completes a record a creation left
|
|
1857
|
-
* pending after its key entries landed, dropping the deployment and the
|
|
1858
|
-
* authority key it carries, and drops the bookkeeping of grants the chain
|
|
1859
|
-
* no longer holds or whose commitments it does not open. A missing record
|
|
1860
|
-
* raises AccountNotFoundError, a record whose address holds no live
|
|
1861
|
-
* contract raises AccountContractMissingError,
|
|
1862
|
-
* a record whose creation is unfinished on chain raises
|
|
1863
|
-
* AccountCreationPendingError, and an inbox holding no envelope of this
|
|
1864
|
-
* device that opens the live `enc_key`, as for a device never handed the
|
|
1865
|
-
* keys or removed since, raises EncryptionKeyMismatchError before the
|
|
1866
|
-
* rescan runs; rediscovering an account by name service when no record is
|
|
1867
|
-
* persisted is a separate flow and out of scope here.
|
|
1868
|
-
*
|
|
1869
|
-
* signIn reports no intermediate stage: the ceremony, the indexer read and
|
|
1870
|
-
* the rescan all settle before there is anything to report.
|
|
1871
|
-
*/
|
|
1872
|
-
const signIn = async ({ seams, accountRecords, rpId }) => {
|
|
1873
|
-
const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
|
|
1874
|
-
return inKeySession(passportAuthoriser, { kind: "sign-in" }, async () => {
|
|
1875
|
-
const local = await readLocalDevice(seams);
|
|
1876
|
-
const record = await accountRecords.read();
|
|
1877
|
-
if (!record) throw new AccountNotFoundError("No Passport account record is stored on this device.");
|
|
1878
|
-
const state = await createIndexerClient({
|
|
1879
|
-
indexerUrl: passportNetwork.indexerUrl,
|
|
1880
|
-
indexerWsUrl: passportNetwork.indexerWsUrl
|
|
1881
|
-
}).queryContractState(record.address);
|
|
1882
|
-
if (!state) throw new AccountContractMissingError(`No live Account Custody Contract found at ${record.address}.`);
|
|
1883
|
-
const ledgerState = ledger(state.data);
|
|
1884
|
-
const { entries, keyring } = readAccountInbox(record.address, ledgerState, local.readerPublicKey);
|
|
1885
|
-
assertCreationComplete(record, ledgerState.booted, entries);
|
|
1886
|
-
if (keyring.envelopes.length === 0) throw new EncryptionKeyMismatchError(`The inbox of the account at ${record.address} holds no envelope for this device.`);
|
|
1887
|
-
const grantParts = grantPartsOf(await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries.filter(isLaceRecord)));
|
|
1888
|
-
const { useCounter, commitmentHex } = await resolveUseCounter({
|
|
1889
|
-
ledgerState,
|
|
1890
|
-
authoriser: passportAuthoriser,
|
|
1891
|
-
address: AccAddress(record.address),
|
|
1892
|
-
knownUseCounter: UseCounter(BigInt(record.localUseCounter))
|
|
1893
|
-
});
|
|
1894
|
-
const devices = toDeviceRoster(ledgerState.devices, commitmentHex);
|
|
1895
|
-
const encKey = HexBytes.fromByteArray(ledgerState.enc_key);
|
|
1896
|
-
const onChain = readGrants(ledgerState);
|
|
1897
|
-
const stored = withRebuiltGrants(await retainStoredGrants(record.grants, onChain, record.address), rpId === void 0 ? void 0 : await rebuildStoredGrants(grantParts, onChain, {
|
|
1898
|
-
address: record.address,
|
|
1899
|
-
origin: rpId,
|
|
1900
|
-
encKey
|
|
1901
|
-
}), encKey);
|
|
1902
|
-
const grants = readGrants(ledgerState, storedGrantSlots(stored), storedGrantOpenings(stored));
|
|
1903
|
-
const localUseCounter = useCounter.toString();
|
|
1904
|
-
await accountRecords.write(completedRecord(record, localUseCounter, { grants: stored }));
|
|
1905
|
-
return {
|
|
1906
|
-
account: {
|
|
1907
|
-
address: record.address,
|
|
1908
|
-
bindingVersion: record.bindingVersion,
|
|
1909
|
-
status: "ready",
|
|
1910
|
-
networkId: passportNetwork.networkId
|
|
1911
|
-
},
|
|
1912
|
-
devices,
|
|
1913
|
-
localUseCounter,
|
|
1914
|
-
grants,
|
|
1915
|
-
encKey,
|
|
1916
|
-
unsealedGrantIds: unsealedStoredReadGrants(stored, onChain, encKey)
|
|
1917
|
-
};
|
|
1918
|
-
});
|
|
1919
|
-
};
|
|
1920
|
-
//#endregion
|
|
1921
|
-
//#region ../../packages/module/passport-account/src/store/dependencies.ts
|
|
1922
|
-
/**
|
|
1923
|
-
* Runs a flow as a cold stream of its events. `run` is invoked once per
|
|
1924
|
-
* subscription with a progress sink that emits synchronously as the flow
|
|
1925
|
-
* reports; the resolution emits 'done' and completes, and a rejection
|
|
1926
|
-
* errors the stream. Unsubscribing only stops delivery: the running
|
|
1927
|
-
* promise is not cancelled, because a flow must run to completion once
|
|
1928
|
-
* started (abandoning it mid-deploy could orphan a sponsored contract).
|
|
1929
|
-
*/
|
|
1930
|
-
const observeFlow = (run) => new Observable((subscriber) => {
|
|
1931
|
-
let isDelivering = true;
|
|
1932
|
-
run((stage) => {
|
|
1933
|
-
if (isDelivering) subscriber.next({
|
|
1934
|
-
type: "progress",
|
|
1935
|
-
stage
|
|
1936
|
-
});
|
|
1937
|
-
}).then((result) => {
|
|
1938
|
-
if (!isDelivering) return;
|
|
1939
|
-
subscriber.next({
|
|
1940
|
-
type: "done",
|
|
1941
|
-
result
|
|
1942
|
-
});
|
|
1943
|
-
subscriber.complete();
|
|
1944
|
-
}, (error) => {
|
|
1945
|
-
if (isDelivering) subscriber.error(error);
|
|
1946
|
-
});
|
|
1947
|
-
return () => {
|
|
1948
|
-
isDelivering = false;
|
|
1949
|
-
};
|
|
1950
|
-
});
|
|
1951
|
-
/**
|
|
1952
|
-
* The context a flow runs under, drawn from the side-effect dependency
|
|
1953
|
-
* bag: the five seams, the record store and the relying party id. An
|
|
1954
|
-
* injected store is used as is; otherwise the store is built over the
|
|
1955
|
-
* platform storage and keyed by the authoriser, so a record sealed by one
|
|
1956
|
-
* authoriser is only ever written through it. The sealed-record options
|
|
1957
|
-
* apply to that built store; signIn and the grant flows pass
|
|
1958
|
-
* `onSealedWithoutKey: 'error'` so a sealed record found without a
|
|
1959
|
-
* storage key surfaces as record-unreadable rather than as an absent
|
|
1960
|
-
* account.
|
|
1961
|
-
*/
|
|
1962
|
-
const toFlowContext = ({ createKeyValueStorage, passportAuthoriser, passportEncryptionKey, passportNetwork, passportProver, passportSponsor }, onProgress, { store, rpId, ...recordOptions }) => ({
|
|
1963
|
-
seams: {
|
|
1964
|
-
passportAuthoriser,
|
|
1965
|
-
passportEncryptionKey,
|
|
1966
|
-
passportNetwork,
|
|
1967
|
-
passportProver,
|
|
1968
|
-
passportSponsor
|
|
1969
|
-
},
|
|
1970
|
-
accountRecords: toAccountRecords(store ?? createAccountRecordStore(createKeyValueStorage, passportAuthoriser.storageKey, recordOptions)),
|
|
1971
|
-
onProgress,
|
|
1972
|
-
rpId
|
|
1973
|
-
});
|
|
1974
|
-
/**
|
|
1975
|
-
* Maps the platform capabilities passed to `createPassportAccountModule`
|
|
1976
|
-
* onto the `passport-dependency` contract, and exposes the platform-neutral
|
|
1977
|
-
* flows observable-based. The flows are promise functions; wrapping them
|
|
1978
|
-
* here rather than in the side effects is what keeps promise handling out
|
|
1979
|
-
* of the side effects, so they compose the flows as plain streams and stay
|
|
1980
|
-
* testable on a virtual scheduler. The relying party id defaults to the
|
|
1981
|
-
* page's hostname, as the passkey ceremony's does.
|
|
1982
|
-
*/
|
|
1983
|
-
const createDependencies = ({ authoriser, encryptionKey, sponsor, prover, network, accountRecordStore, rpId = globalThis.location?.hostname }) => {
|
|
1984
|
-
const host = {
|
|
1985
|
-
store: accountRecordStore,
|
|
1986
|
-
rpId
|
|
1987
|
-
};
|
|
1988
|
-
const recordRequired = {
|
|
1989
|
-
...host,
|
|
1990
|
-
onSealedWithoutKey: "error"
|
|
1991
|
-
};
|
|
1992
|
-
return {
|
|
1993
|
-
passportAuthoriser: authoriser,
|
|
1994
|
-
passportEncryptionKey: encryptionKey,
|
|
1995
|
-
passportSponsor: sponsor,
|
|
1996
|
-
passportProver: prover,
|
|
1997
|
-
passportNetwork: network,
|
|
1998
|
-
passportFlows: {
|
|
1999
|
-
createAccount: (input, dependencies) => observeFlow(async (onProgress) => createAccount(input, toFlowContext(dependencies, onProgress, host))),
|
|
2000
|
-
signIn: (dependencies) => observeFlow(async (onProgress) => signIn(toFlowContext(dependencies, onProgress, recordRequired))),
|
|
2001
|
-
addDevice: (input, dependencies) => observeFlow(async (onProgress) => addDevice(input, toFlowContext(dependencies, onProgress, host))),
|
|
2002
|
-
removeDevice: (input, dependencies) => observeFlow(async (onProgress) => removeDevice(input, toFlowContext(dependencies, onProgress, host))),
|
|
2003
|
-
issueGrant: (input, dependencies) => observeFlow(async (onProgress) => issueGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
|
|
2004
|
-
revokeGrant: (input, dependencies) => observeFlow(async (onProgress) => revokeGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
|
|
2005
|
-
revokeAllGrants: (input, dependencies) => observeFlow(async (onProgress) => revokeAllGrants(input, toFlowContext(dependencies, onProgress, recordRequired))),
|
|
2006
|
-
readShieldedHoldings: (input, dependencies) => observeFlow(async (onProgress) => readShieldedHoldings(input, toFlowContext(dependencies, onProgress, host)))
|
|
2007
|
-
}
|
|
2008
|
-
};
|
|
2009
|
-
};
|
|
2010
|
-
//#endregion
|
|
2011
|
-
//#region ../../packages/module/passport-account/src/store/side-effects/flow-actions.ts
|
|
2012
|
-
/**
|
|
2013
|
-
* The flow error a failure reports: the error's own typed code when it
|
|
2014
|
-
* carries one, otherwise the flow's fallback code.
|
|
2015
|
-
*/
|
|
2016
|
-
const toFlowError = (error, fallbackCode) => ({
|
|
2017
|
-
code: error instanceof Object && "code" in error && typeof error.code === "string" ? error.code : fallbackCode,
|
|
2018
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2019
|
-
});
|
|
2020
|
-
/**
|
|
2021
|
-
* Maps a flow's events onto the passport flow state: each stage to
|
|
2022
|
-
* setFlow, the result to its publishing actions followed by
|
|
2023
|
-
* setFlow('ready'), and a failure to its `onFailed` actions followed by
|
|
2024
|
-
* a single setFlowError, so a flow always settles in 'ready' or 'error'.
|
|
2025
|
-
*/
|
|
2026
|
-
const flowActions = (events$, { actions, fallbackCode, onDone, onFailed = [] }) => events$.pipe(mergeMap((event) => event.type === "progress" ? [actions.passport.setFlow(event.stage)] : [...onDone(event.result), actions.passport.setFlow("ready")]), catchError((error) => of(...onFailed, actions.passport.setFlowError(toFlowError(error, fallbackCode)))));
|
|
2027
|
-
//#endregion
|
|
2028
|
-
//#region ../../packages/module/passport-account/src/store/side-effects/passport-flows.ts
|
|
2029
|
-
/** Flow error code for creation failures that carry no typed code of their own. */
|
|
2030
|
-
const CREATE_ACCOUNT_FALLBACK_ERROR_CODE = "account-create-failed";
|
|
2031
|
-
/** Flow error code for sign-in failures that carry no typed code of their own. */
|
|
2032
|
-
const SIGN_IN_FALLBACK_ERROR_CODE = "account-sign-in-failed";
|
|
2033
|
-
/** Flow error codes for device call failures that carry no typed code of their own. */
|
|
2034
|
-
const ADD_DEVICE_FALLBACK_ERROR_CODE = "device-add-failed";
|
|
2035
|
-
const REMOVE_DEVICE_FALLBACK_ERROR_CODE = "device-remove-failed";
|
|
2036
|
-
/** Flow error codes for grant call failures that carry no typed code of their own. */
|
|
2037
|
-
const ISSUE_GRANT_FALLBACK_ERROR_CODE = "grant-issue-failed";
|
|
2038
|
-
const REVOKE_GRANT_FALLBACK_ERROR_CODE = "grant-revoke-failed";
|
|
2039
|
-
const REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE = "grant-revoke-all-failed";
|
|
2040
|
-
/** Flow error code for holdings reads that carry no typed code of their own. */
|
|
2041
|
-
const READ_HOLDINGS_FALLBACK_ERROR_CODE = "holdings-read-failed";
|
|
2042
|
-
/**
|
|
2043
|
-
* Flows an account call may start from. 'error' is a settled state, not
|
|
2044
|
-
* an operation in progress, so a call that failed can be retried without
|
|
2045
|
-
* a fresh sign-in ceremony; every other non-ready flow is an operation
|
|
2046
|
-
* left unfinished, whose account and use counter cannot be trusted yet.
|
|
2047
|
-
*/
|
|
2048
|
-
const SETTLED_FLOWS = ["error", "ready"];
|
|
2049
|
-
/**
|
|
2050
|
-
* The actions that publish a roster a flow left behind: the devices, then
|
|
2051
|
-
* the counter the local row sits at. Both land ahead of 'ready', in the
|
|
2052
|
-
* order the flows have always published them.
|
|
2053
|
-
*/
|
|
2054
|
-
const publishDeviceRoster = (actions) => ({ devices, localUseCounter }) => [actions.passport.setDevices(devices), actions.passport.setLocalUseCounter(localUseCounter)];
|
|
2055
|
-
/**
|
|
2056
|
-
* The actions that publish what a grant call left behind: the device
|
|
2057
|
-
* roster and counter it advanced, the `enc_key` a rotation it ran stepped
|
|
2058
|
-
* the encryption key to and the devices it left without the new key, the
|
|
2059
|
-
* grant roster, then the read grants the
|
|
2060
|
-
* call could not seal the viewing secret to, empty when it sealed all,
|
|
2061
|
-
* and those the signer refused because their revocation is pending.
|
|
2062
|
-
*/
|
|
2063
|
-
const publishGrantRoster = (actions) => ({ grants, encKey, unsealedGrantIds, revocationPendingGrantIds, droppedDevices, ...roster }) => [
|
|
2064
|
-
...publishDeviceRoster(actions)(roster),
|
|
2065
|
-
...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
|
|
2066
|
-
...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)],
|
|
2067
|
-
actions.passport.setGrants(grants),
|
|
2068
|
-
actions.passport.setUnsealedGrantIds(unsealedGrantIds),
|
|
2069
|
-
actions.passport.setRevocationPendingGrantIds(revocationPendingGrantIds)
|
|
2070
|
-
];
|
|
2071
|
-
/**
|
|
2072
|
-
* The actions that publish an issued grant: the rosters, then the id of
|
|
2073
|
-
* the grant, last so a consumer reacting to it finds the record in state.
|
|
2074
|
-
*/
|
|
2075
|
-
const publishIssuedGrant = (actions) => ({ grantId, ...roster }) => [...publishGrantRoster(actions)(roster), actions.passport.setLastIssuedGrantId(grantId)];
|
|
2076
|
-
/**
|
|
2077
|
-
* The actions that publish an account a flow left ready: the account,
|
|
2078
|
-
* its device roster and counter, the `enc_key` its encryption key is
|
|
2079
|
-
* at, then its grant roster.
|
|
2080
|
-
*/
|
|
2081
|
-
const publishAccountState = (actions) => ({ account, grants, encKey, ...roster }) => [
|
|
2082
|
-
actions.passport.setAccount(account),
|
|
2083
|
-
...publishDeviceRoster(actions)(roster),
|
|
2084
|
-
actions.passport.setEncKey(encKey),
|
|
2085
|
-
actions.passport.setGrants(grants)
|
|
2086
|
-
];
|
|
2087
|
-
/**
|
|
2088
|
-
* The actions that publish an account a sign-in recognised: the account
|
|
2089
|
-
* state, see {@link publishAccountState}, then the read grants whose kept
|
|
2090
|
-
* seal does not hold the live viewing secret, and no grants pending a
|
|
2091
|
-
* revocation, which only a grant call learns from the signer.
|
|
2092
|
-
*/
|
|
2093
|
-
const publishRecognisedAccount = (actions) => ({ unsealedGrantIds, ...state }) => [
|
|
2094
|
-
...publishAccountState(actions)(state),
|
|
2095
|
-
actions.passport.setUnsealedGrantIds(unsealedGrantIds),
|
|
2096
|
-
actions.passport.setRevocationPendingGrantIds([])
|
|
2097
|
-
];
|
|
2098
|
-
/**
|
|
2099
|
-
* A device or grant call may rotate the account keys before it fails, so
|
|
2100
|
-
* the `enc_key` state holds may be the one rotated away: it is forgotten
|
|
2101
|
-
* until the next sign-in or rotation publishes it again, rather than let
|
|
2102
|
-
* a stale seal pass as current. The read grants an earlier call reported
|
|
2103
|
-
* unsealed or pending revocation were judged against that key, so they
|
|
2104
|
-
* are emptied with it.
|
|
2105
|
-
*/
|
|
2106
|
-
const forgetKeyState = (actions) => [
|
|
2107
|
-
actions.passport.clearEncKey(),
|
|
2108
|
-
actions.passport.setUnsealedGrantIds([]),
|
|
2109
|
-
actions.passport.setRevocationPendingGrantIds([])
|
|
2110
|
-
];
|
|
2111
|
-
/**
|
|
2112
|
-
* The createAccount flow behind the createAccount action, as the actions it
|
|
2113
|
-
* publishes: each reported stage as setFlow, then the created account, its
|
|
2114
|
-
* one-device roster, this device's counter and its empty grant roster,
|
|
2115
|
-
* then 'ready'. The flow itself persists the account record before it
|
|
2116
|
-
* resolves, so the account is never published ahead of the record that
|
|
2117
|
-
* recognises it.
|
|
2118
|
-
*/
|
|
2119
|
-
const runCreateAccount = (input, dependencies) => {
|
|
2120
|
-
const { actions, passportFlows } = dependencies;
|
|
2121
|
-
return flowActions(passportFlows.createAccount(input, dependencies), {
|
|
2122
|
-
actions,
|
|
2123
|
-
fallbackCode: CREATE_ACCOUNT_FALLBACK_ERROR_CODE,
|
|
2124
|
-
onDone: publishAccountState(actions)
|
|
2125
|
-
});
|
|
2126
|
-
};
|
|
2127
|
-
/**
|
|
2128
|
-
* The signIn flow behind the signIn action, as the actions it publishes:
|
|
2129
|
-
* the recognised account, its whole device roster, this device's healed
|
|
2130
|
-
* use counter, the grant roster and its unsealed read grants, then
|
|
2131
|
-
* 'ready'. The flow itself persists the healed counter before it
|
|
2132
|
-
* resolves, so the record is never left behind the state it publishes.
|
|
2133
|
-
*/
|
|
2134
|
-
const runSignIn = (dependencies) => {
|
|
2135
|
-
const { actions, passportFlows } = dependencies;
|
|
2136
|
-
return flowActions(passportFlows.signIn(dependencies), {
|
|
2137
|
-
actions,
|
|
2138
|
-
fallbackCode: SIGN_IN_FALLBACK_ERROR_CODE,
|
|
2139
|
-
onDone: publishRecognisedAccount(actions)
|
|
2140
|
-
});
|
|
2141
|
-
};
|
|
2142
|
-
/**
|
|
2143
|
-
* The actions that publish what a device call left behind: its roster and
|
|
2144
|
-
* counter, then, when it rotated the account keys, the `enc_key` it
|
|
2145
|
-
* stepped to and the devices it left without the new key.
|
|
2146
|
-
*/
|
|
2147
|
-
const publishDeviceCall = (actions) => ({ encKey, droppedDevices, ...roster }) => [
|
|
2148
|
-
...publishDeviceRoster(actions)(roster),
|
|
2149
|
-
...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
|
|
2150
|
-
...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)]
|
|
2151
|
-
];
|
|
2152
|
-
/**
|
|
2153
|
-
* The addDevice flow behind its action: what the call left behind, see
|
|
2154
|
-
* {@link publishDeviceCall}, then 'ready'. The joining device's keys
|
|
2155
|
-
* travel with the request when the action names them.
|
|
2156
|
-
*/
|
|
2157
|
-
const addDeviceCall = {
|
|
2158
|
-
start: ({ commitmentHex, reader }, input, dependencies) => dependencies.passportFlows.addDevice({
|
|
2159
|
-
commitmentHex,
|
|
2160
|
-
...reader ? { reader } : {},
|
|
2161
|
-
...input
|
|
2162
|
-
}, dependencies),
|
|
2163
|
-
onDone: publishDeviceCall,
|
|
2164
|
-
fallbackCode: ADD_DEVICE_FALLBACK_ERROR_CODE,
|
|
2165
|
-
onFailed: forgetKeyState
|
|
2166
|
-
};
|
|
2167
|
-
/**
|
|
2168
|
-
* The removeDevice flow behind its action: what the call left behind, see
|
|
2169
|
-
* {@link publishDeviceCall}, then 'ready'.
|
|
2170
|
-
*/
|
|
2171
|
-
const removeDeviceCall = {
|
|
2172
|
-
start: ({ commitmentHex }, input, dependencies) => dependencies.passportFlows.removeDevice({
|
|
2173
|
-
commitmentHex,
|
|
2174
|
-
...input
|
|
2175
|
-
}, dependencies),
|
|
2176
|
-
onDone: publishDeviceCall,
|
|
2177
|
-
fallbackCode: REMOVE_DEVICE_FALLBACK_ERROR_CODE,
|
|
2178
|
-
onFailed: forgetKeyState
|
|
2179
|
-
};
|
|
2180
|
-
/**
|
|
2181
|
-
* The issueGrant flow behind its action: the rosters the call left
|
|
2182
|
-
* behind and the issued grant's id, then 'ready'.
|
|
2183
|
-
*/
|
|
2184
|
-
const issueGrantCall = {
|
|
2185
|
-
start: (scope, input, dependencies) => dependencies.passportFlows.issueGrant({
|
|
2186
|
-
scope,
|
|
2187
|
-
...input
|
|
2188
|
-
}, dependencies),
|
|
2189
|
-
onDone: publishIssuedGrant,
|
|
2190
|
-
fallbackCode: ISSUE_GRANT_FALLBACK_ERROR_CODE,
|
|
2191
|
-
onFailed: forgetKeyState
|
|
2192
|
-
};
|
|
2193
|
-
/** The revokeGrant flow behind its action: the rosters it left behind, then 'ready'. */
|
|
2194
|
-
const revokeGrantCall = {
|
|
2195
|
-
start: ({ grantId }, input, dependencies) => dependencies.passportFlows.revokeGrant({
|
|
2196
|
-
grantId,
|
|
2197
|
-
...input
|
|
2198
|
-
}, dependencies),
|
|
2199
|
-
onDone: publishGrantRoster,
|
|
2200
|
-
fallbackCode: REVOKE_GRANT_FALLBACK_ERROR_CODE,
|
|
2201
|
-
onFailed: forgetKeyState
|
|
2202
|
-
};
|
|
2203
|
-
/** The revokeAllGrants flow behind its action: the rosters it left behind, then 'ready'. */
|
|
2204
|
-
const revokeAllGrantsCall = {
|
|
2205
|
-
start: (_payload, input, dependencies) => dependencies.passportFlows.revokeAllGrants(input, dependencies),
|
|
2206
|
-
onDone: publishGrantRoster,
|
|
2207
|
-
fallbackCode: REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE,
|
|
2208
|
-
onFailed: forgetKeyState
|
|
2209
|
-
};
|
|
2210
|
-
/**
|
|
2211
|
-
* The readShieldedHoldings flow behind its action: the holdings the walk
|
|
2212
|
-
* verified, then 'ready'. The skip count is the flow's to report, not the
|
|
2213
|
-
* store's to keep: a skipped entry is noise the inbox will carry forever.
|
|
2214
|
-
*/
|
|
2215
|
-
const readShieldedHoldingsCall = {
|
|
2216
|
-
start: (_payload, input, dependencies) => dependencies.passportFlows.readShieldedHoldings(input, dependencies),
|
|
2217
|
-
onDone: (actions) => ({ holdings }) => [actions.passport.setShieldedHoldings(holdings)],
|
|
2218
|
-
fallbackCode: READ_HOLDINGS_FALLBACK_ERROR_CODE
|
|
2219
|
-
};
|
|
2220
|
-
/**
|
|
2221
|
-
* An account call behind its action, as the actions it publishes: what
|
|
2222
|
-
* the call left behind, then 'ready', or, when it fails, the actions
|
|
2223
|
-
* undoing what state may wrongly claim, then the error. The flow itself
|
|
2224
|
-
* persists the counter before it resolves.
|
|
2225
|
-
*
|
|
2226
|
-
* Requires an account and a settled flow: with no account in state, or
|
|
2227
|
-
* with an operation left unfinished, the flow is never called (so the
|
|
2228
|
-
* chain and the key session are never touched) and the call settles in
|
|
2229
|
-
* 'error' with 'no-account'. The refusal still runs behind the gate, so
|
|
2230
|
-
* it enters 'ceremony' first like any accepted request and clears the
|
|
2231
|
-
* error a previous flow left behind.
|
|
2232
|
-
*/
|
|
2233
|
-
const runAccountCall = ({ start, onDone, fallbackCode, onFailed }, { payload, flow, account, devices, localUseCounter }, dependencies) => {
|
|
2234
|
-
const { actions } = dependencies;
|
|
2235
|
-
if (!account || !SETTLED_FLOWS.includes(flow)) return of(actions.passport.setFlowError(toFlowError(new NoAccountError(), fallbackCode)));
|
|
2236
|
-
return flowActions(start(payload, {
|
|
2237
|
-
account,
|
|
2238
|
-
devices,
|
|
2239
|
-
localUseCounter
|
|
2240
|
-
}, dependencies), {
|
|
2241
|
-
actions,
|
|
2242
|
-
fallbackCode,
|
|
2243
|
-
onDone: onDone(actions),
|
|
2244
|
-
...onFailed ? { onFailed: onFailed(actions) } : {}
|
|
2245
|
-
});
|
|
2246
|
-
};
|
|
2247
|
-
/**
|
|
2248
|
-
* One start per dispatched account call action, each carrying the state
|
|
2249
|
-
* the call needs. State is sampled when the action arrives rather than
|
|
2250
|
-
* when the shared gate starts the call, so a call reads the account,
|
|
2251
|
-
* roster and counter the user acted on.
|
|
2252
|
-
*/
|
|
2253
|
-
const accountCallStarts = ({ config, trigger$, stateObservables: { passport: { selectAccount$, selectDevices$, selectFlow$, selectLocalUseCounter$ } }, dependencies }) => trigger$.pipe(withLatestFrom(selectFlow$, selectAccount$, selectDevices$, selectLocalUseCounter$), map(([{ payload }, flow, account, devices, localUseCounter]) => () => runAccountCall(config, {
|
|
2254
|
-
payload,
|
|
2255
|
-
flow,
|
|
2256
|
-
account,
|
|
2257
|
-
devices,
|
|
2258
|
-
localUseCounter
|
|
2259
|
-
}, dependencies)));
|
|
2260
|
-
/**
|
|
2261
|
-
* Runs whichever passport flow was requested: {@link runCreateAccount}
|
|
2262
|
-
* behind the createAccount action, {@link runSignIn} behind signIn, and
|
|
2263
|
-
* {@link runAccountCall} behind addDevice, removeDevice, issueGrant,
|
|
2264
|
-
* revokeGrant, revokeAllGrants and readShieldedHoldings.
|
|
2265
|
-
*
|
|
2266
|
-
* Every flow owns the same account record, the same device key and the
|
|
2267
|
-
* same flow state, and each chain call resolves the account's use counter
|
|
2268
|
-
* and auth nonce, so they must never overlap: two calls resolving the same
|
|
2269
|
-
* counter both spend the same entry, one transaction is rejected, and the
|
|
2270
|
-
* loser reports its own failure over state the winner already moved on.
|
|
2271
|
-
* A holdings read spends no entry, but it opens the same key session and
|
|
2272
|
-
* reports through the same flow state, so it runs behind the same gate.
|
|
2273
|
-
* One exhaustMap over the merged starts, rather than one per action,
|
|
2274
|
-
* makes them mutually exclusive: a dispatch arriving while any flow is in
|
|
2275
|
-
* flight emits nothing at all, and the next one is only accepted once the
|
|
2276
|
-
* running flow settles. Dropping it silently is the stance exhaustMap
|
|
2277
|
-
* already took for a repeated dispatch of one action; a flow the user
|
|
2278
|
-
* never saw start is not a failure to report.
|
|
2279
|
-
*
|
|
2280
|
-
* Because only the gate knows which requests it accepted, it also owns
|
|
2281
|
-
* the entry into 'ceremony': the trigger reducers are pure, so a dropped
|
|
2282
|
-
* request cannot overwrite the running flow's state, and setFlow clears
|
|
2283
|
-
* the error a previous flow left behind. The emission is synchronous on
|
|
2284
|
-
* the accepted start, so a consumer sees the ceremony within the same
|
|
2285
|
-
* dispatch cycle as the trigger.
|
|
2286
|
-
*
|
|
2287
|
-
* The flow state cannot carry this guard the other way round: state
|
|
2288
|
-
* reaches a side effect before the action does, and an inner flow is
|
|
2289
|
-
* still subscribed while its own terminal setFlow('ready') is delivered,
|
|
2290
|
-
* so a reducer reading the flow back cannot tell a request the gate will
|
|
2291
|
-
* accept from one it will drop.
|
|
2292
|
-
*/
|
|
2293
|
-
const runPassportFlow = ({ passport: { addDevice$, createAccount$, issueGrant$, readShieldedHoldings$, removeDevice$, revokeAllGrants$, revokeGrant$, signIn$ } }, stateObservables, dependencies) => merge(createAccount$.pipe(map(({ payload }) => () => runCreateAccount(payload, dependencies))), signIn$.pipe(map(() => () => runSignIn(dependencies))), accountCallStarts({
|
|
2294
|
-
config: addDeviceCall,
|
|
2295
|
-
trigger$: addDevice$,
|
|
2296
|
-
stateObservables,
|
|
2297
|
-
dependencies
|
|
2298
|
-
}), accountCallStarts({
|
|
2299
|
-
config: removeDeviceCall,
|
|
2300
|
-
trigger$: removeDevice$,
|
|
2301
|
-
stateObservables,
|
|
2302
|
-
dependencies
|
|
2303
|
-
}), accountCallStarts({
|
|
2304
|
-
config: issueGrantCall,
|
|
2305
|
-
trigger$: issueGrant$,
|
|
2306
|
-
stateObservables,
|
|
2307
|
-
dependencies
|
|
2308
|
-
}), accountCallStarts({
|
|
2309
|
-
config: revokeGrantCall,
|
|
2310
|
-
trigger$: revokeGrant$,
|
|
2311
|
-
stateObservables,
|
|
2312
|
-
dependencies
|
|
2313
|
-
}), accountCallStarts({
|
|
2314
|
-
config: revokeAllGrantsCall,
|
|
2315
|
-
trigger$: revokeAllGrants$,
|
|
2316
|
-
stateObservables,
|
|
2317
|
-
dependencies
|
|
2318
|
-
}), accountCallStarts({
|
|
2319
|
-
config: readShieldedHoldingsCall,
|
|
2320
|
-
trigger$: readShieldedHoldings$,
|
|
2321
|
-
stateObservables,
|
|
2322
|
-
dependencies
|
|
2323
|
-
})).pipe(exhaustMap((start) => concat(of(dependencies.actions.passport.setFlow("ceremony")), start())));
|
|
2324
|
-
//#endregion
|
|
2325
|
-
//#region ../../packages/module/passport-account/src/store/side-effects/restore-account.ts
|
|
2326
|
-
/** Whether a stored record names an activated account rather than a deployment in progress. */
|
|
2327
|
-
const isActivatedRecord = (record) => record !== void 0 && record.deployment === void 0;
|
|
2328
|
-
/**
|
|
2329
|
-
* The grant roster of the restored account, read from chain with the
|
|
2330
|
-
* slots the record kept. The read needs no ceremony, but it does need
|
|
2331
|
-
* the indexer: a contract it does not serve, or a read it fails, leaves
|
|
2332
|
-
* the roster empty rather than failing the restore, and the next sign-in
|
|
2333
|
-
* or grant call reads it again.
|
|
2334
|
-
*/
|
|
2335
|
-
const restoreGrants = (record, { indexerUrl, indexerWsUrl }, actions) => defer(async () => createIndexerClient({
|
|
2336
|
-
indexerUrl,
|
|
2337
|
-
indexerWsUrl
|
|
2338
|
-
}).queryContractState(record.address)).pipe(filter((state) => state !== null && state !== void 0), map((state) => actions.passport.setGrants(readGrants(ledger(state.data), storedGrantSlots(record.grants)))), catchError(() => EMPTY));
|
|
2339
|
-
/**
|
|
2340
|
-
* Rehydrates the persisted account record into the store on startup, so a
|
|
2341
|
-
* returning user's account is recognised without a new passkey ceremony.
|
|
2342
|
-
* Only a record whose activation succeeded is restored, hence status
|
|
2343
|
-
* 'ready'; a record still carrying a pending deployment names no usable
|
|
2344
|
-
* account and is left for the create flow to resume, or for sign-in to
|
|
2345
|
-
* complete once the chain shows it activated. The device set is not
|
|
2346
|
-
* persisted and is rebuilt from chain by the device-management flows; the
|
|
2347
|
-
* grant roster is read from chain here, once the account is published,
|
|
2348
|
-
* so a consumer lists grants without a ceremony. A record sealed by a
|
|
2349
|
-
* passkey authoriser cannot be opened without a ceremony, and a ceremony
|
|
2350
|
-
* cannot run without a user gesture, so no storage key is passed here: a
|
|
2351
|
-
* sealed record reads as absent and the sign-in flow recognises the
|
|
2352
|
-
* account instead.
|
|
2353
|
-
*/
|
|
2354
|
-
const restorePassportAccount = (_actionObservables, _stateObservables, { actions, createKeyValueStorage, passportNetwork }) => createAccountRecordStore(createKeyValueStorage).read().pipe(filter(isActivatedRecord), mergeMap((record) => concat(of(actions.passport.setAccount({
|
|
2355
|
-
address: record.address,
|
|
2356
|
-
bindingVersion: record.bindingVersion,
|
|
2357
|
-
status: "ready",
|
|
2358
|
-
networkId: passportNetwork.networkId
|
|
2359
|
-
}), actions.passport.setLocalUseCounter(record.localUseCounter)), restoreGrants(record, passportNetwork, actions))));
|
|
2360
|
-
//#endregion
|
|
2361
|
-
//#region ../../packages/module/passport-account/src/store/init.ts
|
|
2362
|
-
/**
|
|
2363
|
-
* Builds the passport-account module's store initializer for the given
|
|
2364
|
-
* platform capabilities: the mutually exclusive passport flows behind the
|
|
2365
|
-
* createAccount, signIn, addDevice, removeDevice, issueGrant, revokeGrant
|
|
2366
|
-
* and revokeAllGrants actions, and the restore of a previously created
|
|
2367
|
-
* account from its persisted record.
|
|
2368
|
-
*/
|
|
2369
|
-
const createInit = (props) => () => ({
|
|
2370
|
-
sideEffects: [runPassportFlow, restorePassportAccount],
|
|
2371
|
-
sideEffectDependencies: createDependencies(props)
|
|
2372
|
-
});
|
|
2373
|
-
//#endregion
|
|
2374
|
-
export { createInit };
|