@laceio/sdk 0.1.0 → 0.1.1-canary.0
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/{Ed25519PublicKey-lvu1s54U.cjs → Address-AMKTo-dA.cjs} +17150 -61
- package/dist/{Ed25519PublicKey-CjnuAYRR.js → Address-_c_-9hLC.js} +16955 -264
- package/dist/{Bip32Account-r3KAhCgU.js → Bip32Account-CaW1zC9Y.js} +4 -5
- package/dist/{Bip32Account-Cz3h0ZUa.cjs → Bip32Account-DOaGmKpv.cjs} +4 -5
- package/dist/Cardano-B-gyqBBD.js +311 -0
- package/dist/Cardano-BQ_af0Y3.cjs +322 -0
- package/dist/Ed25519PublicKey-B3QcR0J2.cjs +88 -0
- package/dist/Ed25519PublicKey-DIE19xHg.js +83 -0
- package/dist/InMemoryKeyAgent-Bc_xZY-v.cjs +652 -0
- package/dist/InMemoryKeyAgent-D8T3vtXy.js +631 -0
- package/dist/{PaymentAddress-DhbtuYb7.cjs → PaymentAddress-B4n1KZMx.cjs} +1 -2
- package/dist/{PaymentAddress-xSmGUQ-8.js → PaymentAddress-Cml3EA1n.js} +1 -2
- package/dist/Percent-Cfuw5YyL.cjs +11 -0
- package/dist/Percent-DpvyJ_93.js +5 -0
- package/dist/SodiumBip32Ed25519-BaD-kjyx.cjs +380 -0
- package/dist/SodiumBip32Ed25519-DCikAMsl.js +363 -0
- package/dist/{TransactionBuilder-CeJTNzm-.cjs → TransactionBuilder-BakTJvsJ.cjs} +59 -89
- package/dist/{TransactionBuilder-DGNDM4kt.js → TransactionBuilder-D2ErwVWv.js} +5 -71
- package/dist/_md-6zV2Cme0.cjs +684 -0
- package/dist/_md-gbUqlhqd.js +399 -0
- package/dist/_web-crypto-random-bytes-BvzwTYPG.js +197 -0
- package/dist/acc-module-NL_8mEHH.cjs +27654 -0
- package/dist/acc-module-ur8CkBd1.js +27624 -0
- package/dist/account-call-B7cF8ly4.cjs +293 -0
- package/dist/account-call-BF4rBcNw.js +3 -0
- package/dist/account-call-CoCq3V55.js +252 -0
- package/dist/account-call-DFcD257E.cjs +4 -0
- package/dist/account-guards-BeKbV9D1.cjs +41 -0
- package/dist/account-guards-qnFXuXSE.js +36 -0
- package/dist/{activity-type.stable-ClO9j_PH.cjs → activity-type-BPXxdHBP.cjs} +2 -1
- package/dist/{activity-type.stable-Ml0SOYv9.js → activity-type-C1geyPsH.js} +2 -1
- package/dist/address-OxwXIwbF.js +15502 -0
- package/dist/address-book-address-validator-BqYM0Uyp.cjs +41 -0
- package/dist/{address-book-address-validator-BhqRfmlL.cjs → address-book-address-validator-CGIcqn5W.cjs} +5 -4
- package/dist/{address-book-address-validator-VqIOB8PY.js → address-book-address-validator-FkkYim3z.js} +6 -4
- package/dist/address-book-address-validator-RSx2Jozy.js +39 -0
- package/dist/address-lECYBd9-.cjs +13043 -0
- package/dist/{apply-vkey-witnesses-Ctzk7eye.cjs → apply-vkey-witnesses-1jyczTkw.cjs} +1 -1
- package/dist/{apply-vkey-witnesses-Dfo7XfhL.js → apply-vkey-witnesses-Byb40-yJ.js} +1 -1
- package/dist/artefact-loader-Cqc9eSkL.cjs +182 -0
- package/dist/artefact-loader-D0T2f6F7.js +159 -0
- package/dist/auto-dismiss-failure-on-success-BWJceNXd.cjs +37 -0
- package/dist/auto-dismiss-failure-on-success-Bpod-v4r.js +31 -0
- package/dist/bip32-EPpcFrpH.cjs +3851 -0
- package/dist/{bip32ed25519-D2In8WzR.cjs → bip32ed25519-CRiG-Kjg.cjs} +5 -4
- package/dist/{bip32ed25519-qsLH_eWO.js → bip32ed25519-E-fCUZNv.js} +6 -4
- package/dist/bip39-DZomfqeY.js +9 -0
- package/dist/bip39-MAck9iod.cjs +26 -0
- package/dist/bitcoin-data-provider-D0OT_Uiu.cjs +26 -0
- package/dist/bitcoin-data-provider-LN_8oamW.js +21 -0
- package/dist/bitcoin.cjs +48 -0
- package/dist/bitcoin.d.ts +29805 -1
- package/dist/bitcoin.js +22 -0
- package/dist/{blake2b-BGHUWMsV.js → blake2b-BL-eR5G2.js} +6 -4
- package/dist/{blake2b-DspUqw68.cjs → blake2b-Iy324FwH.cjs} +5 -4
- package/dist/{blockfrost-tx-submit-provider-BUEOmdvc.js → blockfrost-tx-submit-provider-BHDLgOIE.js} +9 -162
- package/dist/{blockfrost-tx-submit-provider-B5dWbdrZ.cjs → blockfrost-tx-submit-provider-BxC19smv.cjs} +7 -171
- package/dist/{blockingWithLatestFrom-BhaE7pnA.js → blockingWithLatestFrom-FUfZFEUG.js} +1 -1
- package/dist/{blockingWithLatestFrom-DkuT3G2a.cjs → blockingWithLatestFrom-bmCPgA3c.cjs} +1 -1
- package/dist/bn-BFq5a4dk.cjs +2553 -0
- package/dist/{SodiumBip32Ed25519-BEfo1Q9m.js → browser-D5jRKv_L.js} +4 -401
- package/dist/bytes.vo-CziaOsba.js +24 -0
- package/dist/bytes.vo-hecRu3YZ.cjs +35 -0
- package/dist/{cardano-network-id-to-network-type-PXvn1ycQ.cjs → cardano-network-id-to-network-type-BwG_d6QP.cjs} +1 -1
- package/dist/{cardano-network-id-to-network-type-DHgKhDAK.js → cardano-network-id-to-network-type-CYJheetp.js} +1 -1
- package/dist/{cardano-network-id.vo-CbNNKd-k.cjs → cardano-network-id.vo-C5VuBXT3.cjs} +2 -2
- package/dist/{cardano-network-id.vo-CTYe-Oix.js → cardano-network-id.vo-CeLJqmb_.js} +2 -2
- package/dist/{cardano-tx-id.vo-BMwemq5x.js → cardano-tx-id.vo-DJ70NC0-.js} +1 -1
- package/dist/{cardano-tx-id.vo-NY-gCOWJ.cjs → cardano-tx-id.vo-lnyO9XaW.cjs} +1 -1
- package/dist/cardano.cjs +31 -24
- package/dist/cardano.d.ts +27602 -8121
- package/dist/cardano.js +21 -13
- package/dist/client-Bw2foG5E.cjs +808 -0
- package/dist/const-BB3MccLt.js +153 -0
- package/dist/{const-BErUlcZw.cjs → const-BWBAzOmN.cjs} +1 -1
- package/dist/const-CSWmOyzn.cjs +42 -0
- package/dist/{const-Dy8Ctnpd.cjs → const-D3mCkwrM.cjs} +5 -5
- package/dist/const-DW7z3R5k.js +13 -0
- package/dist/{const-DeUA7zYl.js → const-DbuUHSzR.js} +5 -5
- package/dist/const-DgBzKfz6.cjs +31995 -0
- package/dist/{const-DVW6DGkZ.js → const-xGSNCtwQ.js} +1 -1
- package/dist/contract-BsIGhsYa.js +171 -0
- package/dist/contract-DD6CGnpB.cjs +189 -0
- package/dist/create-add-accounts-2mQA0ST2.js +102 -0
- package/dist/create-add-accounts-B6mR2NMn.cjs +114 -0
- package/dist/{delegation-tx-builder-CF0VunDW.cjs → delegation-tx-builder-Cp30jtj9.cjs} +9 -8
- package/dist/{delegation-tx-builder-C8LQxU2R.js → delegation-tx-builder-DUMikSTg.js} +9 -7
- package/dist/{deregistration-tx-builder-DcUXA3Ls.cjs → deregistration-tx-builder-B-ZeY-dR.cjs} +10 -9
- package/dist/{deregistration-tx-builder-BOBPP8m_.js → deregistration-tx-builder-CRLrK99U.js} +10 -8
- package/dist/device-commitment-hex.vo-BIUYUNz1.cjs +31 -0
- package/dist/device-commitment-hex.vo-D9FsYLn7.js +26 -0
- package/dist/device-key-BToCoS4D.js +2394 -0
- package/dist/device-key-D6euvxFi.cjs +2489 -0
- package/dist/device-readers-BHJeNR8I.js +3 -0
- package/dist/device-readers-Dw7uTNM5.cjs +167 -0
- package/dist/device-readers-b4g2j3QL.js +144 -0
- package/dist/device-readers-cxcJcWq5.cjs +7 -0
- package/dist/dist-B00GN41m.js +141 -0
- package/dist/dist-BA1xnF9O.cjs +4413 -0
- package/dist/dist-BmHOXLe8.cjs +3814 -0
- package/dist/dist-Br0OVXKt.cjs +535 -0
- package/dist/dist-C1JK9C1I.cjs +3452 -0
- package/dist/dist-DKEViNEI.cjs +1306 -0
- package/dist/dist-Dwqx184a.cjs +43175 -0
- package/dist/dist-MJof_Ky1.cjs +145 -0
- package/dist/dist-p84sVVJ9.cjs +1394 -0
- package/dist/{earn-rewards-tx-builder-BRlTueHS.js → earn-rewards-tx-builder-8G_rouIk.js} +9 -7
- package/dist/{earn-rewards-tx-builder-RgO0QUY8.cjs → earn-rewards-tx-builder-cOUNV58E.cjs} +9 -8
- package/dist/enc-key-circuits-0Dud78IB.js +13 -0
- package/dist/enc-key-circuits-yjPHbuo_.cjs +24 -0
- package/dist/english-BZ_ebp2-.cjs +2057 -0
- package/dist/envelopes-B3QCzo5p.js +2658 -0
- package/dist/envelopes-BdE1xlgz.cjs +2777 -0
- package/dist/errors-BA3b8aVu.cjs +584 -0
- package/dist/errors-pkQdxW-7.js +392 -0
- package/dist/experimental.cjs +6820 -0
- package/dist/experimental.d.ts +63552 -0
- package/dist/experimental.js +6670 -0
- package/dist/fees-6C2uEWFK.js +653 -0
- package/dist/fees-DtC71fOx.cjs +820 -0
- package/dist/gated-call-BrPjKolT.js +3 -0
- package/dist/gated-call-CcsuW9L5.js +691 -0
- package/dist/gated-call-DdDrxEq5.cjs +732 -0
- package/dist/gated-call-Dk7PazhG.cjs +4 -0
- package/dist/grant-rules-B7eDuD5G.cjs +5468 -0
- package/dist/grant-rules-qLrKUIR3.js +5217 -0
- package/dist/grant-status-CDmZpx32.js +296 -0
- package/dist/grant-status-CQtwjLsA.cjs +337 -0
- package/dist/grants-Bnb-3Hkv.cjs +16 -0
- package/dist/grants-CIb6nOV7.cjs +273 -0
- package/dist/grants-CYY0AquW.js +202 -0
- package/dist/grants-DOsFSotP.js +4 -0
- package/dist/{group-cardano-addresses-by-account-C-1GHUu8.cjs → group-cardano-addresses-by-account-BGOtPV-Y.cjs} +4 -40
- package/dist/{group-cardano-addresses-by-account-p4dXmCoj.js → group-cardano-addresses-by-account-TqvESg9x.js} +6 -37
- package/dist/{guarded-transaction-signer-DZSGHp1K.js → guarded-transaction-signer-BbVDE9Nn.js} +12 -18
- package/dist/{guarded-transaction-signer-BLSezbfV.cjs → guarded-transaction-signer-DTLjAkif.cjs} +26 -26
- package/dist/hex-BVtOKJki.cjs +263 -0
- package/dist/hex-CGYJPK_Z.js +192 -0
- package/dist/http-client-B7_kBTYn.js +155 -0
- package/dist/http-client-DYQ_7Rdf.cjs +167 -0
- package/dist/identity-DxPhxkZs.js +239 -0
- package/dist/identity-y41Yle03.cjs +274 -0
- package/dist/in-memory-wallet-integration-5BKiAvPT.js +20861 -0
- package/dist/{in-memory-wallet-integration-CUTa68ET.js → in-memory-wallet-integration-BCOqDgbt.js} +10 -107
- package/dist/in-memory-wallet-integration-Bi8I9Dso.js +128 -0
- package/dist/in-memory-wallet-integration-Bsl2ONua.cjs +239 -0
- package/dist/in-memory-wallet-integration-DZ0CAyvY.cjs +20865 -0
- package/dist/{in-memory-wallet-integration-Dw_S7amX.cjs → in-memory-wallet-integration-DsFm8gLk.cjs} +21 -112
- package/dist/{in-memory-wallet-integration-Z4Bp_DTv.cjs → in-memory-wallet-integration-aW_SF-go.cjs} +8 -5
- package/dist/{in-memory-wallet-integration-B6cbAlvD.js → in-memory-wallet-integration-mIEc8EfR.js} +9 -5
- package/dist/index.cjs +95 -58
- package/dist/index.d.ts +45152 -13448
- package/dist/index.js +55 -28
- package/dist/{init-CVi8J0zk.cjs → init-5SfWgCYD.cjs} +3 -3
- package/dist/{init-Ci7aqWCf2.cjs → init-8NVWbo302.cjs} +2 -2
- package/dist/init-B-C7n6u6.js +5 -0
- package/dist/{init-BFMWRf-L.js → init-B-ppfGO-.js} +1 -1
- package/dist/{init-IiR093Jw.cjs → init-B2OUhY5T.cjs} +2 -2
- package/dist/{init-DXnus199.cjs → init-B6Eg_o6G.cjs} +5 -5
- package/dist/{init-4hDl5bpr.js → init-BAWMHL1k.js} +2 -2
- package/dist/init-BC2CZZOu2.cjs +2002 -0
- package/dist/{init-a228hXnS.cjs → init-BF-a2p6E.cjs} +65 -73
- package/dist/{init-fFirX8OY.cjs → init-BFxlQAD8.cjs} +1 -1
- package/dist/init-BJE1ry0O.js +17 -0
- package/dist/{init-BVnI4z2J.js → init-BPKBVOHU.js} +2 -2
- package/dist/{init-bdqZBd-o.js → init-BSP_xcpB.js} +2 -2
- package/dist/{init-CFNOn3lo.js → init-BSd7mznK.js} +3 -3
- package/dist/{init-MjR4swi6.js → init-BUJTT0MN.js} +36 -43
- package/dist/init-BUpFzYsD.cjs +143 -0
- package/dist/{init-BvpYHyLH.cjs → init-BZ-WyAIL.cjs} +3 -3
- package/dist/init-BZdfdLX7.js +422 -0
- package/dist/{init-CJeOys1w.cjs → init-B_HWNXMD.cjs} +2 -2
- package/dist/init-BbIMt6xU.cjs +6 -0
- package/dist/{init-BYdN1aqE.cjs → init-BewG6swt.cjs} +2 -2
- package/dist/{init-BX0AZAVH2.js → init-Bilqsbng.js} +2 -2
- package/dist/{init-Tpn9VkFu.cjs → init-BpwlQ6wF.cjs} +1 -1
- package/dist/{init-D5-P0HXK.js → init-BtQOzUjX.js} +1 -1
- package/dist/{init-BvGz3qUd2.js → init-CCTscPaY2.js} +2 -2
- package/dist/{init-D4hPipCD.cjs → init-CH0yyBof.cjs} +1 -1
- package/dist/init-CaIkrXal.cjs +121 -0
- package/dist/init-CkZy4IMf.cjs +107 -0
- package/dist/{init-Drr2f_Pr.js → init-CmfcY7SV.js} +1 -1
- package/dist/{init-Kf3_OLv6.js → init-CnJAlks_.js} +1 -1
- package/dist/{init-Cdjt_z-b.cjs → init-CqqmtAIK.cjs} +4 -4
- package/dist/{init-Bb0avZeZ.js → init-Cu2znmIc.js} +5 -5
- package/dist/init-Cz5LAizN.js +2001 -0
- package/dist/init-D4WM3-Mn.cjs +18 -0
- package/dist/init-DMi2FGpf.cjs +424 -0
- package/dist/{init-lcaUSOOG.js → init-DPTJohPd.js} +1 -1
- package/dist/{init-BTYLMF9F2.cjs → init-DexLSprL2.cjs} +258 -61
- package/dist/init-DfKPgD21.js +106 -0
- package/dist/init-DgzRsI-C.js +141 -0
- package/dist/init-Dj4Cn7-P.js +4789 -0
- package/dist/init-DktAnYI0.cjs +2354 -0
- package/dist/{init-DFwhgert.cjs → init-DmyuM9Lo.cjs} +1 -1
- package/dist/{init-CX_zLXc6.js → init-IBnizp1U.js} +5 -5
- package/dist/init-MDKB6tGI2.cjs +4790 -0
- package/dist/{init-DbZ5byj42.js → init-TbB38KNt2.js} +258 -60
- package/dist/init-X6m_cqDy.js +2353 -0
- package/dist/{init-DvVgKCaP2.cjs → init-bzvl1JhF.cjs} +2 -2
- package/dist/{init-BgrY29gW.js → init-dsYkC_0B.js} +4 -4
- package/dist/{init-B5BsI3DR.cjs → init-kvn5FlPS.cjs} +7 -7
- package/dist/init-vfy9DW_E.js +119 -0
- package/dist/{init-BE_6Jyc4.js → init-w7xl6LsP.js} +3 -3
- package/dist/{init-DEsWVyn5.cjs → init-wgiBUmyW.cjs} +1 -1
- package/dist/{is-retriable-error-DWv_ob-f.js → is-retriable-error-CmFR20pe.js} +1 -1
- package/dist/{is-retriable-error-Dtb4JGjd.cjs → is-retriable-error-D2yo0_lS.cjs} +1 -1
- package/dist/jubjub-authoriser-A2c8vmkN.js +84 -0
- package/dist/jubjub-authoriser-D3ZDgzcF.cjs +85 -0
- package/dist/key-source-CcLTx8HK.js +2761 -0
- package/dist/key-source-D2tTAag-.cjs +806 -0
- package/dist/{key-value-store-DTX1quRS.js → key-value-store-DZcNDB3n.js} +1 -1
- package/dist/{key-value-store-B-XRIwaM.cjs → key-value-store-Htj4RgMe.cjs} +1 -1
- package/dist/keyDerivation-DcKRUCVh.cjs +274 -0
- package/dist/keyDerivation-QGesIMbS.js +4077 -0
- package/dist/{lace-context-FpU3iG3f.cjs → lace-context-BN0b_ury.cjs} +4 -4
- package/dist/{lace-context-BQvvxVLm.js → lace-context-DMRJ87Vg.js} +4 -4
- package/dist/lib-BC-62Byh.cjs +2359 -0
- package/dist/lib-JYqcAX0c.js +2354 -0
- package/dist/midnight-account-id.vo--Vcx34Xk.cjs +10 -0
- package/dist/midnight-account-id.vo-BuB8fiVO.js +5 -0
- package/dist/midnight-engine-B7D2hbHo.js +5 -0
- package/dist/midnight-engine-D5ikN_5F.cjs +6 -0
- package/dist/midnight-wallet-BOdFD6SX.cjs +24 -0
- package/dist/midnight-wallet-CAqZrNVe.js +18 -0
- package/dist/midnight.cjs +1412 -0
- package/dist/midnight.d.ts +28609 -0
- package/dist/midnight.js +1355 -0
- package/dist/modules-cardano-BiL9hoM3.js +996 -0
- package/dist/modules-cardano-UeXks3sd.cjs +1109 -0
- package/dist/node-relay-BOuvdZvC.cjs +49 -0
- package/dist/node-relay-CLtc5n1v.js +48 -0
- package/dist/outputs-5qdW8s-f.js +196 -0
- package/dist/outputs-BEM1GPMf.cjs +339 -0
- package/dist/{SodiumBip32Ed25519-BQUcrQWX.cjs → pbkdf2-B1veireP.cjs} +6 -411
- package/dist/pending-operation-CfTUNQVD.cjs +26 -0
- package/dist/pending-operation-Ljijb1Uh.js +21 -0
- package/dist/providers-1on_fwJO.cjs +364 -0
- package/dist/providers-C1MsxfMA.js +341 -0
- package/dist/redacted-4SJPqt-G.js +81 -0
- package/dist/redacted-DwxIDdIF.cjs +86 -0
- package/dist/{reselect-DPML1uFN.cjs → reselect-kP4DaAII.cjs} +1 -1
- package/dist/{retry-config-oNtfdyrG.cjs → retry-config-BGv5q5QM.cjs} +1 -1
- package/dist/{retry-config-VdbuqBsI.js → retry-config-Bu5Yn5ua.js} +1 -1
- package/dist/safe-buffer-CqGpSFCQ.js +43 -0
- package/dist/safe-buffer-DbXbwhUQ.cjs +48 -0
- package/dist/sdk-1SRu8Ype.cjs +1445 -0
- package/dist/sdk-BneJm7ey.js +1249 -0
- package/dist/sdk-init-9TG1ZvHF.cjs +3795 -0
- package/dist/sdk-init-Ce3mD-8F.js +1047 -0
- package/dist/secret-box-CSmRp6Tt.js +3777 -0
- package/dist/secret-box-ZfbYwBgS.cjs +6970 -0
- package/dist/send-proof-progress-DtAcrMP6.cjs +33 -0
- package/dist/send-proof-progress-Dx2cUz1B.js +22 -0
- package/dist/sha2-DaRJZOjk.cjs +390 -0
- package/dist/sha2-c4sB6peD.js +378 -0
- package/dist/side-effects-C3BhLrdP.js +768 -0
- package/dist/side-effects-DG0R-KFc.cjs +804 -0
- package/dist/sign-bitcoin-transaction-with-seed-C7vq9om8.js +147 -0
- package/dist/sign-bitcoin-transaction-with-seed-Ds1dwzan.cjs +158 -0
- package/dist/signer--2I6poMD.js +2 -0
- package/dist/signer-CX21Ups_.cjs +121 -0
- package/dist/signer-Cnn9q9oY.js +92 -0
- package/dist/signer-DbSxjk0F.cjs +7 -0
- package/dist/signer-factory-B5oFuUZ-.cjs +130 -0
- package/dist/signer-factory-BAt8FPNc.cjs +281 -0
- package/dist/signer-factory-BGE4nL4k.js +129 -0
- package/dist/signer-factory-C4pgsiJi.js +281 -0
- package/dist/{signer-factory-DySc1wRX.cjs → signer-factory-D95jQIPV.cjs} +9 -6
- package/dist/{signer-factory-D4HrwFPl.js → signer-factory-PrpsGX-8.js} +10 -6
- package/dist/{slice-BZTQgYSs.js → slice-B-xJcuT1.js} +4 -4
- package/dist/slice-B4QOYaOG.cjs +105 -0
- package/dist/{slice-CtIMw2YT.cjs → slice-B9QKuhi0.cjs} +3 -3
- package/dist/slice-BKnGkAkd.cjs +9031 -0
- package/dist/{slice-Bhwmqn1p.cjs → slice-Bf4enJKZ.cjs} +8 -8
- package/dist/{slice-DaNVz7_l.js → slice-ByNWnxKS.js} +8 -8
- package/dist/{slice-C2UDQrdt.js → slice-C1zOeseG.js} +449 -355
- package/dist/{slice-DFLyKRm5.cjs → slice-C4toRM6A.cjs} +2 -2
- package/dist/{slice-Cx4dULDA.cjs → slice-C8YVv4Qn.cjs} +1 -1
- package/dist/{slice-jnpOgiSZ.cjs → slice-CaMfviYu.cjs} +3 -3
- package/dist/{slice-DFvrwUur.js → slice-CgeRsYRg.js} +6 -6
- package/dist/slice-Cwt8jShq.js +9013 -0
- package/dist/{slice-zNEspZND.js → slice-D0VzpMbY.js} +2 -2
- package/dist/slice-D14F78Ca.js +87 -0
- package/dist/{slice-F6A1YKOF.cjs → slice-DLflttAu.cjs} +479 -366
- package/dist/{slice-C96EdeY1.js → slice-DcO-iNz2.js} +1 -1
- package/dist/{slice-C19quNWM.cjs → slice-DimtxfAT.cjs} +2 -2
- package/dist/{slice-KiNoGwD9.js → slice-DlTEqIoI.js} +1 -1
- package/dist/{slice-ws8PccRX.js → slice-Dn0WojD7.js} +3 -3
- package/dist/{slice-BLSxLmie.cjs → slice-Dongunf7.cjs} +4 -4
- package/dist/{slice-B6vPum19.js → slice-DpufJAjJ.js} +2 -2
- package/dist/{slice-exhhmucR.cjs → slice-DtitVc9L.cjs} +7 -7
- package/dist/{slice-Bjj2t2ut.cjs → slice-WMMMc1Xv.cjs} +2 -2
- package/dist/slice-a8VuIqzS.cjs +378 -0
- package/dist/slice-bRASyM-m.js +354 -0
- package/dist/{slice-CBFrgHSH.js → slice-hNyKbDry.js} +3 -3
- package/dist/{InMemoryKeyAgent-pyB2gA-N.cjs → src-BayYMIjw.cjs} +20 -8228
- package/dist/{InMemoryKeyAgent-D-3HejxN.js → src-CgINPlSx.js} +29 -4811
- package/dist/store-AUlTfcI_.js +37 -0
- package/dist/store-C7AneHEY.cjs +66 -0
- package/dist/submit-BuBCVi-l.cjs +87 -0
- package/dist/submit-D37lxFrK.js +70 -0
- package/dist/submit-bitcoin-tx-DFLnhidO.js +983 -0
- package/dist/submit-bitcoin-tx-sih2hSfb.cjs +1049 -0
- package/dist/{subtractValueQuantities-DfZg6oiG.js → subtractValueQuantities-BuDJVxq6.js} +3 -22
- package/dist/{subtractValueQuantities-C8qDTtgH.cjs → subtractValueQuantities-CqR606HV.cjs} +2 -32
- package/dist/{sync-operation-utils-BrQyWFS_.cjs → sync-operation-utils--Z_IQfyW.cjs} +0 -66
- package/dist/{sync-operation-utils-CAaCGvEN.js → sync-operation-utils-BimBpvI3.js} +1 -37
- package/dist/token-color.vo-CItgoOjw.js +26 -0
- package/dist/token-color.vo-Ca4hUXfh.cjs +37 -0
- package/dist/token-id-mapper-BVau2RXa.js +16 -0
- package/dist/token-id-mapper-CSeLzM4o.cjs +18 -0
- package/dist/{token-id-mapper-pDaTxpw2.js → token-id-mapper-ClBeIdJR.js} +2 -2
- package/dist/{token-id-mapper-DYcd8hhQ.cjs → token-id-mapper-D0wFdbOy.cjs} +2 -2
- package/dist/types-BhzypKMS.cjs +15 -0
- package/dist/types-DTE1ctDB.js +10 -0
- package/dist/types-DXSxXGz0.js +12 -0
- package/dist/types-Dls6Z5gV.cjs +23 -0
- package/dist/{util-P4PX2muj.cjs → util-D1YlKzrc.cjs} +2 -2
- package/dist/{util-CQSJU0wT.js → util-DHxtzUzY.js} +2 -2
- package/dist/{utils-Bv4OHT4S.cjs → utils-DYf4iZKX.cjs} +7 -1
- package/dist/{utils-DrtzChYN.js → utils-DcnoETEP.js} +2 -2
- package/dist/utils-Du0YXRcU.cjs +112 -0
- package/dist/utils-fF_rnZe3.js +65 -0
- package/dist/v8Signatures-ClGpA8mS.cjs +71 -0
- package/dist/{vote-delegation-tx-builder-69gOEkCB.cjs → vote-delegation-tx-builder-BjZxCOd3.cjs} +9 -8
- package/dist/{vote-delegation-tx-builder-BRJ_pf-c.js → vote-delegation-tx-builder-DRJwPXkD.js} +9 -7
- package/dist/watch-FGE4B_Ge.js +13056 -0
- package/dist/watch-imlsRZDp.cjs +52207 -0
- package/package.json +95 -3
- package/dist/Address-4KjeDt_x.cjs +0 -17269
- package/dist/Address-_VPkf7Xh.js +0 -17101
- package/dist/account-guards.stable-BKyYPj4z.cjs +0 -10
- package/dist/account-guards.stable-BdmQ43vC.js +0 -5
- package/dist/init-BR9ZIbED.cjs +0 -378
- package/dist/init-B_k1RUN_.cjs +0 -21
- package/dist/init-Bs3b5ZQH.js +0 -378
- package/dist/init-ChfcrHgG2.cjs +0 -4354
- package/dist/init-D1uUMMpG.js +0 -19
- package/dist/init-DV1OSZiK.js +0 -4352
- package/dist/modules-cardano-BINzpXpk.js +0 -5454
- package/dist/modules-cardano-Dt8OwoQb.cjs +0 -5640
- package/dist/slice-Caf98m0d2.js +0 -107
- package/dist/slice-PM6llajQ2.cjs +0 -125
- /package/dist/{BigIntMath-CoJWXt1h.cjs → BigIntMath-CBciEQn2.cjs} +0 -0
- /package/dist/{BigIntMath-BWUJOsOW.js → BigIntMath-X3EniYDo.js} +0 -0
- /package/dist/{ChainId-sdLmlIRk.cjs → ChainId-CNPSU9nn.cjs} +0 -0
- /package/dist/{ChainId-CKbm87yQ.js → ChainId-CeoG8Sov.js} +0 -0
- /package/dist/{_polyfill-node.process-4fnlkbBi.cjs → _polyfill-node.process-5UxyjoHG.cjs} +0 -0
- /package/dist/{account-id.vo-Xpgnu-av.cjs → account-id.vo-DYZBZQsz.cjs} +0 -0
- /package/dist/{account-id.vo-DP0BtOoF.js → account-id.vo-KDTWJ-W5.js} +0 -0
- /package/dist/{actions-BNagEO6m.cjs → actions-BnHFVSr_.cjs} +0 -0
- /package/dist/{actions-BWTyAr7B.js → actions-Cwj0ApZf.js} +0 -0
- /package/dist/{asyncoption-po7-RiA5.cjs → asyncoption-CYcZNBRM.cjs} +0 -0
- /package/dist/{asyncoption-CzIVH5Y1.js → asyncoption-DZQGfznq.js} +0 -0
- /package/dist/{bignum.vo-DhhZ9HP1.js → bignum.vo-Co6tMH2q.js} +0 -0
- /package/dist/{bignum.vo-CpkrIXjH.cjs → bignum.vo-Du-bpvar.cjs} +0 -0
- /package/dist/{blockchain-network-id.vo-CbgrAY_K.js → blockchain-network-id.vo-C6Jowh8t.js} +0 -0
- /package/dist/{blockchain-network-id.vo-5iC6Kffo.cjs → blockchain-network-id.vo-DEQ14dYj.cjs} +0 -0
- /package/dist/{composite-signer-factory-DABpCe1_.js → composite-signer-factory-D9RWY-1V.js} +0 -0
- /package/dist/{composite-signer-factory-BizuNPP7.cjs → composite-signer-factory-v4Pxn8bA.cjs} +0 -0
- /package/dist/{error-ggOihIfv.cjs → error-B6wj9nPv.cjs} +0 -0
- /package/dist/{error-DHKO2iS4.js → error-CvEYL98P.js} +0 -0
- /package/dist/{esm-browser-DnXNvpcn.cjs → esm-browser-CZbMeVod.cjs} +0 -0
- /package/dist/{esm-browser-BBVQuyQv.js → esm-browser-DpAD_RKv.js} +0 -0
- /package/dist/{feature-flag-key.vo-DoGK5Bfh.js → feature-flag-key.vo-9reURsOQ.js} +0 -0
- /package/dist/{feature-flag-key.vo-CP1Z9s8e.cjs → feature-flag-key.vo-KTIdnzYX.cjs} +0 -0
- /package/dist/{first-state-of-status-Bx4-QQ5m.cjs → first-state-of-status-9ICeDNSa.cjs} +0 -0
- /package/dist/{first-state-of-status-D7XzEuSa.js → first-state-of-status-BscbKj_t.js} +0 -0
- /package/dist/{init-4319psUJ.cjs → init-BT67pd9x.cjs} +0 -0
- /package/dist/{init-AFUM1TSg.js → init-CHk1fvJ2.js} +0 -0
- /package/dist/{init-ZX90nEJe2.js → init-DIyNx6DZ.js} +0 -0
- /package/dist/{init-CTQ2g6Xt.js → init-DtxxFaCx.js} +0 -0
- /package/dist/{init-Dt1Xifg_2.cjs → init-J8QobR93.cjs} +0 -0
- /package/dist/{init-BNewLCqw.cjs → init-xzZPlQwB.cjs} +0 -0
- /package/dist/{is-redux-persist-state-DxJSsvxB.cjs → is-redux-persist-state-C5luvPEH.cjs} +0 -0
- /package/dist/{is-redux-persist-state-Bl2n4VMr.js → is-redux-persist-state-CjcqyCoh.js} +0 -0
- /package/dist/{isNotNil-CZxBMfL5.js → isNotNil-Be7gCY0m.js} +0 -0
- /package/dist/{isNotNil-C_SO1tcC.cjs → isNotNil-CrYIUFry.cjs} +0 -0
- /package/dist/{logging-BvMDKgl0.js → logging-BAMMfn5j.js} +0 -0
- /package/dist/{logging-Bh9ikph3.cjs → logging-CbxgIX9V.cjs} +0 -0
- /package/dist/{metadatum-B-sxBnSY.cjs → metadatum-BGvFqGPt.cjs} +0 -0
- /package/dist/{metadatum-Ego4jVas.js → metadatum-BVocMYy_.js} +0 -0
- /package/dist/{repo-slice-BzZQUTj8.cjs → repo-slice-BB_W-6OL.cjs} +0 -0
- /package/dist/{repo-slice-vCcQJotS.js → repo-slice-BQlssw9M.js} +0 -0
- /package/dist/{reselect-BPkMzw3k.js → reselect-JIgawvPe.js} +0 -0
- /package/dist/{serializable-BxFUShKY.js → serializable-F7OtYl8d.js} +0 -0
- /package/dist/{serializable-DWgkHhv_.cjs → serializable-n3UoxbZc.cjs} +0 -0
- /package/dist/{slice-DY-DNtSi.js → slice-5dGBAz6w.js} +0 -0
- /package/dist/{slice-BEFoeqgr.cjs → slice-BzJgg9FS.cjs} +0 -0
- /package/dist/{src-CPmX4KUh.cjs → src-CeBNkonz.cjs} +0 -0
- /package/dist/{src-XOZXPKxe.js → src-Cv1ifqbe.js} +0 -0
- /package/dist/{time-CQPt4FdU.js → time-ByoKevdZ.js} +0 -0
- /package/dist/{time-Blvoyh9G.cjs → time-qD7edGU0.cjs} +0 -0
- /package/dist/{time.vo-Xln4yGBU.js → time.vo-C17-tYq3.js} +0 -0
- /package/dist/{time.vo-rwWwJzmP.cjs → time.vo-TmYE-_Y2.cjs} +0 -0
- /package/dist/{toEmpty-CzmoZVLN.cjs → toEmpty-Db6V94Mf.cjs} +0 -0
- /package/dist/{toEmpty-DzLd-ZCi.js → toEmpty-Djj-QbpI.js} +0 -0
- /package/dist/{token-id.vo-l8fuSpem.cjs → token-id.vo-CZ622zcZ.cjs} +0 -0
- /package/dist/{token-id.vo-DMKSaMlc.js → token-id.vo-IDI9C_Qm.js} +0 -0
- /package/dist/{types-B61DQoFW.js → types-By-XeXWg.js} +0 -0
- /package/dist/{types-CUC-Kcqh.cjs → types-Cn6WlQV6.cjs} +0 -0
- /package/dist/{util-BV3iGOrz.cjs → util-Cp1h58uD.cjs} +0 -0
- /package/dist/{util-BsQKLorV.js → util-DHppXj7U.js} +0 -0
- /package/dist/{util-selector-CKIyoAOz.js → util-selector-BWmHpo92.js} +0 -0
- /package/dist/{util-selector-C9H5RsNw.cjs → util-selector-glXkO-jR.cjs} +0 -0
- /package/dist/{while-active-CNXeLxre.js → while-active-CDBnUlQA.js} +0 -0
- /package/dist/{while-active-BsDWSNrO.cjs → while-active-I2TlHtm7.cjs} +0 -0
|
@@ -0,0 +1,2777 @@
|
|
|
1
|
+
const require_bytes_vo = require("./bytes.vo-hecRu3YZ.cjs");
|
|
2
|
+
const require_grant_rules = require("./grant-rules-B7eDuD5G.cjs");
|
|
3
|
+
const require_errors = require("./errors-BA3b8aVu.cjs");
|
|
4
|
+
//#region ../../packages/contract/passport/src/grants.ts
|
|
5
|
+
/**
|
|
6
|
+
* Most active grants Lace lets an account hold at once; the contract sets
|
|
7
|
+
* no limit.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_GRANTS = 16;
|
|
10
|
+
const DECIMAL_PATTERN = /^\d+$/;
|
|
11
|
+
const BYTES32_HEX_PATTERN = /^[0-9a-f]{64}$/;
|
|
12
|
+
const UINT128_MAX = (1n << 128n) - 1n;
|
|
13
|
+
const BYTE_BITS$2 = 8n;
|
|
14
|
+
/** The prime of Curve25519, the bound a canonical X25519 encoding stays under. */
|
|
15
|
+
const CURVE25519_PRIME = (1n << 255n) - 19n;
|
|
16
|
+
/**
|
|
17
|
+
* The twelve X25519 public keys of small order, as lowercase hex: the
|
|
18
|
+
* points a shared secret with them is all zero or predictable, which the
|
|
19
|
+
* scoped grants standard refuses as a read key. The last seven are also
|
|
20
|
+
* non-canonical encodings of the first five.
|
|
21
|
+
*/
|
|
22
|
+
const LOW_ORDER_READ_KEYS = new Set([
|
|
23
|
+
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
24
|
+
"0100000000000000000000000000000000000000000000000000000000000000",
|
|
25
|
+
"e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800",
|
|
26
|
+
"5f9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f1157",
|
|
27
|
+
"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
|
28
|
+
"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
|
29
|
+
"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
|
|
30
|
+
"cdeb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b880",
|
|
31
|
+
"4c9c95bca3508c24b1d0b1559c83ef5b04445cc4581c8e86d8224eddd09f11d7",
|
|
32
|
+
"d9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
33
|
+
"daffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
34
|
+
"dbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
|
35
|
+
]);
|
|
36
|
+
const requireBytes32Hex = (field, value) => {
|
|
37
|
+
if (!BYTES32_HEX_PATTERN.test(value)) throw new require_errors.GrantScopeInvalidError(`${field} must be 32 bytes as lowercase hex`);
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The 32-byte little-endian integer a hex X25519 public key encodes.
|
|
41
|
+
*/
|
|
42
|
+
const decodeLittleEndian = (hex) => {
|
|
43
|
+
let value = 0n;
|
|
44
|
+
for (let index = hex.length - 2; index >= 0; index -= 2) value = value << BYTE_BITS$2 | BigInt(Number.parseInt(hex.slice(index, index + 2), 16));
|
|
45
|
+
return value;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Whether a hex X25519 public key is one the account seals secrets to: 32
|
|
49
|
+
* bytes of lowercase hex in the canonical encoding of a coordinate below
|
|
50
|
+
* the curve prime, and none of the small-order points, whose shared
|
|
51
|
+
* secret is all zero or predictable and would seal to nobody. A grant's
|
|
52
|
+
* read key and a device's reader key are both held to it.
|
|
53
|
+
*/
|
|
54
|
+
const isSealableX25519Key = (value) => BYTES32_HEX_PATTERN.test(value) && !LOW_ORDER_READ_KEYS.has(value) && decodeLittleEndian(value) < CURVE25519_PRIME;
|
|
55
|
+
/**
|
|
56
|
+
* Holds a read public key to what the scoped grants standard admits, see
|
|
57
|
+
* {@link isSealableX25519Key}.
|
|
58
|
+
*
|
|
59
|
+
* @throws {@link GrantScopeInvalidError} when the key is malformed,
|
|
60
|
+
* non-canonical or of small order.
|
|
61
|
+
*/
|
|
62
|
+
const requireReadPublicKey = (value) => {
|
|
63
|
+
requireBytes32Hex("read public key", value);
|
|
64
|
+
if (LOW_ORDER_READ_KEYS.has(value)) throw new require_errors.GrantScopeInvalidError("read public key has small order");
|
|
65
|
+
if (!isSealableX25519Key(value)) throw new require_errors.GrantScopeInvalidError("read public key is not a canonical X25519 encoding");
|
|
66
|
+
};
|
|
67
|
+
const parseUint128 = (field, value) => {
|
|
68
|
+
if (!DECIMAL_PATTERN.test(value)) throw new require_errors.GrantScopeInvalidError(`${field} must be a non-negative decimal integer, got "${value}"`);
|
|
69
|
+
const amount = BigInt(value);
|
|
70
|
+
if (amount > UINT128_MAX) throw new require_errors.GrantScopeInvalidError(`${field} exceeds the 128-bit maximum`);
|
|
71
|
+
return amount;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Checks a requested grant against the rules the contract and the account
|
|
75
|
+
* impose, so a doomed request fails before any key ceremony or proof.
|
|
76
|
+
*
|
|
77
|
+
* @throws {@link GrantLimitReachedError} when the account already holds
|
|
78
|
+
* MAX_GRANTS active grants.
|
|
79
|
+
* @throws {@link GrantScopeInvalidError} when the scope breaks a rule: no
|
|
80
|
+
* operation, a malformed key, recipient or caller, a malformed amount, a cap
|
|
81
|
+
* below the per-call cap, an expiry that is not a whole second in the future,
|
|
82
|
+
* a rolling window, which a JavaScript caller can still pass, a read grant
|
|
83
|
+
* without an rp id, or a read grant without a canonical, large-order read
|
|
84
|
+
* public key.
|
|
85
|
+
*/
|
|
86
|
+
const validateGrantScope = (scope, { nowSeconds, grantCount }) => {
|
|
87
|
+
if (grantCount >= 16) throw new require_errors.GrantLimitReachedError();
|
|
88
|
+
if (scope.operations.length === 0) throw new require_errors.GrantScopeInvalidError("A grant must allow at least one operation");
|
|
89
|
+
requireBytes32Hex("grantee public key", scope.grantee.publicKey);
|
|
90
|
+
if (scope.recipient.kind !== "any") requireBytes32Hex("recipient value", scope.recipient.value);
|
|
91
|
+
if (scope.caller !== void 0) requireBytes32Hex("caller", scope.caller);
|
|
92
|
+
parseUint128("maxCoinValue", scope.maxCoinValue);
|
|
93
|
+
const perCallCap = parseUint128("perCallCap", scope.perCallCap);
|
|
94
|
+
if (parseUint128("cap", scope.cap) < perCallCap) throw new require_errors.GrantScopeInvalidError("cap must not be below the per-call cap");
|
|
95
|
+
if (scope.expiresAt !== null) {
|
|
96
|
+
if (!Number.isInteger(scope.expiresAt)) throw new require_errors.GrantScopeInvalidError("expiresAt must be a whole number of seconds");
|
|
97
|
+
if (scope.expiresAt <= nowSeconds) throw new require_errors.GrantScopeInvalidError("expiresAt must be in the future");
|
|
98
|
+
}
|
|
99
|
+
if ("window" in scope) throw new require_errors.GrantScopeInvalidError("A grant carries no rolling window: the contract reserves the window bounds");
|
|
100
|
+
if (scope.read && !scope.rpId) throw new require_errors.GrantScopeInvalidError("A read grant requires an rp id");
|
|
101
|
+
if (scope.read) {
|
|
102
|
+
if (scope.readPublicKey === void 0) throw new require_errors.GrantScopeInvalidError("A read grant requires the read public key the viewing secret is sealed to");
|
|
103
|
+
requireReadPublicKey(scope.readPublicKey);
|
|
104
|
+
} else if (scope.readPublicKey !== void 0) throw new require_errors.GrantScopeInvalidError("A read public key is only given with a read grant");
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/abstract/fft.js
|
|
108
|
+
function checkU32(n) {
|
|
109
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 4294967295) throw new Error("wrong u32 integer:" + n);
|
|
110
|
+
return n;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @param n - Input value.
|
|
114
|
+
* @returns Next power of two within the u32/array-length domain.
|
|
115
|
+
* @throws If `n` is not a valid unsigned 32-bit integer. {@link Error}
|
|
116
|
+
* @example
|
|
117
|
+
* Round an integer up to the FFT size it needs.
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* nextPowerOfTwo(9);
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
function nextPowerOfTwo(n) {
|
|
124
|
+
checkU32(n);
|
|
125
|
+
if (n <= 1) return 1;
|
|
126
|
+
if (n > 2147483648) throw new Error("nextPowerOfTwo overflow: result does not fit u32");
|
|
127
|
+
return 1 << log2(n - 1) + 1 >>> 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Similar to `bitLen(x)-1` but much faster for small integers, like indices.
|
|
131
|
+
* @param n - Input value.
|
|
132
|
+
* @returns Base-2 logarithm. For `n = 0`, the current implementation returns `-1`.
|
|
133
|
+
* @throws If `n` is not a valid unsigned 32-bit integer. {@link Error}
|
|
134
|
+
* @example
|
|
135
|
+
* Compute the radix-2 stage count for one transform size.
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* log2(8);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
function log2(n) {
|
|
142
|
+
checkU32(n);
|
|
143
|
+
return 31 - Math.clz32(n);
|
|
144
|
+
}
|
|
145
|
+
function poly(field, roots, create, fft, length) {
|
|
146
|
+
const F = field;
|
|
147
|
+
const _create = create || ((len, elm) => new Array(len).fill(elm ?? F.ZERO));
|
|
148
|
+
const isPoly = (x) => {
|
|
149
|
+
if (Array.isArray(x)) return true;
|
|
150
|
+
if (!ArrayBuffer.isView(x)) return false;
|
|
151
|
+
const v = x;
|
|
152
|
+
return typeof v.length === "number" && typeof v.slice === "function" && typeof v[Symbol.iterator] === "function";
|
|
153
|
+
};
|
|
154
|
+
const checkLength = (...lst) => {
|
|
155
|
+
if (!lst.length) return 0;
|
|
156
|
+
for (const i of lst) if (!isPoly(i)) throw new Error("poly: not polynomial: " + i);
|
|
157
|
+
const L = lst[0].length;
|
|
158
|
+
for (let i = 1; i < lst.length; i++) if (lst[i].length !== L) throw new Error(`poly: mismatched lengths ${L} vs ${lst[i].length}`);
|
|
159
|
+
if (length !== void 0 && L !== length) throw new Error(`poly: expected fixed length ${length}, got ${L}`);
|
|
160
|
+
return L;
|
|
161
|
+
};
|
|
162
|
+
function findOmegaIndex(x, n, brp = false) {
|
|
163
|
+
const bits = log2(n);
|
|
164
|
+
const omega = brp ? roots.brp(bits) : roots.roots(bits);
|
|
165
|
+
for (let i = 0; i < n; i++) if (F.eql(x, omega[i])) return i;
|
|
166
|
+
return -1;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
roots,
|
|
170
|
+
create: _create,
|
|
171
|
+
length,
|
|
172
|
+
extend: (a, len) => {
|
|
173
|
+
checkLength(a);
|
|
174
|
+
const out = _create(len, F.ZERO);
|
|
175
|
+
for (let i = 0; i < Math.min(a.length, len); i++) out[i] = a[i];
|
|
176
|
+
return out;
|
|
177
|
+
},
|
|
178
|
+
degree: (a) => {
|
|
179
|
+
checkLength(a);
|
|
180
|
+
for (let i = a.length - 1; i >= 0; i--) if (!F.is0(a[i])) return i;
|
|
181
|
+
return -1;
|
|
182
|
+
},
|
|
183
|
+
add: (a, b) => {
|
|
184
|
+
const len = checkLength(a, b);
|
|
185
|
+
const out = _create(len);
|
|
186
|
+
for (let i = 0; i < len; i++) out[i] = F.add(a[i], b[i]);
|
|
187
|
+
return out;
|
|
188
|
+
},
|
|
189
|
+
sub: (a, b) => {
|
|
190
|
+
const len = checkLength(a, b);
|
|
191
|
+
const out = _create(len);
|
|
192
|
+
for (let i = 0; i < len; i++) out[i] = F.sub(a[i], b[i]);
|
|
193
|
+
return out;
|
|
194
|
+
},
|
|
195
|
+
dot: (a, b) => {
|
|
196
|
+
const len = checkLength(a, b);
|
|
197
|
+
const out = _create(len);
|
|
198
|
+
for (let i = 0; i < len; i++) out[i] = F.mul(a[i], b[i]);
|
|
199
|
+
return out;
|
|
200
|
+
},
|
|
201
|
+
mul: (a, b) => {
|
|
202
|
+
if (isPoly(b)) {
|
|
203
|
+
const len = checkLength(a, b);
|
|
204
|
+
if (fft) {
|
|
205
|
+
const A = fft.direct(a, false, true);
|
|
206
|
+
const B = fft.direct(b, false, true);
|
|
207
|
+
for (let i = 0; i < A.length; i++) A[i] = F.mul(A[i], B[i]);
|
|
208
|
+
return fft.inverse(A, true, false);
|
|
209
|
+
} else {
|
|
210
|
+
const res = _create(len);
|
|
211
|
+
for (let i = 0; i < len; i++) for (let j = 0; j < len; j++) {
|
|
212
|
+
const k = (i + j) % len;
|
|
213
|
+
res[k] = F.add(res[k], F.mul(a[i], b[j]));
|
|
214
|
+
}
|
|
215
|
+
return res;
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
const out = _create(checkLength(a));
|
|
219
|
+
for (let i = 0; i < out.length; i++) out[i] = F.mul(a[i], b);
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
convolve(a, b) {
|
|
224
|
+
const len = nextPowerOfTwo(a.length + b.length - 1);
|
|
225
|
+
return this.mul(this.extend(a, len), this.extend(b, len));
|
|
226
|
+
},
|
|
227
|
+
shift(p, factor) {
|
|
228
|
+
const out = _create(checkLength(p));
|
|
229
|
+
out[0] = p[0];
|
|
230
|
+
for (let i = 1, power = F.ONE; i < p.length; i++) {
|
|
231
|
+
power = F.mul(power, factor);
|
|
232
|
+
out[i] = F.mul(p[i], power);
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
},
|
|
236
|
+
clone: (a) => {
|
|
237
|
+
checkLength(a);
|
|
238
|
+
const out = _create(a.length);
|
|
239
|
+
for (let i = 0; i < a.length; i++) out[i] = a[i];
|
|
240
|
+
return out;
|
|
241
|
+
},
|
|
242
|
+
eval: (a, basis) => {
|
|
243
|
+
checkLength(a, basis);
|
|
244
|
+
let acc = F.ZERO;
|
|
245
|
+
for (let i = 0; i < a.length; i++) acc = F.add(acc, F.mul(a[i], basis[i]));
|
|
246
|
+
return acc;
|
|
247
|
+
},
|
|
248
|
+
monomial: {
|
|
249
|
+
basis: (x, n) => {
|
|
250
|
+
const out = _create(n);
|
|
251
|
+
let pow = F.ONE;
|
|
252
|
+
for (let i = 0; i < n; i++) {
|
|
253
|
+
out[i] = pow;
|
|
254
|
+
pow = F.mul(pow, x);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
},
|
|
258
|
+
eval: (a, x) => {
|
|
259
|
+
checkLength(a);
|
|
260
|
+
let acc = F.ZERO;
|
|
261
|
+
for (let i = a.length - 1; i >= 0; i--) acc = F.add(F.mul(acc, x), a[i]);
|
|
262
|
+
return acc;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
lagrange: {
|
|
266
|
+
basis: (x, n, brp = false, weights) => {
|
|
267
|
+
const bits = log2(n);
|
|
268
|
+
const cache = weights || (brp ? roots.brp(bits) : roots.roots(bits));
|
|
269
|
+
const out = _create(n);
|
|
270
|
+
const idx = findOmegaIndex(x, n, brp);
|
|
271
|
+
if (idx !== -1) {
|
|
272
|
+
out[idx] = F.ONE;
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
const tm = F.pow(x, BigInt(n));
|
|
276
|
+
const c = F.mul(F.sub(tm, F.ONE), F.inv(BigInt(n)));
|
|
277
|
+
const denom = _create(n);
|
|
278
|
+
for (let i = 0; i < n; i++) denom[i] = F.sub(x, cache[i]);
|
|
279
|
+
const inv = F.invertBatch(denom);
|
|
280
|
+
for (let i = 0; i < n; i++) out[i] = F.mul(c, F.mul(cache[i], inv[i]));
|
|
281
|
+
return out;
|
|
282
|
+
},
|
|
283
|
+
eval(a, x, brp = false) {
|
|
284
|
+
checkLength(a);
|
|
285
|
+
const idx = findOmegaIndex(x, a.length, brp);
|
|
286
|
+
if (idx !== -1) return a[idx];
|
|
287
|
+
const L = this.basis(x, a.length, brp);
|
|
288
|
+
let acc = F.ZERO;
|
|
289
|
+
for (let i = 0; i < a.length; i++) if (!F.is0(a[i])) acc = F.add(acc, F.mul(a[i], L[i]));
|
|
290
|
+
return acc;
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
vanishing(roots) {
|
|
294
|
+
checkLength(roots);
|
|
295
|
+
const out = _create(roots.length + 1, F.ZERO);
|
|
296
|
+
out[0] = F.ONE;
|
|
297
|
+
for (const r of roots) {
|
|
298
|
+
const neg = F.neg(r);
|
|
299
|
+
for (let j = out.length - 1; j > 0; j--) out[j] = F.add(F.mul(out[j], neg), out[j - 1]);
|
|
300
|
+
out[0] = F.mul(out[0], neg);
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/abstract/hash-to-curve.js
|
|
308
|
+
const os2ip = require_grant_rules.bytesToNumberBE;
|
|
309
|
+
function i2osp(value, length) {
|
|
310
|
+
require_grant_rules.asafenumber(value);
|
|
311
|
+
require_grant_rules.asafenumber(length);
|
|
312
|
+
if (length < 0 || length > 4) throw new Error("invalid I2OSP length: " + length);
|
|
313
|
+
if (value < 0 || value > 2 ** (8 * length) - 1) throw new Error("invalid I2OSP input: " + value);
|
|
314
|
+
const res = Array.from({ length }).fill(0);
|
|
315
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
316
|
+
res[i] = value & 255;
|
|
317
|
+
value >>>= 8;
|
|
318
|
+
}
|
|
319
|
+
return new Uint8Array(res);
|
|
320
|
+
}
|
|
321
|
+
function strxor(a, b) {
|
|
322
|
+
const arr = new Uint8Array(a.length);
|
|
323
|
+
for (let i = 0; i < a.length; i++) arr[i] = a[i] ^ b[i];
|
|
324
|
+
return arr;
|
|
325
|
+
}
|
|
326
|
+
function normDST(DST) {
|
|
327
|
+
if (!require_grant_rules.isBytes(DST) && typeof DST !== "string") throw new Error("DST must be Uint8Array or ascii string");
|
|
328
|
+
const dst = typeof DST === "string" ? require_grant_rules.asciiToBytes(DST) : DST;
|
|
329
|
+
if (dst.length === 0) throw new Error("DST must be non-empty");
|
|
330
|
+
return dst;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Produces a uniformly random byte string using a cryptographic hash
|
|
334
|
+
* function H that outputs b bits.
|
|
335
|
+
* See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.3.1 | RFC 9380 section 5.3.1}.
|
|
336
|
+
* @param msg - Input message.
|
|
337
|
+
* @param DST - Domain separation tag. This helper normalizes DST, rejects empty DSTs, and
|
|
338
|
+
* oversize-hashes DST when needed.
|
|
339
|
+
* @param lenInBytes - Output length.
|
|
340
|
+
* @param H - Hash function.
|
|
341
|
+
* @returns Uniform byte string.
|
|
342
|
+
* @throws If the message, DST, hash, or output length is invalid. {@link Error}
|
|
343
|
+
* @example
|
|
344
|
+
* Expand one message into uniform bytes with the XMD construction.
|
|
345
|
+
*
|
|
346
|
+
* ```ts
|
|
347
|
+
* import { expand_message_xmd } from '@noble/curves/abstract/hash-to-curve.js';
|
|
348
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
349
|
+
* const uniform = expand_message_xmd(new TextEncoder().encode('hello noble'), 'DST', 32, sha256);
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
function expand_message_xmd(msg, DST, lenInBytes, H) {
|
|
353
|
+
require_grant_rules.abytes(msg);
|
|
354
|
+
require_grant_rules.asafenumber(lenInBytes);
|
|
355
|
+
DST = normDST(DST);
|
|
356
|
+
if (DST.length > 255) DST = H(require_grant_rules.concatBytes(require_grant_rules.asciiToBytes("H2C-OVERSIZE-DST-"), DST));
|
|
357
|
+
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
|
|
358
|
+
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
|
359
|
+
if (lenInBytes > 65535 || ell > 255) throw new Error("expand_message_xmd: invalid lenInBytes");
|
|
360
|
+
const DST_prime = require_grant_rules.concatBytes(DST, i2osp(DST.length, 1));
|
|
361
|
+
const Z_pad = new Uint8Array(r_in_bytes);
|
|
362
|
+
const l_i_b_str = i2osp(lenInBytes, 2);
|
|
363
|
+
const b = new Array(ell);
|
|
364
|
+
const b_0 = H(require_grant_rules.concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
|
365
|
+
b[0] = H(require_grant_rules.concatBytes(b_0, i2osp(1, 1), DST_prime));
|
|
366
|
+
for (let i = 1; i < ell; i++) b[i] = H(require_grant_rules.concatBytes(...[
|
|
367
|
+
strxor(b_0, b[i - 1]),
|
|
368
|
+
i2osp(i + 1, 1),
|
|
369
|
+
DST_prime
|
|
370
|
+
]));
|
|
371
|
+
return require_grant_rules.concatBytes(...b).slice(0, lenInBytes);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Produces a uniformly random byte string using an extendable-output function (XOF) H.
|
|
375
|
+
* 1. The collision resistance of H MUST be at least k bits.
|
|
376
|
+
* 2. H MUST be an XOF that has been proved indifferentiable from
|
|
377
|
+
* a random oracle under a reasonable cryptographic assumption.
|
|
378
|
+
* See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.3.2 | RFC 9380 section 5.3.2}.
|
|
379
|
+
* @param msg - Input message.
|
|
380
|
+
* @param DST - Domain separation tag. This helper normalizes DST, rejects empty DSTs, and
|
|
381
|
+
* oversize-hashes DST when needed.
|
|
382
|
+
* @param lenInBytes - Output length.
|
|
383
|
+
* @param k - Target security level.
|
|
384
|
+
* @param H - XOF hash function.
|
|
385
|
+
* @returns Uniform byte string.
|
|
386
|
+
* @throws If the message, DST, XOF, or output length is invalid. {@link Error}
|
|
387
|
+
* @example
|
|
388
|
+
* Expand one message into uniform bytes with the XOF construction.
|
|
389
|
+
*
|
|
390
|
+
* ```ts
|
|
391
|
+
* import { expand_message_xof } from '@noble/curves/abstract/hash-to-curve.js';
|
|
392
|
+
* import { shake256 } from '@noble/hashes/sha3.js';
|
|
393
|
+
* const uniform = expand_message_xof(
|
|
394
|
+
* new TextEncoder().encode('hello noble'),
|
|
395
|
+
* 'DST',
|
|
396
|
+
* 32,
|
|
397
|
+
* 128,
|
|
398
|
+
* shake256
|
|
399
|
+
* );
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
function expand_message_xof(msg, DST, lenInBytes, k, H) {
|
|
403
|
+
require_grant_rules.abytes(msg);
|
|
404
|
+
require_grant_rules.asafenumber(lenInBytes);
|
|
405
|
+
DST = normDST(DST);
|
|
406
|
+
if (DST.length > 255) {
|
|
407
|
+
const dkLen = Math.ceil(2 * k / 8);
|
|
408
|
+
DST = H.create({ dkLen }).update(require_grant_rules.asciiToBytes("H2C-OVERSIZE-DST-")).update(DST).digest();
|
|
409
|
+
}
|
|
410
|
+
if (lenInBytes > 65535 || DST.length > 255) throw new Error("expand_message_xof: invalid lenInBytes");
|
|
411
|
+
return H.create({ dkLen: lenInBytes }).update(msg).update(i2osp(lenInBytes, 2)).update(DST).update(i2osp(DST.length, 1)).digest();
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
|
|
415
|
+
* See {@link https://www.rfc-editor.org/rfc/rfc9380#section-5.2 | RFC 9380 section 5.2}.
|
|
416
|
+
* @param msg - Input message bytes.
|
|
417
|
+
* @param count - Number of field elements to derive. Must be `>= 1`.
|
|
418
|
+
* @param options - RFC 9380 options. See {@link H2COpts}. `m` must be `>= 1`.
|
|
419
|
+
* @returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
|
|
420
|
+
* @throws If the expander choice or RFC 9380 options are invalid. {@link Error}
|
|
421
|
+
* @example
|
|
422
|
+
* Hash one message into field elements before mapping it onto a curve.
|
|
423
|
+
*
|
|
424
|
+
* ```ts
|
|
425
|
+
* import { hash_to_field } from '@noble/curves/abstract/hash-to-curve.js';
|
|
426
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
427
|
+
* const scalars = hash_to_field(new TextEncoder().encode('hello noble'), 2, {
|
|
428
|
+
* DST: 'DST',
|
|
429
|
+
* p: 17n,
|
|
430
|
+
* m: 1,
|
|
431
|
+
* k: 128,
|
|
432
|
+
* expand: 'xmd',
|
|
433
|
+
* hash: sha256,
|
|
434
|
+
* });
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
function hash_to_field(msg, count, options) {
|
|
438
|
+
require_grant_rules.validateObject(options, {
|
|
439
|
+
p: "bigint",
|
|
440
|
+
m: "number",
|
|
441
|
+
k: "number",
|
|
442
|
+
hash: "function"
|
|
443
|
+
});
|
|
444
|
+
const { p, k, m, hash, expand, DST } = options;
|
|
445
|
+
require_grant_rules.asafenumber(hash.outputLen, "valid hash");
|
|
446
|
+
require_grant_rules.abytes(msg);
|
|
447
|
+
require_grant_rules.asafenumber(count);
|
|
448
|
+
if (count < 1) throw new Error("hash_to_field: expected count >= 1");
|
|
449
|
+
if (m < 1) throw new Error("hash_to_field: expected m >= 1");
|
|
450
|
+
const log2p = p.toString(2).length;
|
|
451
|
+
const L = Math.ceil((log2p + k) / 8);
|
|
452
|
+
const len_in_bytes = count * m * L;
|
|
453
|
+
let prb;
|
|
454
|
+
if (expand === "xmd") prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
|
|
455
|
+
else if (expand === "xof") prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
|
|
456
|
+
else if (expand === "_internal_pass") prb = msg;
|
|
457
|
+
else throw new Error("expand must be \"xmd\" or \"xof\"");
|
|
458
|
+
const u = new Array(count);
|
|
459
|
+
for (let i = 0; i < count; i++) {
|
|
460
|
+
const e = new Array(m);
|
|
461
|
+
for (let j = 0; j < m; j++) {
|
|
462
|
+
const elm_offset = L * (j + i * m);
|
|
463
|
+
e[j] = require_grant_rules.mod(os2ip(prb.subarray(elm_offset, elm_offset + L)), p);
|
|
464
|
+
}
|
|
465
|
+
u[i] = e;
|
|
466
|
+
}
|
|
467
|
+
return u;
|
|
468
|
+
}
|
|
469
|
+
const _DST_scalar = "HashToScalar-";
|
|
470
|
+
/**
|
|
471
|
+
* Creates hash-to-curve methods from EC Point and mapToCurve function. See {@link H2CHasher}.
|
|
472
|
+
* @param Point - Point constructor.
|
|
473
|
+
* @param mapToCurve - Map-to-curve function.
|
|
474
|
+
* @param defaults - Default hash-to-curve options. This object is frozen in place and reused as
|
|
475
|
+
* the shared defaults bundle for the returned helpers.
|
|
476
|
+
* @returns Hash-to-curve helper namespace.
|
|
477
|
+
* @throws If the map-to-curve callback or default hash-to-curve options are invalid. {@link Error}
|
|
478
|
+
* @example
|
|
479
|
+
* Bundle hash-to-curve, hash-to-scalar, and encode-to-curve helpers for one curve.
|
|
480
|
+
*
|
|
481
|
+
* ```ts
|
|
482
|
+
* import { createHasher } from '@noble/curves/abstract/hash-to-curve.js';
|
|
483
|
+
* import { p256 } from '@noble/curves/nist.js';
|
|
484
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
485
|
+
* const hasher = createHasher(p256.Point, () => p256.Point.BASE.toAffine(), {
|
|
486
|
+
* DST: 'P256_XMD:SHA-256_SSWU_RO_',
|
|
487
|
+
* encodeDST: 'P256_XMD:SHA-256_SSWU_NU_',
|
|
488
|
+
* p: p256.Point.Fp.ORDER,
|
|
489
|
+
* m: 1,
|
|
490
|
+
* k: 128,
|
|
491
|
+
* expand: 'xmd',
|
|
492
|
+
* hash: sha256,
|
|
493
|
+
* });
|
|
494
|
+
* const point = hasher.encodeToCurve(new TextEncoder().encode('hello noble'));
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
function createHasher(Point, mapToCurve, defaults) {
|
|
498
|
+
if (typeof mapToCurve !== "function") throw new Error("mapToCurve() must be defined");
|
|
499
|
+
const snapshot = (src) => Object.freeze({
|
|
500
|
+
...src,
|
|
501
|
+
DST: require_grant_rules.isBytes(src.DST) ? require_grant_rules.copyBytes(src.DST) : src.DST,
|
|
502
|
+
...src.encodeDST === void 0 ? {} : { encodeDST: require_grant_rules.isBytes(src.encodeDST) ? require_grant_rules.copyBytes(src.encodeDST) : src.encodeDST }
|
|
503
|
+
});
|
|
504
|
+
const safeDefaults = snapshot(defaults);
|
|
505
|
+
function map(num) {
|
|
506
|
+
return Point.fromAffine(mapToCurve(num));
|
|
507
|
+
}
|
|
508
|
+
function clear(initial) {
|
|
509
|
+
const P = initial.clearCofactor();
|
|
510
|
+
if (P.equals(Point.ZERO)) return Point.ZERO;
|
|
511
|
+
P.assertValidity();
|
|
512
|
+
return P;
|
|
513
|
+
}
|
|
514
|
+
return Object.freeze({
|
|
515
|
+
get defaults() {
|
|
516
|
+
return snapshot(safeDefaults);
|
|
517
|
+
},
|
|
518
|
+
Point,
|
|
519
|
+
hashToCurve(msg, options) {
|
|
520
|
+
const u = hash_to_field(msg, 2, Object.assign({}, safeDefaults, options));
|
|
521
|
+
const u0 = map(u[0]);
|
|
522
|
+
const u1 = map(u[1]);
|
|
523
|
+
return clear(u0.add(u1));
|
|
524
|
+
},
|
|
525
|
+
encodeToCurve(msg, options) {
|
|
526
|
+
const optsDst = safeDefaults.encodeDST ? { DST: safeDefaults.encodeDST } : {};
|
|
527
|
+
return clear(map(hash_to_field(msg, 1, Object.assign({}, safeDefaults, optsDst, options))[0]));
|
|
528
|
+
},
|
|
529
|
+
mapToCurve(scalars) {
|
|
530
|
+
if (safeDefaults.m === 1) {
|
|
531
|
+
if (typeof scalars !== "bigint") throw new Error("expected bigint (m=1)");
|
|
532
|
+
return clear(map([scalars]));
|
|
533
|
+
}
|
|
534
|
+
if (!Array.isArray(scalars)) throw new Error("expected array of bigints");
|
|
535
|
+
for (const i of scalars) if (typeof i !== "bigint") throw new Error("expected array of bigints");
|
|
536
|
+
return clear(map(scalars));
|
|
537
|
+
},
|
|
538
|
+
hashToScalar(msg, options) {
|
|
539
|
+
const N = Point.Fn.ORDER;
|
|
540
|
+
return hash_to_field(msg, 1, Object.assign({}, safeDefaults, {
|
|
541
|
+
p: N,
|
|
542
|
+
m: 1,
|
|
543
|
+
DST: _DST_scalar
|
|
544
|
+
}, options))[0][0];
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/abstract/frost.js
|
|
550
|
+
/**
|
|
551
|
+
* FROST: Flexible Round-Optimized Schnorr Threshold Protocol for Two-Round Schnorr Signatures.
|
|
552
|
+
*
|
|
553
|
+
* See [RFC 9591](https://datatracker.ietf.org/doc/rfc9591/) and [frost.zfnd.org](https://frost.zfnd.org).
|
|
554
|
+
* @module
|
|
555
|
+
*/
|
|
556
|
+
const validateSigners = (signers) => {
|
|
557
|
+
if (!Number.isSafeInteger(signers.min) || !Number.isSafeInteger(signers.max)) throw new Error("Wrong signers info: min=" + signers.min + " max=" + signers.max);
|
|
558
|
+
if (signers.min < 2 || signers.max < 2 || signers.min > signers.max) throw new Error("Wrong signers info: min=" + signers.min + " max=" + signers.max);
|
|
559
|
+
};
|
|
560
|
+
const validateCommitmentsNum = (signers, len) => {
|
|
561
|
+
if (len < signers.min || len > signers.max) throw new Error("Wrong number of commitments=" + len);
|
|
562
|
+
};
|
|
563
|
+
var AggErr = class extends Error {
|
|
564
|
+
cheaters;
|
|
565
|
+
constructor(msg, cheaters) {
|
|
566
|
+
super(msg);
|
|
567
|
+
this.cheaters = cheaters;
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
function createFROST(opts) {
|
|
571
|
+
require_grant_rules.validateObject(opts, {
|
|
572
|
+
name: "string",
|
|
573
|
+
hash: "function"
|
|
574
|
+
}, {
|
|
575
|
+
hashToScalar: "function",
|
|
576
|
+
validatePoint: "function",
|
|
577
|
+
parsePublicKey: "function",
|
|
578
|
+
adjustScalar: "function",
|
|
579
|
+
adjustPoint: "function",
|
|
580
|
+
challenge: "function",
|
|
581
|
+
adjustNonces: "function",
|
|
582
|
+
adjustSecret: "function",
|
|
583
|
+
adjustPublic: "function",
|
|
584
|
+
adjustGroupCommitmentShare: "function",
|
|
585
|
+
adjustDKG: "function"
|
|
586
|
+
});
|
|
587
|
+
require_grant_rules.validatePointCons(opts.Point);
|
|
588
|
+
const { Point } = opts;
|
|
589
|
+
const Fn = opts.Fn === void 0 ? Point.Fn : opts.Fn;
|
|
590
|
+
const hashBytes = opts.hash;
|
|
591
|
+
const hashToScalar = opts.hashToScalar === void 0 ? (msg, opts = { DST: new Uint8Array() }) => {
|
|
592
|
+
const t = hashBytes(require_grant_rules.concatBytes(opts.DST, msg));
|
|
593
|
+
return Fn.create(Fn.isLE ? require_grant_rules.bytesToNumberLE(t) : require_grant_rules.bytesToNumberBE(t));
|
|
594
|
+
} : opts.hashToScalar;
|
|
595
|
+
const H1Prefix = require_grant_rules.utf8ToBytes(opts.H1 !== void 0 ? opts.H1 : opts.name + "rho");
|
|
596
|
+
const H2Prefix = require_grant_rules.utf8ToBytes(opts.H2 !== void 0 ? opts.H2 : opts.name + "chal");
|
|
597
|
+
const H3Prefix = require_grant_rules.utf8ToBytes(opts.H3 !== void 0 ? opts.H3 : opts.name + "nonce");
|
|
598
|
+
const H4Prefix = require_grant_rules.utf8ToBytes(opts.H4 !== void 0 ? opts.H4 : opts.name + "msg");
|
|
599
|
+
const H5Prefix = require_grant_rules.utf8ToBytes(opts.H5 !== void 0 ? opts.H5 : opts.name + "com");
|
|
600
|
+
const HDKGPrefix = require_grant_rules.utf8ToBytes(opts.HDKG !== void 0 ? opts.HDKG : opts.name + "dkg");
|
|
601
|
+
const HIDPrefix = require_grant_rules.utf8ToBytes(opts.HID !== void 0 ? opts.HID : opts.name + "id");
|
|
602
|
+
const H1 = (msg) => hashToScalar(msg, { DST: H1Prefix });
|
|
603
|
+
const H2 = (msg) => hashToScalar(msg, { DST: H2Prefix });
|
|
604
|
+
const H3 = (msg) => hashToScalar(msg, { DST: H3Prefix });
|
|
605
|
+
const H4 = (msg) => hashBytes(require_grant_rules.concatBytes(H4Prefix, msg));
|
|
606
|
+
const H5 = (msg) => hashBytes(require_grant_rules.concatBytes(H5Prefix, msg));
|
|
607
|
+
const HDKG = (msg) => hashToScalar(msg, { DST: HDKGPrefix });
|
|
608
|
+
const HID = (msg) => hashToScalar(msg, { DST: HIDPrefix });
|
|
609
|
+
const randomScalar = (rng = require_grant_rules.randomBytes) => {
|
|
610
|
+
const t = require_grant_rules.mapHashToField(rng(require_grant_rules.getMinHashLength(Fn.ORDER)), Fn.ORDER, Fn.isLE);
|
|
611
|
+
return Fn.isLE ? require_grant_rules.bytesToNumberLE(t) : require_grant_rules.bytesToNumberBE(t);
|
|
612
|
+
};
|
|
613
|
+
const serializePoint = (p) => p.toBytes();
|
|
614
|
+
const parsePoint = (bytes) => {
|
|
615
|
+
const p = Point.fromBytes(bytes);
|
|
616
|
+
if (opts.validatePoint) opts.validatePoint(p);
|
|
617
|
+
return p;
|
|
618
|
+
};
|
|
619
|
+
const nonceCommitments = (identifier, nonces) => ({
|
|
620
|
+
identifier,
|
|
621
|
+
hiding: serializePoint(Point.BASE.multiply(Fn.fromBytes(nonces.hiding))),
|
|
622
|
+
binding: serializePoint(Point.BASE.multiply(Fn.fromBytes(nonces.binding)))
|
|
623
|
+
});
|
|
624
|
+
const adjustPoint = opts.adjustPoint === void 0 ? (n) => n : opts.adjustPoint;
|
|
625
|
+
const validateIdentifier = (n) => {
|
|
626
|
+
if (!Fn.isValid(n) || Fn.is0(n)) throw new Error("Invalid identifier " + n);
|
|
627
|
+
return n;
|
|
628
|
+
};
|
|
629
|
+
const serializeIdentifier = (id) => require_grant_rules.bytesToHex(Fn.toBytes(validateIdentifier(id)));
|
|
630
|
+
const parseIdentifier = (id) => {
|
|
631
|
+
const n = validateIdentifier(Fn.fromBytes(require_grant_rules.hexToBytes(id)));
|
|
632
|
+
if (serializeIdentifier(n) !== id) throw new Error("expected canonical identifier hex");
|
|
633
|
+
return n;
|
|
634
|
+
};
|
|
635
|
+
const Signature = {
|
|
636
|
+
encode: (R, z) => {
|
|
637
|
+
let res = require_grant_rules.concatBytes(serializePoint(R), Fn.toBytes(z));
|
|
638
|
+
if (opts.adjustTx) res = opts.adjustTx.encode(res);
|
|
639
|
+
return res;
|
|
640
|
+
},
|
|
641
|
+
decode: (sig) => {
|
|
642
|
+
if (opts.adjustTx) sig = opts.adjustTx.decode(sig);
|
|
643
|
+
return {
|
|
644
|
+
R: parsePoint(sig.subarray(0, -Fn.BYTES)),
|
|
645
|
+
z: Fn.fromBytes(sig.subarray(-Fn.BYTES))
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const genPointScalarPair = (rng = require_grant_rules.randomBytes) => {
|
|
650
|
+
let n = randomScalar(rng);
|
|
651
|
+
if (opts.adjustScalar) n = opts.adjustScalar(n);
|
|
652
|
+
let p = Point.BASE.multiply(n);
|
|
653
|
+
return {
|
|
654
|
+
scalar: n,
|
|
655
|
+
point: p
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
const nrErr = "roots are unavailable in FROST polynomial mode";
|
|
659
|
+
const Poly = poly(Fn, {
|
|
660
|
+
info: {
|
|
661
|
+
G: Fn.ZERO,
|
|
662
|
+
oddFactor: Fn.ZERO,
|
|
663
|
+
powerOfTwo: 0
|
|
664
|
+
},
|
|
665
|
+
roots() {
|
|
666
|
+
throw new Error(nrErr);
|
|
667
|
+
},
|
|
668
|
+
brp() {
|
|
669
|
+
throw new Error(nrErr);
|
|
670
|
+
},
|
|
671
|
+
inverse() {
|
|
672
|
+
throw new Error(nrErr);
|
|
673
|
+
},
|
|
674
|
+
omega() {
|
|
675
|
+
throw new Error(nrErr);
|
|
676
|
+
},
|
|
677
|
+
clear() {}
|
|
678
|
+
});
|
|
679
|
+
const msm = (points, scalars) => require_grant_rules.pippenger(Point, points, scalars);
|
|
680
|
+
const polynomialEvaluate = (x, coeffs) => {
|
|
681
|
+
if (!coeffs.length) throw new Error("empty coefficients");
|
|
682
|
+
return Poly.monomial.eval(coeffs, x);
|
|
683
|
+
};
|
|
684
|
+
const deriveInterpolatingValue = (L, xi) => {
|
|
685
|
+
const err = "invalid parameters";
|
|
686
|
+
if (!L.some((x) => Fn.eql(x, xi))) throw new Error(err);
|
|
687
|
+
const Lset = new Set(L);
|
|
688
|
+
if (Lset.size !== L.length) throw new Error(err);
|
|
689
|
+
if (!Lset.has(xi)) throw new Error(err);
|
|
690
|
+
let num = Fn.ONE;
|
|
691
|
+
let den = Fn.ONE;
|
|
692
|
+
for (const x of L) {
|
|
693
|
+
if (Fn.eql(x, xi)) continue;
|
|
694
|
+
num = Fn.mul(num, x);
|
|
695
|
+
den = Fn.mul(den, Fn.sub(x, xi));
|
|
696
|
+
}
|
|
697
|
+
return Fn.div(num, den);
|
|
698
|
+
};
|
|
699
|
+
const evalutateVSS = (identifier, commitment) => {
|
|
700
|
+
return msm(commitment, Poly.monomial.basis(identifier, commitment.length));
|
|
701
|
+
};
|
|
702
|
+
const generateSecretPolynomial = (signers, secret, coeffs, rng = require_grant_rules.randomBytes) => {
|
|
703
|
+
validateSigners(signers);
|
|
704
|
+
const secretScalar = secret === void 0 ? randomScalar(rng) : Fn.fromBytes(secret);
|
|
705
|
+
if (!coeffs) {
|
|
706
|
+
coeffs = [];
|
|
707
|
+
for (let i = 0; i < signers.min - 1; i++) coeffs.push(randomScalar(rng));
|
|
708
|
+
}
|
|
709
|
+
if (coeffs.length !== signers.min - 1) throw new Error("wrong coefficients length");
|
|
710
|
+
const coefficients = [secretScalar, ...coeffs];
|
|
711
|
+
return {
|
|
712
|
+
coefficients,
|
|
713
|
+
commitment: coefficients.map((i) => Point.BASE.multiply(i)),
|
|
714
|
+
secret: secretScalar
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
const ProofOfKnowledge = {
|
|
718
|
+
challenge: (id, verKey, R) => HDKG(require_grant_rules.concatBytes(Fn.toBytes(id), serializePoint(verKey), serializePoint(R))),
|
|
719
|
+
compute(id, coefficents, commitments, rng = require_grant_rules.randomBytes) {
|
|
720
|
+
if (coefficents.length < 1) throw new Error("coefficients should have at least one element");
|
|
721
|
+
const { point: R, scalar: k } = genPointScalarPair(rng);
|
|
722
|
+
const verKey = commitments[0];
|
|
723
|
+
const c = this.challenge(id, verKey, R);
|
|
724
|
+
const mu = Fn.add(k, Fn.mul(coefficents[0], c));
|
|
725
|
+
return Signature.encode(R, mu);
|
|
726
|
+
},
|
|
727
|
+
validate(id, commitment, proof) {
|
|
728
|
+
if (commitment.length < 1) throw new Error("commitment should have at least one element");
|
|
729
|
+
const { R, z } = Signature.decode(proof);
|
|
730
|
+
const phi = parsePoint(commitment[0]);
|
|
731
|
+
const c = this.challenge(id, phi, R);
|
|
732
|
+
if (!R.equals(Point.BASE.multiply(z).subtract(phi.multiply(c)))) throw new Error("invalid proof of knowledge");
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
const Basic = {
|
|
736
|
+
challenge: (R, PK, msg) => {
|
|
737
|
+
if (opts.challenge) return opts.challenge(R, PK, msg);
|
|
738
|
+
return H2(require_grant_rules.concatBytes(serializePoint(R), serializePoint(PK), msg));
|
|
739
|
+
},
|
|
740
|
+
sign(msg, sk, rng = require_grant_rules.randomBytes) {
|
|
741
|
+
const { point: R, scalar: r } = genPointScalarPair(rng);
|
|
742
|
+
const PK = Point.BASE.multiply(sk);
|
|
743
|
+
const c = this.challenge(R, PK, msg);
|
|
744
|
+
return [R, Fn.add(r, Fn.mul(c, sk))];
|
|
745
|
+
},
|
|
746
|
+
verify(msg, R, z, PK) {
|
|
747
|
+
if (opts.adjustPoint) PK = opts.adjustPoint(PK);
|
|
748
|
+
if (opts.adjustPoint) R = opts.adjustPoint(R);
|
|
749
|
+
const c = this.challenge(R, PK, msg);
|
|
750
|
+
const zB = Point.BASE.multiply(z);
|
|
751
|
+
const cA = PK.multiply(c);
|
|
752
|
+
let check = zB.subtract(cA).subtract(R);
|
|
753
|
+
if (check.clearCofactor) check = check.clearCofactor();
|
|
754
|
+
return Point.ZERO.equals(check);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
const validateSecretShare = (identifier, commitment, signingShare) => {
|
|
758
|
+
if (!Point.BASE.multiply(signingShare).equals(evalutateVSS(identifier, commitment))) throw new Error("invalid secret share");
|
|
759
|
+
};
|
|
760
|
+
const Identifier = {
|
|
761
|
+
fromNumber(n) {
|
|
762
|
+
if (!Number.isSafeInteger(n)) throw new Error("expected safe interger");
|
|
763
|
+
return serializeIdentifier(BigInt(n));
|
|
764
|
+
},
|
|
765
|
+
derive(s) {
|
|
766
|
+
if (typeof s !== "string") throw new Error("wrong identifier string: " + s);
|
|
767
|
+
return serializeIdentifier(HID(require_grant_rules.utf8ToBytes(s)));
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const generateNonce = (secret, rng = require_grant_rules.randomBytes) => H3(require_grant_rules.concatBytes(rng(32), Fn.toBytes(secret)));
|
|
771
|
+
const getGroupCommitment = (GPK, commitmentList, msg) => {
|
|
772
|
+
const CL = commitmentList.map((i) => [
|
|
773
|
+
i.identifier,
|
|
774
|
+
parseIdentifier(i.identifier),
|
|
775
|
+
parsePoint(i.hiding),
|
|
776
|
+
parsePoint(i.binding)
|
|
777
|
+
]);
|
|
778
|
+
CL.sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0);
|
|
779
|
+
const Cbytes = [];
|
|
780
|
+
for (const [_, id, hC, bC] of CL) Cbytes.push(Fn.toBytes(id), serializePoint(hC), serializePoint(bC));
|
|
781
|
+
const encodedCommitmentHash = H5(require_grant_rules.concatBytes(...Cbytes));
|
|
782
|
+
const rhoPrefix = require_grant_rules.concatBytes(serializePoint(GPK), H4(msg), encodedCommitmentHash);
|
|
783
|
+
const bindingFactors = {};
|
|
784
|
+
for (const [i, id] of CL) bindingFactors[i] = H1(require_grant_rules.concatBytes(rhoPrefix, Fn.toBytes(id)));
|
|
785
|
+
const points = [];
|
|
786
|
+
const scalars = [];
|
|
787
|
+
for (const [i, _, hC, bC] of CL) {
|
|
788
|
+
if (Point.ZERO.equals(hC) || Point.ZERO.equals(bC)) throw new Error("infinity commitment");
|
|
789
|
+
points.push(hC, bC);
|
|
790
|
+
scalars.push(Fn.ONE, bindingFactors[i]);
|
|
791
|
+
}
|
|
792
|
+
const groupCommitment = msm(points, scalars);
|
|
793
|
+
return {
|
|
794
|
+
identifiers: CL.map((i) => i[1]),
|
|
795
|
+
groupCommitment,
|
|
796
|
+
bindingFactors
|
|
797
|
+
};
|
|
798
|
+
};
|
|
799
|
+
const prepareShare = (PK, commitmentList, msg, identifier) => {
|
|
800
|
+
const GPK = adjustPoint(parsePoint(PK));
|
|
801
|
+
const id = parseIdentifier(identifier);
|
|
802
|
+
const { identifiers, groupCommitment, bindingFactors } = getGroupCommitment(GPK, commitmentList, msg);
|
|
803
|
+
const bindingFactor = bindingFactors[identifier];
|
|
804
|
+
return {
|
|
805
|
+
lambda: deriveInterpolatingValue(identifiers, id),
|
|
806
|
+
challenge: Basic.challenge(groupCommitment, GPK, msg),
|
|
807
|
+
bindingFactor,
|
|
808
|
+
groupCommitment
|
|
809
|
+
};
|
|
810
|
+
};
|
|
811
|
+
Object.freeze(Identifier);
|
|
812
|
+
const frost = {
|
|
813
|
+
Identifier,
|
|
814
|
+
DKG: Object.freeze({
|
|
815
|
+
round1: (id, signers, secret, rng = require_grant_rules.randomBytes) => {
|
|
816
|
+
validateSigners(signers);
|
|
817
|
+
const idNum = parseIdentifier(id);
|
|
818
|
+
const { coefficients, commitment } = generateSecretPolynomial(signers, secret, void 0, rng);
|
|
819
|
+
const proofOfKnowledge = ProofOfKnowledge.compute(idNum, coefficients, commitment, rng);
|
|
820
|
+
const commitmentBytes = commitment.map(serializePoint);
|
|
821
|
+
return {
|
|
822
|
+
public: {
|
|
823
|
+
identifier: serializeIdentifier(idNum),
|
|
824
|
+
commitment: commitmentBytes,
|
|
825
|
+
proofOfKnowledge
|
|
826
|
+
},
|
|
827
|
+
secret: {
|
|
828
|
+
identifier: idNum,
|
|
829
|
+
coefficients,
|
|
830
|
+
commitment: commitment.map(serializePoint),
|
|
831
|
+
signers: {
|
|
832
|
+
min: signers.min,
|
|
833
|
+
max: signers.max
|
|
834
|
+
},
|
|
835
|
+
step: 1
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
},
|
|
839
|
+
round2: (secret, others) => {
|
|
840
|
+
if (others.length !== secret.signers.max - 1) throw new Error("wrong number of round1 packages");
|
|
841
|
+
if (!secret.coefficients || secret.step === 3) throw new Error("round3 package used in round2");
|
|
842
|
+
const res = {};
|
|
843
|
+
for (const p of others) {
|
|
844
|
+
if (p.commitment.length !== secret.signers.min) throw new Error("wrong number of commitments");
|
|
845
|
+
const id = parseIdentifier(p.identifier);
|
|
846
|
+
if (id === secret.identifier) throw new Error("duplicate id=" + serializeIdentifier(id));
|
|
847
|
+
ProofOfKnowledge.validate(id, p.commitment, p.proofOfKnowledge);
|
|
848
|
+
for (const c of p.commitment) parsePoint(c);
|
|
849
|
+
if (res[p.identifier]) throw new Error("Duplicate id=" + id);
|
|
850
|
+
const signingShare = Fn.toBytes(polynomialEvaluate(id, secret.coefficients));
|
|
851
|
+
res[p.identifier] = {
|
|
852
|
+
identifier: serializeIdentifier(secret.identifier),
|
|
853
|
+
signingShare
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
secret.step = 2;
|
|
857
|
+
return res;
|
|
858
|
+
},
|
|
859
|
+
round3: (secret, round1, round2) => {
|
|
860
|
+
if (round1.length !== secret.signers.max - 1) throw new Error("wrong length of round1 packages");
|
|
861
|
+
if (!secret.coefficients || secret.step !== 2) throw new Error("round2 package used in round3");
|
|
862
|
+
if (round2.length !== round1.length) throw new Error("wrong length of round2 packages");
|
|
863
|
+
const merged = {};
|
|
864
|
+
for (const r1 of round1) {
|
|
865
|
+
if (!r1.identifier || !r1.commitment) throw new Error("wrong round1 share");
|
|
866
|
+
merged[r1.identifier] = { ...r1 };
|
|
867
|
+
}
|
|
868
|
+
for (const r2 of round2) {
|
|
869
|
+
if (!r2.identifier || !r2.signingShare) throw new Error("wrong round2 share");
|
|
870
|
+
if (!merged[r2.identifier]) throw new Error("round1 share for " + r2.identifier + " is missing");
|
|
871
|
+
merged[r2.identifier].signingShare = r2.signingShare;
|
|
872
|
+
}
|
|
873
|
+
if (Object.keys(merged).length !== round1.length) throw new Error("mismatch identifiers between rounds");
|
|
874
|
+
let signingShare = Fn.ZERO;
|
|
875
|
+
if (secret.commitment.length !== secret.signers.min) throw new Error("wrong commitments length");
|
|
876
|
+
const localCommitment = secret.commitment.map(parsePoint);
|
|
877
|
+
const localShare = polynomialEvaluate(secret.identifier, secret.coefficients);
|
|
878
|
+
validateSecretShare(secret.identifier, localCommitment, localShare);
|
|
879
|
+
const localCommitmentBytes = localCommitment.map(serializePoint);
|
|
880
|
+
const commitments = { [serializeIdentifier(secret.identifier)]: localCommitmentBytes };
|
|
881
|
+
for (const k in merged) {
|
|
882
|
+
const v = merged[k];
|
|
883
|
+
if (!v.signingShare || !v.commitment) throw new Error("mismatch identifiers");
|
|
884
|
+
const id = parseIdentifier(k);
|
|
885
|
+
const signingSharePart = Fn.fromBytes(v.signingShare);
|
|
886
|
+
const commitment = v.commitment.map(parsePoint);
|
|
887
|
+
validateSecretShare(secret.identifier, commitment, signingSharePart);
|
|
888
|
+
signingShare = Fn.add(signingShare, signingSharePart);
|
|
889
|
+
const idSer = serializeIdentifier(id);
|
|
890
|
+
if (commitments[idSer]) throw new Error("duplicated id=" + idSer);
|
|
891
|
+
commitments[idSer] = v.commitment;
|
|
892
|
+
}
|
|
893
|
+
signingShare = Fn.add(signingShare, localShare);
|
|
894
|
+
const mergedCommitment = new Array(secret.signers.min).fill(Point.ZERO);
|
|
895
|
+
for (const k in commitments) {
|
|
896
|
+
const v = commitments[k];
|
|
897
|
+
if (v.length !== secret.signers.min) throw new Error("wrong commitments length");
|
|
898
|
+
for (let i = 0; i < v.length; i++) mergedCommitment[i] = mergedCommitment[i].add(parsePoint(v[i]));
|
|
899
|
+
}
|
|
900
|
+
const mergedCommitmentBytes = mergedCommitment.map(serializePoint);
|
|
901
|
+
const verifyingShares = {};
|
|
902
|
+
for (const k in commitments) verifyingShares[k] = serializePoint(evalutateVSS(parseIdentifier(k), mergedCommitment));
|
|
903
|
+
let res = {
|
|
904
|
+
public: {
|
|
905
|
+
signers: {
|
|
906
|
+
min: secret.signers.min,
|
|
907
|
+
max: secret.signers.max
|
|
908
|
+
},
|
|
909
|
+
commitments: mergedCommitmentBytes,
|
|
910
|
+
verifyingShares: Object.fromEntries(Object.entries(verifyingShares).map(([k, v]) => [k, v.slice()]))
|
|
911
|
+
},
|
|
912
|
+
secret: {
|
|
913
|
+
identifier: serializeIdentifier(secret.identifier),
|
|
914
|
+
signingShare: Fn.toBytes(signingShare)
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
if (opts.adjustDKG) res = opts.adjustDKG(res);
|
|
918
|
+
for (let i = 0; i < secret.coefficients.length; i++) secret.coefficients[i] -= secret.coefficients[i];
|
|
919
|
+
delete secret.coefficients;
|
|
920
|
+
secret.step = 3;
|
|
921
|
+
return res;
|
|
922
|
+
},
|
|
923
|
+
clean(secret) {
|
|
924
|
+
secret.identifier -= secret.identifier;
|
|
925
|
+
if (secret.coefficients) for (let i = 0; i < secret.coefficients.length; i++) secret.coefficients[i] -= secret.coefficients[i];
|
|
926
|
+
secret.step = 3;
|
|
927
|
+
}
|
|
928
|
+
}),
|
|
929
|
+
trustedDealer(signers, identifiers, secret, rng = require_grant_rules.randomBytes) {
|
|
930
|
+
validateSigners(signers);
|
|
931
|
+
if (identifiers === void 0) {
|
|
932
|
+
identifiers = [];
|
|
933
|
+
for (let i = 1; i <= signers.max; i++) identifiers.push(Identifier.fromNumber(i));
|
|
934
|
+
} else if (!Array.isArray(identifiers) || identifiers.length !== signers.max) throw new Error("identifiers should be array of " + signers.max);
|
|
935
|
+
const identifierNums = {};
|
|
936
|
+
for (const id of identifiers) {
|
|
937
|
+
const idNum = parseIdentifier(id);
|
|
938
|
+
if (id in identifierNums) throw new Error("duplicated id=" + id);
|
|
939
|
+
identifierNums[id] = idNum;
|
|
940
|
+
}
|
|
941
|
+
const sp = generateSecretPolynomial(signers, secret, void 0, rng);
|
|
942
|
+
const commitmentBytes = sp.commitment.map(serializePoint);
|
|
943
|
+
const secretShares = {};
|
|
944
|
+
const verifyingShares = {};
|
|
945
|
+
for (const id of identifiers) {
|
|
946
|
+
const signingShare = polynomialEvaluate(identifierNums[id], sp.coefficients);
|
|
947
|
+
verifyingShares[id] = serializePoint(Point.BASE.multiply(signingShare));
|
|
948
|
+
secretShares[id] = {
|
|
949
|
+
identifier: id,
|
|
950
|
+
signingShare: Fn.toBytes(signingShare)
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
public: {
|
|
955
|
+
signers: {
|
|
956
|
+
min: signers.min,
|
|
957
|
+
max: signers.max
|
|
958
|
+
},
|
|
959
|
+
commitments: commitmentBytes,
|
|
960
|
+
verifyingShares
|
|
961
|
+
},
|
|
962
|
+
secretShares
|
|
963
|
+
};
|
|
964
|
+
},
|
|
965
|
+
validateSecret(secret, pub) {
|
|
966
|
+
validateSecretShare(parseIdentifier(secret.identifier), pub.commitments.map(parsePoint), Fn.fromBytes(secret.signingShare));
|
|
967
|
+
},
|
|
968
|
+
commit(secret, rng = require_grant_rules.randomBytes) {
|
|
969
|
+
const secretScalar = Fn.fromBytes(secret.signingShare);
|
|
970
|
+
const hiding = generateNonce(secretScalar, rng);
|
|
971
|
+
const binding = generateNonce(secretScalar, rng);
|
|
972
|
+
const nonces = {
|
|
973
|
+
hiding: Fn.toBytes(hiding),
|
|
974
|
+
binding: Fn.toBytes(binding)
|
|
975
|
+
};
|
|
976
|
+
return {
|
|
977
|
+
nonces,
|
|
978
|
+
commitments: nonceCommitments(secret.identifier, nonces)
|
|
979
|
+
};
|
|
980
|
+
},
|
|
981
|
+
signShare(secret, pub, nonces, commitmentList, msg) {
|
|
982
|
+
validateCommitmentsNum(pub.signers, commitmentList.length);
|
|
983
|
+
const hidingNonce0 = Fn.fromBytes(nonces.hiding);
|
|
984
|
+
const bindingNonce0 = Fn.fromBytes(nonces.binding);
|
|
985
|
+
if (Fn.is0(hidingNonce0) || Fn.is0(bindingNonce0)) throw new Error("signing nonces already used");
|
|
986
|
+
const expectedCommitment = {
|
|
987
|
+
identifier: secret.identifier,
|
|
988
|
+
hiding: serializePoint(Point.BASE.multiply(hidingNonce0)),
|
|
989
|
+
binding: serializePoint(Point.BASE.multiply(bindingNonce0))
|
|
990
|
+
};
|
|
991
|
+
const commitment = commitmentList.find((i) => i.identifier === secret.identifier);
|
|
992
|
+
if (!commitment) throw new Error("missing signer commitment");
|
|
993
|
+
if (require_grant_rules.bytesToHex(commitment.hiding) !== require_grant_rules.bytesToHex(expectedCommitment.hiding) || require_grant_rules.bytesToHex(commitment.binding) !== require_grant_rules.bytesToHex(expectedCommitment.binding)) throw new Error("incorrect signer commitment");
|
|
994
|
+
if (opts.adjustSecret) secret = opts.adjustSecret(secret, pub);
|
|
995
|
+
if (opts.adjustPublic) pub = opts.adjustPublic(pub);
|
|
996
|
+
const SK = Fn.fromBytes(secret.signingShare);
|
|
997
|
+
const { lambda, challenge, bindingFactor, groupCommitment } = prepareShare(pub.commitments[0], commitmentList, msg, secret.identifier);
|
|
998
|
+
const N = opts.adjustNonces ? opts.adjustNonces(groupCommitment, nonces) : nonces;
|
|
999
|
+
const hidingNonce = opts.adjustNonces ? Fn.fromBytes(N.hiding) : hidingNonce0;
|
|
1000
|
+
const bindingNonce = opts.adjustNonces ? Fn.fromBytes(N.binding) : bindingNonce0;
|
|
1001
|
+
const t = Fn.mul(Fn.mul(lambda, SK), challenge);
|
|
1002
|
+
const t2 = Fn.mul(bindingNonce, bindingFactor);
|
|
1003
|
+
const r = Fn.toBytes(Fn.add(Fn.add(hidingNonce, t2), t));
|
|
1004
|
+
nonces.hiding.fill(0);
|
|
1005
|
+
nonces.binding.fill(0);
|
|
1006
|
+
return r;
|
|
1007
|
+
},
|
|
1008
|
+
verifyShare(pub, commitmentList, msg, identifier, sigShare) {
|
|
1009
|
+
if (opts.adjustPublic) pub = opts.adjustPublic(pub);
|
|
1010
|
+
const comm = commitmentList.find((i) => i.identifier === identifier);
|
|
1011
|
+
if (!comm) throw new Error("cannot find identifier commitment");
|
|
1012
|
+
const PK = parsePoint(pub.verifyingShares[identifier]);
|
|
1013
|
+
const hidingNonceCommitment = parsePoint(comm.hiding);
|
|
1014
|
+
const bindingNonceCommitment = parsePoint(comm.binding);
|
|
1015
|
+
const { lambda, challenge, bindingFactor, groupCommitment } = prepareShare(pub.commitments[0], commitmentList, msg, identifier);
|
|
1016
|
+
let commShare = hidingNonceCommitment.add(bindingNonceCommitment.multiply(bindingFactor));
|
|
1017
|
+
if (opts.adjustGroupCommitmentShare) commShare = opts.adjustGroupCommitmentShare(groupCommitment, commShare);
|
|
1018
|
+
const l = Point.BASE.multiply(Fn.fromBytes(sigShare));
|
|
1019
|
+
const r = commShare.add(PK.multiply(Fn.mul(challenge, lambda)));
|
|
1020
|
+
return l.equals(r);
|
|
1021
|
+
},
|
|
1022
|
+
aggregate(pub, commitmentList, msg, sigShares) {
|
|
1023
|
+
if (opts.adjustPublic) pub = opts.adjustPublic(pub);
|
|
1024
|
+
try {
|
|
1025
|
+
validateCommitmentsNum(pub.signers, commitmentList.length);
|
|
1026
|
+
} catch {
|
|
1027
|
+
throw new AggErr("aggregation failed", []);
|
|
1028
|
+
}
|
|
1029
|
+
const ids = commitmentList.map((i) => i.identifier);
|
|
1030
|
+
if (ids.length !== Object.keys(sigShares).length) throw new AggErr("aggregation failed", []);
|
|
1031
|
+
for (const id of ids) if (!(id in sigShares) || !(id in pub.verifyingShares)) throw new AggErr("aggregation failed", []);
|
|
1032
|
+
const GPK = parsePoint(pub.commitments[0]);
|
|
1033
|
+
const { groupCommitment } = getGroupCommitment(GPK, commitmentList, msg);
|
|
1034
|
+
let z = Fn.ZERO;
|
|
1035
|
+
for (const id of ids) z = Fn.add(z, Fn.fromBytes(sigShares[id]));
|
|
1036
|
+
if (!Basic.verify(msg, groupCommitment, z, GPK)) {
|
|
1037
|
+
const cheaters = [];
|
|
1038
|
+
for (const id of ids) if (!this.verifyShare(pub, commitmentList, msg, id, sigShares[id])) cheaters.push(id);
|
|
1039
|
+
throw new AggErr("aggregation failed", cheaters);
|
|
1040
|
+
}
|
|
1041
|
+
return Signature.encode(groupCommitment, z);
|
|
1042
|
+
},
|
|
1043
|
+
sign(msg, secretKey) {
|
|
1044
|
+
let sk = Fn.fromBytes(secretKey);
|
|
1045
|
+
if (opts.adjustScalar) sk = opts.adjustScalar(sk);
|
|
1046
|
+
const [R, z] = Basic.sign(msg, sk);
|
|
1047
|
+
return Signature.encode(R, z);
|
|
1048
|
+
},
|
|
1049
|
+
verify(sig, msg, publicKey) {
|
|
1050
|
+
const PK = opts.parsePublicKey ? opts.parsePublicKey(publicKey) : parsePoint(publicKey);
|
|
1051
|
+
const { R, z } = Signature.decode(sig);
|
|
1052
|
+
return Basic.verify(msg, R, z, PK);
|
|
1053
|
+
},
|
|
1054
|
+
combineSecret(shares, signers) {
|
|
1055
|
+
validateSigners(signers);
|
|
1056
|
+
if (!Array.isArray(shares) || shares.length < signers.min) throw new Error("wrong secret shares array");
|
|
1057
|
+
const points = [];
|
|
1058
|
+
const seen = {};
|
|
1059
|
+
for (const s of shares) {
|
|
1060
|
+
const idNum = parseIdentifier(s.identifier);
|
|
1061
|
+
const id = serializeIdentifier(idNum);
|
|
1062
|
+
if (seen[id]) throw new Error("duplicated id=" + id);
|
|
1063
|
+
seen[id] = true;
|
|
1064
|
+
points.push([idNum, Fn.fromBytes(s.signingShare)]);
|
|
1065
|
+
}
|
|
1066
|
+
const xCoords = points.map(([x]) => x);
|
|
1067
|
+
let res = Fn.ZERO;
|
|
1068
|
+
for (const [x, y] of points) res = Fn.add(res, Fn.mul(y, deriveInterpolatingValue(xCoords, x)));
|
|
1069
|
+
return Fn.toBytes(res);
|
|
1070
|
+
},
|
|
1071
|
+
utils: Object.freeze({
|
|
1072
|
+
Fn,
|
|
1073
|
+
randomScalar: (rng = require_grant_rules.randomBytes) => Fn.toBytes(genPointScalarPair(rng).scalar),
|
|
1074
|
+
generateSecretPolynomial: (signers, secret, coeffs, rng) => {
|
|
1075
|
+
const res = generateSecretPolynomial(signers, secret, coeffs, rng);
|
|
1076
|
+
return {
|
|
1077
|
+
...res,
|
|
1078
|
+
commitment: res.commitment.map(serializePoint)
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
})
|
|
1082
|
+
};
|
|
1083
|
+
return Object.freeze(frost);
|
|
1084
|
+
}
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/abstract/montgomery.js
|
|
1087
|
+
/**
|
|
1088
|
+
* Montgomery curve methods. It's not really whole montgomery curve,
|
|
1089
|
+
* just bunch of very specific methods for X25519 / X448 from
|
|
1090
|
+
* [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748)
|
|
1091
|
+
* @module
|
|
1092
|
+
*/
|
|
1093
|
+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
1094
|
+
const _0n$1 = BigInt(0);
|
|
1095
|
+
const _1n$1 = BigInt(1);
|
|
1096
|
+
const _2n$1 = BigInt(2);
|
|
1097
|
+
function validateOpts(curve) {
|
|
1098
|
+
require_grant_rules.validateObject(curve, {
|
|
1099
|
+
P: "bigint",
|
|
1100
|
+
type: "string",
|
|
1101
|
+
adjustScalarBytes: "function",
|
|
1102
|
+
powPminus2: "function"
|
|
1103
|
+
}, { randomBytes: "function" });
|
|
1104
|
+
return Object.freeze({ ...curve });
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* @param curveDef - Montgomery curve definition.
|
|
1108
|
+
* @returns ECDH helper namespace.
|
|
1109
|
+
* @throws If the curve definition or derived shared point is invalid. {@link Error}
|
|
1110
|
+
* @example
|
|
1111
|
+
* Perform one X25519 key exchange through the generic Montgomery helper.
|
|
1112
|
+
*
|
|
1113
|
+
* ```ts
|
|
1114
|
+
* import { x25519 } from '@noble/curves/ed25519.js';
|
|
1115
|
+
* const alice = x25519.keygen();
|
|
1116
|
+
* const shared = x25519.getSharedSecret(alice.secretKey, alice.publicKey);
|
|
1117
|
+
* ```
|
|
1118
|
+
*/
|
|
1119
|
+
function montgomery(curveDef) {
|
|
1120
|
+
const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = validateOpts(curveDef);
|
|
1121
|
+
const is25519 = type === "x25519";
|
|
1122
|
+
if (!is25519 && type !== "x448") throw new Error("invalid type");
|
|
1123
|
+
const randomBytes_ = rand === void 0 ? require_grant_rules.randomBytes : rand;
|
|
1124
|
+
const montgomeryBits = is25519 ? 255 : 448;
|
|
1125
|
+
const fieldLen = is25519 ? 32 : 56;
|
|
1126
|
+
const Gu = is25519 ? BigInt(9) : BigInt(5);
|
|
1127
|
+
const a24 = is25519 ? BigInt(121665) : BigInt(39081);
|
|
1128
|
+
const minScalar = is25519 ? _2n$1 ** BigInt(254) : _2n$1 ** BigInt(447);
|
|
1129
|
+
const maxScalar = minScalar + (is25519 ? BigInt(8) * _2n$1 ** BigInt(251) - _1n$1 : BigInt(4) * _2n$1 ** BigInt(445) - _1n$1) + _1n$1;
|
|
1130
|
+
const modP = (n) => require_grant_rules.mod(n, P);
|
|
1131
|
+
const GuBytes = encodeU(Gu);
|
|
1132
|
+
function encodeU(u) {
|
|
1133
|
+
return require_grant_rules.numberToBytesLE(modP(u), fieldLen);
|
|
1134
|
+
}
|
|
1135
|
+
function decodeU(u) {
|
|
1136
|
+
const _u = require_grant_rules.copyBytes(require_grant_rules.abytes(u, fieldLen, "uCoordinate"));
|
|
1137
|
+
if (is25519) _u[31] &= 127;
|
|
1138
|
+
return modP(require_grant_rules.bytesToNumberLE(_u));
|
|
1139
|
+
}
|
|
1140
|
+
function decodeScalar(scalar) {
|
|
1141
|
+
return require_grant_rules.bytesToNumberLE(adjustScalarBytes(require_grant_rules.copyBytes(require_grant_rules.abytes(scalar, fieldLen, "scalar"))));
|
|
1142
|
+
}
|
|
1143
|
+
function scalarMult(scalar, u) {
|
|
1144
|
+
const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
|
|
1145
|
+
if (pu === _0n$1) throw new Error("invalid private or public key received");
|
|
1146
|
+
return encodeU(pu);
|
|
1147
|
+
}
|
|
1148
|
+
function scalarMultBase(scalar) {
|
|
1149
|
+
return scalarMult(scalar, GuBytes);
|
|
1150
|
+
}
|
|
1151
|
+
const getPublicKey = scalarMultBase;
|
|
1152
|
+
const getSharedSecret = scalarMult;
|
|
1153
|
+
function cswap(swap, x_2, x_3) {
|
|
1154
|
+
const dummy = modP(swap * (x_2 - x_3));
|
|
1155
|
+
x_2 = modP(x_2 - dummy);
|
|
1156
|
+
x_3 = modP(x_3 + dummy);
|
|
1157
|
+
return {
|
|
1158
|
+
x_2,
|
|
1159
|
+
x_3
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Montgomery x-only multiplication ladder for the selected X25519/X448 curve.
|
|
1164
|
+
* @param pointU - decoded Montgomery u coordinate for the selected curve
|
|
1165
|
+
* @param scalar - decoded clamped scalar by which the point is multiplied
|
|
1166
|
+
* @returns resulting Montgomery u coordinate for the selected curve
|
|
1167
|
+
*/
|
|
1168
|
+
function montgomeryLadder(u, scalar) {
|
|
1169
|
+
require_grant_rules.aInRange("u", u, _0n$1, P);
|
|
1170
|
+
require_grant_rules.aInRange("scalar", scalar, minScalar, maxScalar);
|
|
1171
|
+
const k = scalar;
|
|
1172
|
+
const x_1 = u;
|
|
1173
|
+
let x_2 = _1n$1;
|
|
1174
|
+
let z_2 = _0n$1;
|
|
1175
|
+
let x_3 = u;
|
|
1176
|
+
let z_3 = _1n$1;
|
|
1177
|
+
let swap = _0n$1;
|
|
1178
|
+
for (let t = BigInt(montgomeryBits - 1); t >= _0n$1; t--) {
|
|
1179
|
+
const k_t = k >> t & _1n$1;
|
|
1180
|
+
swap ^= k_t;
|
|
1181
|
+
({x_2, x_3} = cswap(swap, x_2, x_3));
|
|
1182
|
+
({x_2: z_2, x_3: z_3} = cswap(swap, z_2, z_3));
|
|
1183
|
+
swap = k_t;
|
|
1184
|
+
const A = x_2 + z_2;
|
|
1185
|
+
const AA = modP(A * A);
|
|
1186
|
+
const B = x_2 - z_2;
|
|
1187
|
+
const BB = modP(B * B);
|
|
1188
|
+
const E = AA - BB;
|
|
1189
|
+
const C = x_3 + z_3;
|
|
1190
|
+
const DA = modP((x_3 - z_3) * A);
|
|
1191
|
+
const CB = modP(C * B);
|
|
1192
|
+
const dacb = DA + CB;
|
|
1193
|
+
const da_cb = DA - CB;
|
|
1194
|
+
x_3 = modP(dacb * dacb);
|
|
1195
|
+
z_3 = modP(x_1 * modP(da_cb * da_cb));
|
|
1196
|
+
x_2 = modP(AA * BB);
|
|
1197
|
+
z_2 = modP(E * (AA + modP(a24 * E)));
|
|
1198
|
+
}
|
|
1199
|
+
({x_2, x_3} = cswap(swap, x_2, x_3));
|
|
1200
|
+
({x_2: z_2, x_3: z_3} = cswap(swap, z_2, z_3));
|
|
1201
|
+
const z2 = powPminus2(z_2);
|
|
1202
|
+
return modP(x_2 * z2);
|
|
1203
|
+
}
|
|
1204
|
+
const lengths = {
|
|
1205
|
+
secretKey: fieldLen,
|
|
1206
|
+
publicKey: fieldLen,
|
|
1207
|
+
seed: fieldLen
|
|
1208
|
+
};
|
|
1209
|
+
const randomSecretKey = (seed) => {
|
|
1210
|
+
seed = seed === void 0 ? randomBytes_(fieldLen) : seed;
|
|
1211
|
+
require_grant_rules.abytes(seed, lengths.seed, "seed");
|
|
1212
|
+
return seed;
|
|
1213
|
+
};
|
|
1214
|
+
const utils = { randomSecretKey };
|
|
1215
|
+
Object.freeze(lengths);
|
|
1216
|
+
Object.freeze(utils);
|
|
1217
|
+
return Object.freeze({
|
|
1218
|
+
keygen: require_grant_rules.createKeygen(randomSecretKey, getPublicKey),
|
|
1219
|
+
getSharedSecret,
|
|
1220
|
+
getPublicKey,
|
|
1221
|
+
scalarMult,
|
|
1222
|
+
scalarMultBase,
|
|
1223
|
+
utils,
|
|
1224
|
+
GuBytes: GuBytes.slice(),
|
|
1225
|
+
lengths
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/abstract/oprf.js
|
|
1230
|
+
/**
|
|
1231
|
+
* RFC 9497: Oblivious Pseudorandom Functions (OPRFs) Using Prime-Order Groups.
|
|
1232
|
+
* https://www.rfc-editor.org/rfc/rfc9497
|
|
1233
|
+
*
|
|
1234
|
+
|
|
1235
|
+
OPRF allows to interactively create an `Output = PRF(Input, serverSecretKey)`:
|
|
1236
|
+
|
|
1237
|
+
- Server cannot calculate Output by itself: it doesn't know Input
|
|
1238
|
+
- Client cannot calculate Output by itself: it doesn't know server secretKey
|
|
1239
|
+
- An attacker interception the communication can't restore Input/Output/serverSecretKey and can't
|
|
1240
|
+
link Input to some value.
|
|
1241
|
+
|
|
1242
|
+
## Issues
|
|
1243
|
+
|
|
1244
|
+
- Low-entropy inputs (e.g. password '123') enable brute-forced dictionary attacks by the server
|
|
1245
|
+
(solveable by domain separation in POPRF)
|
|
1246
|
+
- High-level protocol needs to be constructed on top, because OPRF is low-level
|
|
1247
|
+
|
|
1248
|
+
## Use cases
|
|
1249
|
+
|
|
1250
|
+
1. **Password-Authenticated Key Exchange (PAKE):** Enables secure password login (e.g., OPAQUE)
|
|
1251
|
+
without revealing the password to the server.
|
|
1252
|
+
2. **Private Set Intersection (PSI):** Allows two parties to compute the intersection of their
|
|
1253
|
+
private sets without revealing non-intersecting elements.
|
|
1254
|
+
3. **Anonymous Credential Systems:** Supports issuance of anonymous, unlinkable credentials
|
|
1255
|
+
(e.g., Privacy Pass) using blind OPRF evaluation.
|
|
1256
|
+
4. **Private Information Retrieval (PIR):** Helps users query databases without revealing which
|
|
1257
|
+
item they accessed.
|
|
1258
|
+
5. **Encrypted Search / Secure Indexing:** Enables keyword search over encrypted data while keeping
|
|
1259
|
+
queries private.
|
|
1260
|
+
6. **Spam Prevention and Rate-Limiting:** Issues anonymous tokens to prevent abuse
|
|
1261
|
+
(e.g., CAPTCHA bypass) without compromising user privacy.
|
|
1262
|
+
|
|
1263
|
+
## Modes
|
|
1264
|
+
|
|
1265
|
+
- OPRF: simple mode, client doesn't need to know server public key
|
|
1266
|
+
- VOPRF: verifiable mode. It lets the client verify that the server used the
|
|
1267
|
+
secret key corresponding to a known public key
|
|
1268
|
+
- POPRF: partially oblivious mode, VOPRF + domain separation
|
|
1269
|
+
|
|
1270
|
+
There is also non-interactive mode (Evaluate), which creates Output
|
|
1271
|
+
non-interactively with knowledge of the secret key.
|
|
1272
|
+
|
|
1273
|
+
Flow:
|
|
1274
|
+
- (once) Server generates secret and public keys, distributes public keys to clients
|
|
1275
|
+
- deterministically: `deriveKeyPair` or just random: `generateKeyPair`
|
|
1276
|
+
- Client blinds input: `blind(secretInput)`
|
|
1277
|
+
- Server evaluates blinded input: `blindEvaluate` generated by client, sends result to client
|
|
1278
|
+
- Client creates output using result of evaluation via 'finalize'
|
|
1279
|
+
|
|
1280
|
+
* @module
|
|
1281
|
+
*/
|
|
1282
|
+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
1283
|
+
const _DST_scalarBytes = /* @__PURE__ */ require_grant_rules.asciiToBytes(_DST_scalar);
|
|
1284
|
+
/**
|
|
1285
|
+
* @param opts - OPRF ciphersuite options. See {@link OPRFOpts}.
|
|
1286
|
+
* @returns OPRF helper namespace.
|
|
1287
|
+
* @example
|
|
1288
|
+
* Instantiate an OPRF suite from curve-specific hashing hooks.
|
|
1289
|
+
*
|
|
1290
|
+
* ```ts
|
|
1291
|
+
* import { createOPRF } from '@noble/curves/abstract/oprf.js';
|
|
1292
|
+
* import { p256, p256_hasher } from '@noble/curves/nist.js';
|
|
1293
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
1294
|
+
* const oprf = createOPRF({
|
|
1295
|
+
* name: 'P256-SHA256',
|
|
1296
|
+
* Point: p256.Point,
|
|
1297
|
+
* hash: sha256,
|
|
1298
|
+
* hashToGroup: p256_hasher.hashToCurve,
|
|
1299
|
+
* hashToScalar: p256_hasher.hashToScalar,
|
|
1300
|
+
* });
|
|
1301
|
+
* const keys = oprf.oprf.generateKeyPair();
|
|
1302
|
+
* ```
|
|
1303
|
+
*/
|
|
1304
|
+
function createOPRF(opts) {
|
|
1305
|
+
require_grant_rules.validateObject(opts, {
|
|
1306
|
+
name: "string",
|
|
1307
|
+
hash: "function",
|
|
1308
|
+
hashToScalar: "function",
|
|
1309
|
+
hashToGroup: "function"
|
|
1310
|
+
});
|
|
1311
|
+
require_grant_rules.validatePointCons(opts.Point);
|
|
1312
|
+
const { name, Point, hash } = opts;
|
|
1313
|
+
const { Fn } = Point;
|
|
1314
|
+
const hashToGroup = (msg, ctx) => opts.hashToGroup(msg, { DST: require_grant_rules.concatBytes(require_grant_rules.asciiToBytes("HashToGroup-"), ctx) });
|
|
1315
|
+
const hashToScalarPrefixed = (msg, ctx) => opts.hashToScalar(msg, { DST: require_grant_rules.concatBytes(_DST_scalarBytes, ctx) });
|
|
1316
|
+
const randomScalar = (rng = require_grant_rules.randomBytes) => {
|
|
1317
|
+
const t = require_grant_rules.mapHashToField(rng(require_grant_rules.getMinHashLength(Fn.ORDER)), Fn.ORDER, Fn.isLE);
|
|
1318
|
+
return Fn.isLE ? require_grant_rules.bytesToNumberLE(t) : require_grant_rules.bytesToNumberBE(t);
|
|
1319
|
+
};
|
|
1320
|
+
const msm = (points, scalars) => require_grant_rules.pippenger(Point, points, scalars);
|
|
1321
|
+
const getCtx = (mode) => require_grant_rules.concatBytes(require_grant_rules.asciiToBytes("OPRFV1-"), new Uint8Array([mode]), require_grant_rules.asciiToBytes("-" + name));
|
|
1322
|
+
const ctxOPRF = getCtx(0);
|
|
1323
|
+
const ctxVOPRF = getCtx(1);
|
|
1324
|
+
const ctxPOPRF = getCtx(2);
|
|
1325
|
+
function encode(...args) {
|
|
1326
|
+
const res = [];
|
|
1327
|
+
for (const a of args) if (typeof a === "number") res.push(require_grant_rules.numberToBytesBE(a, 2));
|
|
1328
|
+
else if (typeof a === "string") res.push(require_grant_rules.asciiToBytes(a));
|
|
1329
|
+
else {
|
|
1330
|
+
require_grant_rules.abytes(a);
|
|
1331
|
+
res.push(require_grant_rules.numberToBytesBE(a.length, 2), a);
|
|
1332
|
+
}
|
|
1333
|
+
return require_grant_rules.concatBytes(...res);
|
|
1334
|
+
}
|
|
1335
|
+
const inputBytes = (title, bytes) => {
|
|
1336
|
+
require_grant_rules.abytes(bytes, void 0, title);
|
|
1337
|
+
if (bytes.length > 65535) throw new Error(`"${title}" expected Uint8Array of length <= 65535, got length=${bytes.length}`);
|
|
1338
|
+
return bytes;
|
|
1339
|
+
};
|
|
1340
|
+
const hashInput = (...bytes) => hash(encode(...bytes, "Finalize"));
|
|
1341
|
+
function getTranscripts(B, C, D, ctx) {
|
|
1342
|
+
const seed = hash(encode(B.toBytes(), require_grant_rules.concatBytes(require_grant_rules.asciiToBytes("Seed-"), ctx)));
|
|
1343
|
+
const res = [];
|
|
1344
|
+
for (let i = 0; i < C.length; i++) {
|
|
1345
|
+
const Ci = C[i].toBytes();
|
|
1346
|
+
const Di = D[i].toBytes();
|
|
1347
|
+
const di = hashToScalarPrefixed(encode(seed, i, Ci, Di, "Composite"), ctx);
|
|
1348
|
+
res.push(di);
|
|
1349
|
+
}
|
|
1350
|
+
return res;
|
|
1351
|
+
}
|
|
1352
|
+
function computeComposites(B, C, D, ctx) {
|
|
1353
|
+
const T = getTranscripts(B, C, D, ctx);
|
|
1354
|
+
return {
|
|
1355
|
+
M: msm(C, T),
|
|
1356
|
+
Z: msm(D, T)
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function computeCompositesFast(k, B, C, D, ctx) {
|
|
1360
|
+
const M = msm(C, getTranscripts(B, C, D, ctx));
|
|
1361
|
+
return {
|
|
1362
|
+
M,
|
|
1363
|
+
Z: M.multiply(k)
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function challengeTranscript(B, M, Z, t2, t3, ctx) {
|
|
1367
|
+
const [Bm, a0, a1, a2, a3] = [
|
|
1368
|
+
B,
|
|
1369
|
+
M,
|
|
1370
|
+
Z,
|
|
1371
|
+
t2,
|
|
1372
|
+
t3
|
|
1373
|
+
].map((i) => i.toBytes());
|
|
1374
|
+
return hashToScalarPrefixed(encode(Bm, a0, a1, a2, a3, "Challenge"), ctx);
|
|
1375
|
+
}
|
|
1376
|
+
function generateProof(ctx, k, B, C, D, rng) {
|
|
1377
|
+
const { M, Z } = computeCompositesFast(k, B, C, D, ctx);
|
|
1378
|
+
const r = randomScalar(rng);
|
|
1379
|
+
const c = challengeTranscript(B, M, Z, Point.BASE.multiply(r), M.multiply(r), ctx);
|
|
1380
|
+
return require_grant_rules.concatBytes(...[c, Fn.sub(r, Fn.mul(c, k))].map((i) => Fn.toBytes(i)));
|
|
1381
|
+
}
|
|
1382
|
+
function verifyProof(ctx, B, C, D, proof) {
|
|
1383
|
+
require_grant_rules.abytes(proof, 2 * Fn.BYTES);
|
|
1384
|
+
const { M, Z } = computeComposites(B, C, D, ctx);
|
|
1385
|
+
const [c, s] = [proof.subarray(0, Fn.BYTES), proof.subarray(Fn.BYTES)].map((f) => Fn.fromBytes(f));
|
|
1386
|
+
const expectedC = challengeTranscript(B, M, Z, Point.BASE.multiply(s).add(B.multiply(c)), M.multiply(s).add(Z.multiply(c)), ctx);
|
|
1387
|
+
if (!Fn.eql(c, expectedC)) throw new Error("proof verification failed");
|
|
1388
|
+
}
|
|
1389
|
+
function generateKeyPair() {
|
|
1390
|
+
const skS = randomScalar();
|
|
1391
|
+
const pkS = Point.BASE.multiply(skS);
|
|
1392
|
+
return {
|
|
1393
|
+
secretKey: Fn.toBytes(skS),
|
|
1394
|
+
publicKey: pkS.toBytes()
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
function deriveKeyPair(ctx, seed, info) {
|
|
1398
|
+
require_grant_rules.abytes(seed, 32, "seed");
|
|
1399
|
+
info = inputBytes("keyInfo", info);
|
|
1400
|
+
const dst = require_grant_rules.concatBytes(require_grant_rules.asciiToBytes("DeriveKeyPair"), ctx);
|
|
1401
|
+
const msg = require_grant_rules.concatBytes(seed, encode(info), Uint8Array.of(0));
|
|
1402
|
+
for (let counter = 0; counter <= 255; counter++) {
|
|
1403
|
+
msg[msg.length - 1] = counter;
|
|
1404
|
+
const skS = opts.hashToScalar(msg, { DST: dst });
|
|
1405
|
+
if (Fn.is0(skS)) continue;
|
|
1406
|
+
return {
|
|
1407
|
+
secretKey: Fn.toBytes(skS),
|
|
1408
|
+
publicKey: Point.BASE.multiply(skS).toBytes()
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
throw new Error("Cannot derive key");
|
|
1412
|
+
}
|
|
1413
|
+
const wirePoint = (label, bytes) => {
|
|
1414
|
+
const point = Point.fromBytes(bytes);
|
|
1415
|
+
if (point.equals(Point.ZERO)) throw new Error(label + " point at infinity");
|
|
1416
|
+
return point;
|
|
1417
|
+
};
|
|
1418
|
+
function blind(ctx, input, rng = require_grant_rules.randomBytes) {
|
|
1419
|
+
input = inputBytes("input", input);
|
|
1420
|
+
const blind = randomScalar(rng);
|
|
1421
|
+
const inputPoint = hashToGroup(input, ctx);
|
|
1422
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error("Input point at infinity");
|
|
1423
|
+
const blinded = inputPoint.multiply(blind);
|
|
1424
|
+
return {
|
|
1425
|
+
blind: Fn.toBytes(blind),
|
|
1426
|
+
blinded: blinded.toBytes()
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
function evaluate(ctx, secretKey, input) {
|
|
1430
|
+
input = inputBytes("input", input);
|
|
1431
|
+
const skS = Fn.fromBytes(secretKey);
|
|
1432
|
+
const inputPoint = hashToGroup(input, ctx);
|
|
1433
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error("Input point at infinity");
|
|
1434
|
+
const unblinded = inputPoint.multiply(skS).toBytes();
|
|
1435
|
+
return hashInput(input, unblinded);
|
|
1436
|
+
}
|
|
1437
|
+
const oprf = Object.freeze({
|
|
1438
|
+
generateKeyPair,
|
|
1439
|
+
deriveKeyPair: (seed, keyInfo) => deriveKeyPair(ctxOPRF, seed, keyInfo),
|
|
1440
|
+
blind: (input, rng = require_grant_rules.randomBytes) => blind(ctxOPRF, input, rng),
|
|
1441
|
+
blindEvaluate(secretKey, blindedPoint) {
|
|
1442
|
+
const skS = Fn.fromBytes(secretKey);
|
|
1443
|
+
return wirePoint("blinded", blindedPoint).multiply(skS).toBytes();
|
|
1444
|
+
},
|
|
1445
|
+
finalize(input, blindBytes, evaluatedBytes) {
|
|
1446
|
+
input = inputBytes("input", input);
|
|
1447
|
+
const blind = Fn.fromBytes(blindBytes);
|
|
1448
|
+
const unblinded = wirePoint("evaluated", evaluatedBytes).multiply(Fn.inv(blind)).toBytes();
|
|
1449
|
+
return hashInput(input, unblinded);
|
|
1450
|
+
},
|
|
1451
|
+
evaluate: (secretKey, input) => evaluate(ctxOPRF, secretKey, input)
|
|
1452
|
+
});
|
|
1453
|
+
const voprf = Object.freeze({
|
|
1454
|
+
generateKeyPair,
|
|
1455
|
+
deriveKeyPair: (seed, keyInfo) => deriveKeyPair(ctxVOPRF, seed, keyInfo),
|
|
1456
|
+
blind: (input, rng = require_grant_rules.randomBytes) => blind(ctxVOPRF, input, rng),
|
|
1457
|
+
blindEvaluateBatch(secretKey, publicKey, blinded, rng = require_grant_rules.randomBytes) {
|
|
1458
|
+
if (!Array.isArray(blinded)) throw new Error("expected array");
|
|
1459
|
+
const skS = Fn.fromBytes(secretKey);
|
|
1460
|
+
const pkS = wirePoint("public key", publicKey);
|
|
1461
|
+
const blindedPoints = blinded.map((i) => wirePoint("blinded", i));
|
|
1462
|
+
const evaluated = blindedPoints.map((i) => i.multiply(skS));
|
|
1463
|
+
const proof = generateProof(ctxVOPRF, skS, pkS, blindedPoints, evaluated, rng);
|
|
1464
|
+
return {
|
|
1465
|
+
evaluated: evaluated.map((i) => i.toBytes()),
|
|
1466
|
+
proof
|
|
1467
|
+
};
|
|
1468
|
+
},
|
|
1469
|
+
blindEvaluate(secretKey, publicKey, blinded, rng = require_grant_rules.randomBytes) {
|
|
1470
|
+
const res = this.blindEvaluateBatch(secretKey, publicKey, [blinded], rng);
|
|
1471
|
+
return {
|
|
1472
|
+
evaluated: res.evaluated[0],
|
|
1473
|
+
proof: res.proof
|
|
1474
|
+
};
|
|
1475
|
+
},
|
|
1476
|
+
finalizeBatch(items, publicKey, proof) {
|
|
1477
|
+
if (!Array.isArray(items)) throw new Error("expected array");
|
|
1478
|
+
verifyProof(ctxVOPRF, wirePoint("public key", publicKey), items.map((i) => wirePoint("blinded", i.blinded)), items.map((i) => wirePoint("evaluated", i.evaluated)), proof);
|
|
1479
|
+
return items.map((i) => oprf.finalize(i.input, i.blind, i.evaluated));
|
|
1480
|
+
},
|
|
1481
|
+
finalize(input, blind, evaluated, blinded, publicKey, proof) {
|
|
1482
|
+
return this.finalizeBatch([{
|
|
1483
|
+
input,
|
|
1484
|
+
blind,
|
|
1485
|
+
evaluated,
|
|
1486
|
+
blinded
|
|
1487
|
+
}], publicKey, proof)[0];
|
|
1488
|
+
},
|
|
1489
|
+
evaluate: (secretKey, input) => evaluate(ctxVOPRF, secretKey, input)
|
|
1490
|
+
});
|
|
1491
|
+
const poprf = (info) => {
|
|
1492
|
+
info = inputBytes("info", info);
|
|
1493
|
+
const m = hashToScalarPrefixed(encode("Info", info), ctxPOPRF);
|
|
1494
|
+
const T = Point.BASE.multiply(m);
|
|
1495
|
+
return Object.freeze({
|
|
1496
|
+
generateKeyPair,
|
|
1497
|
+
deriveKeyPair: (seed, keyInfo) => deriveKeyPair(ctxPOPRF, seed, keyInfo),
|
|
1498
|
+
blind(input, publicKey, rng = require_grant_rules.randomBytes) {
|
|
1499
|
+
input = inputBytes("input", input);
|
|
1500
|
+
const pkS = wirePoint("public key", publicKey);
|
|
1501
|
+
const tweakedKey = T.add(pkS);
|
|
1502
|
+
if (tweakedKey.equals(Point.ZERO)) throw new Error("tweakedKey point at infinity");
|
|
1503
|
+
const blind = randomScalar(rng);
|
|
1504
|
+
const inputPoint = hashToGroup(input, ctxPOPRF);
|
|
1505
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error("Input point at infinity");
|
|
1506
|
+
const blindedPoint = inputPoint.multiply(blind);
|
|
1507
|
+
return {
|
|
1508
|
+
blind: Fn.toBytes(blind),
|
|
1509
|
+
blinded: blindedPoint.toBytes(),
|
|
1510
|
+
tweakedKey: tweakedKey.toBytes()
|
|
1511
|
+
};
|
|
1512
|
+
},
|
|
1513
|
+
blindEvaluateBatch(secretKey, blinded, rng = require_grant_rules.randomBytes) {
|
|
1514
|
+
if (!Array.isArray(blinded)) throw new Error("expected array");
|
|
1515
|
+
const skS = Fn.fromBytes(secretKey);
|
|
1516
|
+
const t = Fn.add(skS, m);
|
|
1517
|
+
const invT = Fn.inv(t);
|
|
1518
|
+
const blindedPoints = blinded.map((i) => wirePoint("blinded", i));
|
|
1519
|
+
const evalPoints = blindedPoints.map((i) => i.multiply(invT));
|
|
1520
|
+
const proof = generateProof(ctxPOPRF, t, Point.BASE.multiply(t), evalPoints, blindedPoints, rng);
|
|
1521
|
+
return {
|
|
1522
|
+
evaluated: evalPoints.map((i) => i.toBytes()),
|
|
1523
|
+
proof
|
|
1524
|
+
};
|
|
1525
|
+
},
|
|
1526
|
+
blindEvaluate(secretKey, blinded, rng = require_grant_rules.randomBytes) {
|
|
1527
|
+
const res = this.blindEvaluateBatch(secretKey, [blinded], rng);
|
|
1528
|
+
return {
|
|
1529
|
+
evaluated: res.evaluated[0],
|
|
1530
|
+
proof: res.proof
|
|
1531
|
+
};
|
|
1532
|
+
},
|
|
1533
|
+
finalizeBatch(items, proof, tweakedKey) {
|
|
1534
|
+
if (!Array.isArray(items)) throw new Error("expected array");
|
|
1535
|
+
const inputs = items.map((i) => inputBytes("input", i.input));
|
|
1536
|
+
const evalPoints = items.map((i) => wirePoint("evaluated", i.evaluated));
|
|
1537
|
+
verifyProof(ctxPOPRF, wirePoint("tweakedKey", tweakedKey), evalPoints, items.map((i) => wirePoint("blinded", i.blinded)), proof);
|
|
1538
|
+
return items.map((i, j) => {
|
|
1539
|
+
const blind = Fn.fromBytes(i.blind);
|
|
1540
|
+
const point = evalPoints[j].multiply(Fn.inv(blind)).toBytes();
|
|
1541
|
+
return hashInput(inputs[j], info, point);
|
|
1542
|
+
});
|
|
1543
|
+
},
|
|
1544
|
+
finalize(input, blind, evaluated, blinded, proof, tweakedKey) {
|
|
1545
|
+
return this.finalizeBatch([{
|
|
1546
|
+
input,
|
|
1547
|
+
blind,
|
|
1548
|
+
evaluated,
|
|
1549
|
+
blinded
|
|
1550
|
+
}], proof, tweakedKey)[0];
|
|
1551
|
+
},
|
|
1552
|
+
evaluate(secretKey, input) {
|
|
1553
|
+
input = inputBytes("input", input);
|
|
1554
|
+
const skS = Fn.fromBytes(secretKey);
|
|
1555
|
+
const inputPoint = hashToGroup(input, ctxPOPRF);
|
|
1556
|
+
if (inputPoint.equals(Point.ZERO)) throw new Error("Input point at infinity");
|
|
1557
|
+
const t = Fn.add(skS, m);
|
|
1558
|
+
const invT = Fn.inv(t);
|
|
1559
|
+
const unblinded = inputPoint.multiply(invT).toBytes();
|
|
1560
|
+
return hashInput(input, info, unblinded);
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
};
|
|
1564
|
+
const res = {
|
|
1565
|
+
name,
|
|
1566
|
+
oprf,
|
|
1567
|
+
voprf,
|
|
1568
|
+
poprf,
|
|
1569
|
+
__tests: Object.freeze({ Fn })
|
|
1570
|
+
};
|
|
1571
|
+
return Object.freeze(res);
|
|
1572
|
+
}
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region ../../packages/module/passport-account/node_modules/@noble/curves/ed25519.js
|
|
1575
|
+
/**
|
|
1576
|
+
* ed25519 Twisted Edwards curve with following addons:
|
|
1577
|
+
* - X25519 ECDH
|
|
1578
|
+
* - Ristretto cofactor elimination
|
|
1579
|
+
* - Elligator hash-to-group / point indistinguishability
|
|
1580
|
+
* @module
|
|
1581
|
+
*/
|
|
1582
|
+
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
1583
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _3n = /* @__PURE__ */ BigInt(3);
|
|
1584
|
+
const _5n = /* @__PURE__ */ BigInt(5), _8n = /* @__PURE__ */ BigInt(8);
|
|
1585
|
+
const ed25519_CURVE_p = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
|
1586
|
+
const ed25519_CURVE = {
|
|
1587
|
+
p: ed25519_CURVE_p,
|
|
1588
|
+
n: BigInt("0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"),
|
|
1589
|
+
h: _8n,
|
|
1590
|
+
a: BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec"),
|
|
1591
|
+
d: BigInt("0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3"),
|
|
1592
|
+
Gx: BigInt("0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a"),
|
|
1593
|
+
Gy: BigInt("0x6666666666666666666666666666666666666666666666666666666666666658")
|
|
1594
|
+
};
|
|
1595
|
+
function ed25519_pow_2_252_3(x) {
|
|
1596
|
+
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
|
1597
|
+
const P = ed25519_CURVE_p;
|
|
1598
|
+
const b2 = x * x % P * x % P;
|
|
1599
|
+
const b5 = require_grant_rules.pow2(require_grant_rules.pow2(b2, _2n, P) * b2 % P, _1n, P) * x % P;
|
|
1600
|
+
const b10 = require_grant_rules.pow2(b5, _5n, P) * b5 % P;
|
|
1601
|
+
const b20 = require_grant_rules.pow2(b10, _10n, P) * b10 % P;
|
|
1602
|
+
const b40 = require_grant_rules.pow2(b20, _20n, P) * b20 % P;
|
|
1603
|
+
const b80 = require_grant_rules.pow2(b40, _40n, P) * b40 % P;
|
|
1604
|
+
return {
|
|
1605
|
+
pow_p_5_8: require_grant_rules.pow2(require_grant_rules.pow2(require_grant_rules.pow2(require_grant_rules.pow2(b80, _80n, P) * b80 % P, _80n, P) * b80 % P, _10n, P) * b10 % P, _2n, P) * x % P,
|
|
1606
|
+
b2
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function adjustScalarBytes(bytes) {
|
|
1610
|
+
bytes[0] &= 248;
|
|
1611
|
+
bytes[31] &= 127;
|
|
1612
|
+
bytes[31] |= 64;
|
|
1613
|
+
return bytes;
|
|
1614
|
+
}
|
|
1615
|
+
const ED25519_SQRT_M1 = /* @__PURE__ */ BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752");
|
|
1616
|
+
function uvRatio(u, v) {
|
|
1617
|
+
const P = ed25519_CURVE_p;
|
|
1618
|
+
const v3 = require_grant_rules.mod(v * v * v, P);
|
|
1619
|
+
const pow = ed25519_pow_2_252_3(u * require_grant_rules.mod(v3 * v3 * v, P)).pow_p_5_8;
|
|
1620
|
+
let x = require_grant_rules.mod(u * v3 * pow, P);
|
|
1621
|
+
const vx2 = require_grant_rules.mod(v * x * x, P);
|
|
1622
|
+
const root1 = x;
|
|
1623
|
+
const root2 = require_grant_rules.mod(x * ED25519_SQRT_M1, P);
|
|
1624
|
+
const useRoot1 = vx2 === u;
|
|
1625
|
+
const useRoot2 = vx2 === require_grant_rules.mod(-u, P);
|
|
1626
|
+
const noRoot = vx2 === require_grant_rules.mod(-u * ED25519_SQRT_M1, P);
|
|
1627
|
+
if (useRoot1) x = root1;
|
|
1628
|
+
if (useRoot2 || noRoot) x = root2;
|
|
1629
|
+
if (require_grant_rules.isNegativeLE(x, P)) x = require_grant_rules.mod(-x, P);
|
|
1630
|
+
return {
|
|
1631
|
+
isValid: useRoot1 || useRoot2,
|
|
1632
|
+
value: x
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
const ed25519_Point = /* @__PURE__ */ require_grant_rules.edwards(ed25519_CURVE, { uvRatio });
|
|
1636
|
+
const Fp = ed25519_Point.Fp;
|
|
1637
|
+
const Fn = ed25519_Point.Fn;
|
|
1638
|
+
createFROST({
|
|
1639
|
+
name: "FROST-ED25519-SHA512-v1",
|
|
1640
|
+
Point: ed25519_Point,
|
|
1641
|
+
validatePoint: (p) => {
|
|
1642
|
+
p.assertValidity();
|
|
1643
|
+
if (!p.isTorsionFree()) throw new Error("bad point: not torsion-free");
|
|
1644
|
+
},
|
|
1645
|
+
hash: require_grant_rules.sha512,
|
|
1646
|
+
H2: ""
|
|
1647
|
+
});
|
|
1648
|
+
/**
|
|
1649
|
+
* ECDH using curve25519 aka x25519.
|
|
1650
|
+
* `getSharedSecret()` rejects low-order peer inputs by default, and seeded
|
|
1651
|
+
* `keygen(seed)` reuses the provided 32-byte seed buffer instead of copying it.
|
|
1652
|
+
* @example
|
|
1653
|
+
* Derive one shared secret between two X25519 peers.
|
|
1654
|
+
*
|
|
1655
|
+
* ```js
|
|
1656
|
+
* import { x25519 } from '@noble/curves/ed25519.js';
|
|
1657
|
+
* const alice = x25519.keygen();
|
|
1658
|
+
* const bob = x25519.keygen();
|
|
1659
|
+
* const shared = x25519.getSharedSecret(alice.secretKey, bob.publicKey);
|
|
1660
|
+
* ```
|
|
1661
|
+
*/
|
|
1662
|
+
const x25519 = /* @__PURE__ */ (() => {
|
|
1663
|
+
const P = ed25519_CURVE_p;
|
|
1664
|
+
return montgomery({
|
|
1665
|
+
P,
|
|
1666
|
+
type: "x25519",
|
|
1667
|
+
powPminus2: (x) => {
|
|
1668
|
+
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
|
1669
|
+
return require_grant_rules.mod(require_grant_rules.pow2(pow_p_5_8, _3n, P) * b2, P);
|
|
1670
|
+
},
|
|
1671
|
+
adjustScalarBytes
|
|
1672
|
+
});
|
|
1673
|
+
})();
|
|
1674
|
+
const ELL2_C1 = (ed25519_CURVE_p + _3n) / _8n;
|
|
1675
|
+
const ELL2_C2 = Fp.pow(_2n, ELL2_C1);
|
|
1676
|
+
const ELL2_C3 = Fp.sqrt(Fp.neg(Fp.ONE));
|
|
1677
|
+
/**
|
|
1678
|
+
* RFC 9380 method `map_to_curve_elligator2_curve25519`. Experimental name: may be renamed later.
|
|
1679
|
+
* @private
|
|
1680
|
+
*/
|
|
1681
|
+
function _map_to_curve_elligator2_curve25519(u) {
|
|
1682
|
+
const ELL2_C4 = (ed25519_CURVE_p - _5n) / _8n;
|
|
1683
|
+
const ELL2_J = BigInt(486662);
|
|
1684
|
+
let tv1 = Fp.sqr(u);
|
|
1685
|
+
tv1 = Fp.mul(tv1, _2n);
|
|
1686
|
+
let xd = Fp.add(tv1, Fp.ONE);
|
|
1687
|
+
let x1n = Fp.neg(ELL2_J);
|
|
1688
|
+
let tv2 = Fp.sqr(xd);
|
|
1689
|
+
let gxd = Fp.mul(tv2, xd);
|
|
1690
|
+
let gx1 = Fp.mul(tv1, ELL2_J);
|
|
1691
|
+
gx1 = Fp.mul(gx1, x1n);
|
|
1692
|
+
gx1 = Fp.add(gx1, tv2);
|
|
1693
|
+
gx1 = Fp.mul(gx1, x1n);
|
|
1694
|
+
let tv3 = Fp.sqr(gxd);
|
|
1695
|
+
tv2 = Fp.sqr(tv3);
|
|
1696
|
+
tv3 = Fp.mul(tv3, gxd);
|
|
1697
|
+
tv3 = Fp.mul(tv3, gx1);
|
|
1698
|
+
tv2 = Fp.mul(tv2, tv3);
|
|
1699
|
+
let y11 = Fp.pow(tv2, ELL2_C4);
|
|
1700
|
+
y11 = Fp.mul(y11, tv3);
|
|
1701
|
+
let y12 = Fp.mul(y11, ELL2_C3);
|
|
1702
|
+
tv2 = Fp.sqr(y11);
|
|
1703
|
+
tv2 = Fp.mul(tv2, gxd);
|
|
1704
|
+
let e1 = Fp.eql(tv2, gx1);
|
|
1705
|
+
let y1 = Fp.cmov(y12, y11, e1);
|
|
1706
|
+
let x2n = Fp.mul(x1n, tv1);
|
|
1707
|
+
let y21 = Fp.mul(y11, u);
|
|
1708
|
+
y21 = Fp.mul(y21, ELL2_C2);
|
|
1709
|
+
let y22 = Fp.mul(y21, ELL2_C3);
|
|
1710
|
+
let gx2 = Fp.mul(gx1, tv1);
|
|
1711
|
+
tv2 = Fp.sqr(y21);
|
|
1712
|
+
tv2 = Fp.mul(tv2, gxd);
|
|
1713
|
+
let e2 = Fp.eql(tv2, gx2);
|
|
1714
|
+
let y2 = Fp.cmov(y22, y21, e2);
|
|
1715
|
+
tv2 = Fp.sqr(y1);
|
|
1716
|
+
tv2 = Fp.mul(tv2, gxd);
|
|
1717
|
+
let e3 = Fp.eql(tv2, gx1);
|
|
1718
|
+
let xn = Fp.cmov(x2n, x1n, e3);
|
|
1719
|
+
let y = Fp.cmov(y2, y1, e3);
|
|
1720
|
+
let e4 = Fp.isOdd(y);
|
|
1721
|
+
y = Fp.cmov(y, Fp.neg(y), e3 !== e4);
|
|
1722
|
+
return {
|
|
1723
|
+
xMn: xn,
|
|
1724
|
+
xMd: xd,
|
|
1725
|
+
yMn: y,
|
|
1726
|
+
yMd: _1n
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
const ELL2_C1_EDWARDS = require_grant_rules.FpSqrtEven(Fp, Fp.neg(BigInt(486664)));
|
|
1730
|
+
function map_to_curve_elligator2_edwards25519(u) {
|
|
1731
|
+
const { xMn, xMd, yMn, yMd } = _map_to_curve_elligator2_curve25519(u);
|
|
1732
|
+
let xn = Fp.mul(xMn, yMd);
|
|
1733
|
+
xn = Fp.mul(xn, ELL2_C1_EDWARDS);
|
|
1734
|
+
let xd = Fp.mul(xMd, yMn);
|
|
1735
|
+
let yn = Fp.sub(xMn, xMd);
|
|
1736
|
+
let yd = Fp.add(xMn, xMd);
|
|
1737
|
+
let tv1 = Fp.mul(xd, yd);
|
|
1738
|
+
let e = Fp.eql(tv1, Fp.ZERO);
|
|
1739
|
+
xn = Fp.cmov(xn, Fp.ZERO, e);
|
|
1740
|
+
xd = Fp.cmov(xd, Fp.ONE, e);
|
|
1741
|
+
yn = Fp.cmov(yn, Fp.ONE, e);
|
|
1742
|
+
yd = Fp.cmov(yd, Fp.ONE, e);
|
|
1743
|
+
const [xd_inv, yd_inv] = require_grant_rules.FpInvertBatch(Fp, [xd, yd], true);
|
|
1744
|
+
return {
|
|
1745
|
+
x: Fp.mul(xn, xd_inv),
|
|
1746
|
+
y: Fp.mul(yn, yd_inv)
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
createHasher(ed25519_Point, (scalars) => map_to_curve_elligator2_edwards25519(scalars[0]), {
|
|
1750
|
+
DST: "edwards25519_XMD:SHA-512_ELL2_RO_",
|
|
1751
|
+
encodeDST: "edwards25519_XMD:SHA-512_ELL2_NU_",
|
|
1752
|
+
p: ed25519_CURVE_p,
|
|
1753
|
+
m: 1,
|
|
1754
|
+
k: 128,
|
|
1755
|
+
expand: "xmd",
|
|
1756
|
+
hash: require_grant_rules.sha512
|
|
1757
|
+
});
|
|
1758
|
+
const SQRT_M1 = ED25519_SQRT_M1;
|
|
1759
|
+
const SQRT_AD_MINUS_ONE = /* @__PURE__ */ BigInt("25063068953384623474111414158702152701244531502492656460079210482610430750235");
|
|
1760
|
+
const INVSQRT_A_MINUS_D = /* @__PURE__ */ BigInt("54469307008909316920995813868745141605393597292927456921205312896311721017578");
|
|
1761
|
+
const ONE_MINUS_D_SQ = /* @__PURE__ */ BigInt("1159843021668779879193775521855586647937357759715417654439879720876111806838");
|
|
1762
|
+
const D_MINUS_ONE_SQ = /* @__PURE__ */ BigInt("40440834346308536858101042469323190826248399146238708352240133220865137265952");
|
|
1763
|
+
const invertSqrt = (number) => uvRatio(_1n, number);
|
|
1764
|
+
const MAX_255B = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
1765
|
+
const bytes255ToNumberLE = (bytes) => Fp.create(require_grant_rules.bytesToNumberLE(bytes) & MAX_255B);
|
|
1766
|
+
/**
|
|
1767
|
+
* Computes Elligator map for Ristretto255.
|
|
1768
|
+
* Primary formula source is RFC 9496 §4.3.4 MAP; RFC 9380 Appendix B builds
|
|
1769
|
+
* `hash_to_ristretto255` on top of this helper.
|
|
1770
|
+
* Returns an internal Edwards representative, not a public `_RistrettoPoint`.
|
|
1771
|
+
*/
|
|
1772
|
+
function calcElligatorRistrettoMap(r0) {
|
|
1773
|
+
const { d } = ed25519_CURVE;
|
|
1774
|
+
const P = ed25519_CURVE_p;
|
|
1775
|
+
const mod = (n) => Fp.create(n);
|
|
1776
|
+
const r = mod(SQRT_M1 * r0 * r0);
|
|
1777
|
+
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ);
|
|
1778
|
+
let c = BigInt(-1);
|
|
1779
|
+
const D = mod((c - d * r) * mod(r + d));
|
|
1780
|
+
let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D);
|
|
1781
|
+
let s_ = mod(s * r0);
|
|
1782
|
+
if (!require_grant_rules.isNegativeLE(s_, P)) s_ = mod(-s_);
|
|
1783
|
+
if (!Ns_D_is_sq) s = s_;
|
|
1784
|
+
if (!Ns_D_is_sq) c = r;
|
|
1785
|
+
const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D);
|
|
1786
|
+
const s2 = s * s;
|
|
1787
|
+
const W0 = mod((s + s) * D);
|
|
1788
|
+
const W1 = mod(Nt * SQRT_AD_MINUS_ONE);
|
|
1789
|
+
const W2 = mod(_1n - s2);
|
|
1790
|
+
const W3 = mod(_1n + s2);
|
|
1791
|
+
return new ed25519_Point(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Wrapper over Edwards Point for ristretto255.
|
|
1795
|
+
*
|
|
1796
|
+
* Each ed25519/EdwardsPoint has 8 different equivalent points. This can be
|
|
1797
|
+
* a source of bugs for protocols like ring signatures. Ristretto was created to solve this.
|
|
1798
|
+
* Ristretto point operates in X:Y:Z:T extended coordinates like EdwardsPoint,
|
|
1799
|
+
* but it should work in its own namespace: do not combine those two.
|
|
1800
|
+
* See [RFC9496](https://www.rfc-editor.org/rfc/rfc9496).
|
|
1801
|
+
*/
|
|
1802
|
+
var _RistrettoPoint = class _RistrettoPoint extends require_grant_rules.PrimeEdwardsPoint {
|
|
1803
|
+
static BASE = new _RistrettoPoint(ed25519_Point.BASE);
|
|
1804
|
+
static ZERO = new _RistrettoPoint(ed25519_Point.ZERO);
|
|
1805
|
+
static Fp = Fp;
|
|
1806
|
+
static Fn = Fn;
|
|
1807
|
+
constructor(ep) {
|
|
1808
|
+
super(ep);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Create one Ristretto255 point from affine Edwards coordinates.
|
|
1812
|
+
* This wraps the internal Edwards representative directly and is not a
|
|
1813
|
+
* canonical ristretto255 decoding path.
|
|
1814
|
+
* Use `toBytes()` / `fromBytes()` if canonical ristretto255 bytes matter.
|
|
1815
|
+
*/
|
|
1816
|
+
static fromAffine(ap) {
|
|
1817
|
+
return new _RistrettoPoint(ed25519_Point.fromAffine(ap));
|
|
1818
|
+
}
|
|
1819
|
+
assertSame(other) {
|
|
1820
|
+
if (!(other instanceof _RistrettoPoint)) throw new Error("RistrettoPoint expected");
|
|
1821
|
+
}
|
|
1822
|
+
init(ep) {
|
|
1823
|
+
return new _RistrettoPoint(ep);
|
|
1824
|
+
}
|
|
1825
|
+
static fromBytes(bytes) {
|
|
1826
|
+
require_grant_rules.abytes$1(bytes, 32);
|
|
1827
|
+
const { a, d } = ed25519_CURVE;
|
|
1828
|
+
const P = ed25519_CURVE_p;
|
|
1829
|
+
const mod = (n) => Fp.create(n);
|
|
1830
|
+
const s = bytes255ToNumberLE(bytes);
|
|
1831
|
+
if (!require_grant_rules.equalBytes(Fp.toBytes(s), bytes) || require_grant_rules.isNegativeLE(s, P)) throw new Error("invalid ristretto255 encoding 1");
|
|
1832
|
+
const s2 = mod(s * s);
|
|
1833
|
+
const u1 = mod(_1n + a * s2);
|
|
1834
|
+
const u2 = mod(_1n - a * s2);
|
|
1835
|
+
const u1_2 = mod(u1 * u1);
|
|
1836
|
+
const u2_2 = mod(u2 * u2);
|
|
1837
|
+
const v = mod(a * d * u1_2 - u2_2);
|
|
1838
|
+
const { isValid, value: I } = invertSqrt(mod(v * u2_2));
|
|
1839
|
+
const Dx = mod(I * u2);
|
|
1840
|
+
const Dy = mod(I * Dx * v);
|
|
1841
|
+
let x = mod((s + s) * Dx);
|
|
1842
|
+
if (require_grant_rules.isNegativeLE(x, P)) x = mod(-x);
|
|
1843
|
+
const y = mod(u1 * Dy);
|
|
1844
|
+
const t = mod(x * y);
|
|
1845
|
+
if (!isValid || require_grant_rules.isNegativeLE(t, P) || y === _0n) throw new Error("invalid ristretto255 encoding 2");
|
|
1846
|
+
return new _RistrettoPoint(new ed25519_Point(x, y, _1n, t));
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Converts ristretto-encoded string to ristretto point.
|
|
1850
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-decode).
|
|
1851
|
+
* @param hex - Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
|
1852
|
+
*/
|
|
1853
|
+
static fromHex(hex) {
|
|
1854
|
+
return _RistrettoPoint.fromBytes(require_grant_rules.hexToBytes$1(hex));
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Encodes ristretto point to Uint8Array.
|
|
1858
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-encode).
|
|
1859
|
+
*/
|
|
1860
|
+
toBytes() {
|
|
1861
|
+
let { X, Y, Z, T } = this.ep;
|
|
1862
|
+
const P = ed25519_CURVE_p;
|
|
1863
|
+
const mod = (n) => Fp.create(n);
|
|
1864
|
+
const u1 = mod(mod(Z + Y) * mod(Z - Y));
|
|
1865
|
+
const u2 = mod(X * Y);
|
|
1866
|
+
const { value: invsqrt } = invertSqrt(mod(u1 * mod(u2 * u2)));
|
|
1867
|
+
const D1 = mod(invsqrt * u1);
|
|
1868
|
+
const D2 = mod(invsqrt * u2);
|
|
1869
|
+
const zInv = mod(D1 * D2 * T);
|
|
1870
|
+
let D;
|
|
1871
|
+
if (require_grant_rules.isNegativeLE(T * zInv, P)) {
|
|
1872
|
+
let _x = mod(Y * SQRT_M1);
|
|
1873
|
+
let _y = mod(X * SQRT_M1);
|
|
1874
|
+
X = _x;
|
|
1875
|
+
Y = _y;
|
|
1876
|
+
D = mod(D1 * INVSQRT_A_MINUS_D);
|
|
1877
|
+
} else D = D2;
|
|
1878
|
+
if (require_grant_rules.isNegativeLE(X * zInv, P)) Y = mod(-Y);
|
|
1879
|
+
let s = mod((Z - Y) * D);
|
|
1880
|
+
if (require_grant_rules.isNegativeLE(s, P)) s = mod(-s);
|
|
1881
|
+
return Fp.toBytes(s);
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Compares two Ristretto points.
|
|
1885
|
+
* Described in [RFC9496](https://www.rfc-editor.org/rfc/rfc9496#name-equals).
|
|
1886
|
+
*/
|
|
1887
|
+
equals(other) {
|
|
1888
|
+
this.assertSame(other);
|
|
1889
|
+
const { X: X1, Y: Y1 } = this.ep;
|
|
1890
|
+
const { X: X2, Y: Y2 } = other.ep;
|
|
1891
|
+
const mod = (n) => Fp.create(n);
|
|
1892
|
+
const one = mod(X1 * Y2) === mod(Y1 * X2);
|
|
1893
|
+
const two = mod(Y1 * Y2) === mod(X1 * X2);
|
|
1894
|
+
return one || two;
|
|
1895
|
+
}
|
|
1896
|
+
is0() {
|
|
1897
|
+
return this.equals(_RistrettoPoint.ZERO);
|
|
1898
|
+
}
|
|
1899
|
+
};
|
|
1900
|
+
Object.freeze(_RistrettoPoint.BASE);
|
|
1901
|
+
Object.freeze(_RistrettoPoint.ZERO);
|
|
1902
|
+
Object.freeze(_RistrettoPoint.prototype);
|
|
1903
|
+
Object.freeze(_RistrettoPoint);
|
|
1904
|
+
/**
|
|
1905
|
+
* Hashing to ristretto255 points / field. RFC 9380 methods.
|
|
1906
|
+
* `hashToCurve()` is RFC 9380 Appendix B, `deriveToCurve()` is the RFC 9496
|
|
1907
|
+
* §4.3.4 element-derivation building block, and `hashToScalar()` is a
|
|
1908
|
+
* library-specific helper for OPRF-style use.
|
|
1909
|
+
* @example
|
|
1910
|
+
* Hash one message onto ristretto255.
|
|
1911
|
+
*
|
|
1912
|
+
* ```ts
|
|
1913
|
+
* const point = ristretto255_hasher.hashToCurve(new TextEncoder().encode('hello noble'));
|
|
1914
|
+
* ```
|
|
1915
|
+
*/
|
|
1916
|
+
const ristretto255_hasher = Object.freeze({
|
|
1917
|
+
Point: _RistrettoPoint,
|
|
1918
|
+
hashToCurve(msg, options) {
|
|
1919
|
+
const xmd = expand_message_xmd(msg, options?.DST === void 0 ? "ristretto255_XMD:SHA-512_R255MAP_RO_" : options.DST, 64, require_grant_rules.sha512);
|
|
1920
|
+
return ristretto255_hasher.deriveToCurve(xmd);
|
|
1921
|
+
},
|
|
1922
|
+
hashToScalar(msg, options = { DST: _DST_scalar }) {
|
|
1923
|
+
const xmd = expand_message_xmd(msg, options.DST, 64, require_grant_rules.sha512);
|
|
1924
|
+
return Fn.create(require_grant_rules.bytesToNumberLE(xmd));
|
|
1925
|
+
},
|
|
1926
|
+
deriveToCurve(bytes) {
|
|
1927
|
+
require_grant_rules.abytes$1(bytes, 64);
|
|
1928
|
+
const R1 = calcElligatorRistrettoMap(bytes255ToNumberLE(bytes.subarray(0, 32)));
|
|
1929
|
+
const R2 = calcElligatorRistrettoMap(bytes255ToNumberLE(bytes.subarray(32, 64)));
|
|
1930
|
+
return new _RistrettoPoint(R1.add(R2));
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
createOPRF({
|
|
1934
|
+
name: "ristretto255-SHA512",
|
|
1935
|
+
Point: _RistrettoPoint,
|
|
1936
|
+
hash: require_grant_rules.sha512,
|
|
1937
|
+
hashToGroup: ristretto255_hasher.hashToCurve,
|
|
1938
|
+
hashToScalar: ristretto255_hasher.hashToScalar
|
|
1939
|
+
});
|
|
1940
|
+
createFROST({
|
|
1941
|
+
name: "FROST-RISTRETTO255-SHA512-v1",
|
|
1942
|
+
Point: _RistrettoPoint,
|
|
1943
|
+
validatePoint: (p) => {
|
|
1944
|
+
p.assertValidity();
|
|
1945
|
+
},
|
|
1946
|
+
hash: require_grant_rules.sha512
|
|
1947
|
+
});
|
|
1948
|
+
/**
|
|
1949
|
+
* The most inbox entries one open-inbox request carries, held by the
|
|
1950
|
+
* client that batches and the signer that answers alike: a busy inbox is
|
|
1951
|
+
* opened over several requests, so no single request holds the signer
|
|
1952
|
+
* decrypting without bound.
|
|
1953
|
+
*/
|
|
1954
|
+
const MAX_INBOX_ENTRIES_PER_REQUEST = 4096;
|
|
1955
|
+
const ENTRY_VERSION = 1;
|
|
1956
|
+
const ENTRY_SUITE = 1;
|
|
1957
|
+
const EPHEMERAL_KEY_OFFSET$1 = 2;
|
|
1958
|
+
const NONCE_OFFSET$1 = 34;
|
|
1959
|
+
const TAG_OFFSET$1 = 46;
|
|
1960
|
+
const CIPHERTEXT_OFFSET$1 = 62;
|
|
1961
|
+
const NONCE_LENGTH$2 = 12;
|
|
1962
|
+
const TAG_LENGTH$2 = 16;
|
|
1963
|
+
const KEY_LENGTH$2 = 32;
|
|
1964
|
+
const COIN_NONCE_LENGTH = 32;
|
|
1965
|
+
const COLOR_LENGTH = 32;
|
|
1966
|
+
const VALUE_LENGTH = 16;
|
|
1967
|
+
const PLAINTEXT_LENGTH = COIN_NONCE_LENGTH + COLOR_LENGTH + VALUE_LENGTH;
|
|
1968
|
+
const AEAD_TAG_BITS$2 = TAG_LENGTH$2 * 8;
|
|
1969
|
+
const VALUE_MAX = (1n << BigInt(VALUE_LENGTH * 8)) - 1n;
|
|
1970
|
+
const BYTE_BITS$1 = 8n;
|
|
1971
|
+
const BYTE_MASK$1 = 255n;
|
|
1972
|
+
const WRAPPING_KEY_INFO = new TextEncoder().encode("midnight:custody:inbox:v1");
|
|
1973
|
+
/**
|
|
1974
|
+
* The AES-256-GCM key that wraps one entry: HKDF-SHA256 over the X25519
|
|
1975
|
+
* shared secret with an empty salt and the fixed inbox label, imported
|
|
1976
|
+
* non-extractable so the key bytes never exist outside the shared secret,
|
|
1977
|
+
* which is zeroed once the key is derived.
|
|
1978
|
+
*/
|
|
1979
|
+
const wrappingKey = async (shared) => {
|
|
1980
|
+
try {
|
|
1981
|
+
const material = await crypto.subtle.importKey("raw", shared, "HKDF", false, ["deriveKey"]);
|
|
1982
|
+
return await crypto.subtle.deriveKey({
|
|
1983
|
+
name: "HKDF",
|
|
1984
|
+
hash: "SHA-256",
|
|
1985
|
+
salt: new Uint8Array(0),
|
|
1986
|
+
info: WRAPPING_KEY_INFO
|
|
1987
|
+
}, material, {
|
|
1988
|
+
name: "AES-GCM",
|
|
1989
|
+
length: KEY_LENGTH$2 * 8
|
|
1990
|
+
}, false, ["encrypt", "decrypt"]);
|
|
1991
|
+
} finally {
|
|
1992
|
+
shared.fill(0);
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
const encodeValue = (value) => {
|
|
1996
|
+
const bytes = new Uint8Array(VALUE_LENGTH);
|
|
1997
|
+
let remaining = value;
|
|
1998
|
+
for (let index = VALUE_LENGTH - 1; index >= 0; index -= 1) {
|
|
1999
|
+
bytes[index] = Number(remaining & BYTE_MASK$1);
|
|
2000
|
+
remaining >>= BYTE_BITS$1;
|
|
2001
|
+
}
|
|
2002
|
+
return bytes;
|
|
2003
|
+
};
|
|
2004
|
+
const decodeValue = (bytes) => bytes.reduce((value, byte) => value << BYTE_BITS$1 | BigInt(byte), 0n);
|
|
2005
|
+
const encodeCoin = ({ nonce, color, value }) => {
|
|
2006
|
+
if (nonce.length !== COIN_NONCE_LENGTH || color.length !== COLOR_LENGTH) throw new Error(`An inbox coin description needs a ${COIN_NONCE_LENGTH}-byte nonce and a ${COLOR_LENGTH}-byte colour.`);
|
|
2007
|
+
if (value < 0n || value > VALUE_MAX) throw new Error(`An inbox coin value must fit an unsigned 128-bit integer, got ${value}.`);
|
|
2008
|
+
const plaintext = new Uint8Array(PLAINTEXT_LENGTH);
|
|
2009
|
+
plaintext.set(nonce, 0);
|
|
2010
|
+
plaintext.set(color, COIN_NONCE_LENGTH);
|
|
2011
|
+
plaintext.set(encodeValue(value), COIN_NONCE_LENGTH + COLOR_LENGTH);
|
|
2012
|
+
return plaintext;
|
|
2013
|
+
};
|
|
2014
|
+
const decodeCoin = (plaintext) => ({
|
|
2015
|
+
nonce: plaintext.slice(0, COIN_NONCE_LENGTH),
|
|
2016
|
+
color: plaintext.slice(COIN_NONCE_LENGTH, COIN_NONCE_LENGTH + COLOR_LENGTH),
|
|
2017
|
+
value: decodeValue(plaintext.slice(COIN_NONCE_LENGTH + COLOR_LENGTH))
|
|
2018
|
+
});
|
|
2019
|
+
/**
|
|
2020
|
+
* Seals a coin description towards an account as an InboxEntry v1: the
|
|
2021
|
+
* plaintext is the coin nonce, colour and big-endian 128-bit value, wrapped
|
|
2022
|
+
* under a key agreed between a fresh ephemeral X25519 key and the
|
|
2023
|
+
* account's `enc_key`, with the version and suite bytes as associated
|
|
2024
|
+
* data; the trailing padding stays zero. The ephemeral secret is zeroed
|
|
2025
|
+
* before this returns.
|
|
2026
|
+
*/
|
|
2027
|
+
const sealInboxEntry = async (encPublicKey, coin) => {
|
|
2028
|
+
const plaintext = encodeCoin(coin);
|
|
2029
|
+
const ephemeral = x25519.keygen();
|
|
2030
|
+
const entry = new Uint8Array(192);
|
|
2031
|
+
entry[0] = ENTRY_VERSION;
|
|
2032
|
+
entry[1] = ENTRY_SUITE;
|
|
2033
|
+
try {
|
|
2034
|
+
const key = await wrappingKey(x25519.getSharedSecret(ephemeral.secretKey, encPublicKey));
|
|
2035
|
+
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH$2));
|
|
2036
|
+
const sealed = new Uint8Array(await crypto.subtle.encrypt({
|
|
2037
|
+
name: "AES-GCM",
|
|
2038
|
+
iv: nonce,
|
|
2039
|
+
additionalData: entry.slice(0, EPHEMERAL_KEY_OFFSET$1),
|
|
2040
|
+
tagLength: AEAD_TAG_BITS$2
|
|
2041
|
+
}, key, plaintext));
|
|
2042
|
+
entry.set(ephemeral.publicKey, EPHEMERAL_KEY_OFFSET$1);
|
|
2043
|
+
entry.set(nonce, NONCE_OFFSET$1);
|
|
2044
|
+
entry.set(sealed.subarray(PLAINTEXT_LENGTH), TAG_OFFSET$1);
|
|
2045
|
+
entry.set(sealed.subarray(0, PLAINTEXT_LENGTH), CIPHERTEXT_OFFSET$1);
|
|
2046
|
+
return entry;
|
|
2047
|
+
} finally {
|
|
2048
|
+
ephemeral.secretKey.fill(0);
|
|
2049
|
+
plaintext.fill(0);
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
/**
|
|
2053
|
+
* Opens an InboxEntry v1 with the account encryption secret. Resolves the
|
|
2054
|
+
* coin the entry describes, or null for an entry the inbox walk must skip
|
|
2055
|
+
* rather than fail on: one of another length, version or suite, one
|
|
2056
|
+
* sealed towards another key, or one whose authentication fails. The
|
|
2057
|
+
* contract accepts any bytes from anyone, so a null is noise, not an
|
|
2058
|
+
* error.
|
|
2059
|
+
*/
|
|
2060
|
+
const openInboxEntry = async (encSecret, entry) => {
|
|
2061
|
+
if (entry.length !== 192 || entry[0] !== ENTRY_VERSION || entry[1] !== ENTRY_SUITE) return null;
|
|
2062
|
+
try {
|
|
2063
|
+
const key = await wrappingKey(x25519.getSharedSecret(encSecret, entry.slice(EPHEMERAL_KEY_OFFSET$1, NONCE_OFFSET$1)));
|
|
2064
|
+
const sealed = new Uint8Array(PLAINTEXT_LENGTH + TAG_LENGTH$2);
|
|
2065
|
+
sealed.set(entry.slice(CIPHERTEXT_OFFSET$1, CIPHERTEXT_OFFSET$1 + PLAINTEXT_LENGTH), 0);
|
|
2066
|
+
sealed.set(entry.slice(TAG_OFFSET$1, CIPHERTEXT_OFFSET$1), PLAINTEXT_LENGTH);
|
|
2067
|
+
const plaintext = new Uint8Array(await crypto.subtle.decrypt({
|
|
2068
|
+
name: "AES-GCM",
|
|
2069
|
+
iv: entry.slice(NONCE_OFFSET$1, TAG_OFFSET$1),
|
|
2070
|
+
additionalData: entry.slice(0, EPHEMERAL_KEY_OFFSET$1),
|
|
2071
|
+
tagLength: AEAD_TAG_BITS$2
|
|
2072
|
+
}, key, sealed));
|
|
2073
|
+
try {
|
|
2074
|
+
return decodeCoin(plaintext);
|
|
2075
|
+
} finally {
|
|
2076
|
+
plaintext.fill(0);
|
|
2077
|
+
}
|
|
2078
|
+
} catch {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
const SEAL_VERSION = 1;
|
|
2083
|
+
const SEAL_SUITE = 1;
|
|
2084
|
+
const EPHEMERAL_KEY_OFFSET = 2;
|
|
2085
|
+
const NONCE_OFFSET = 34;
|
|
2086
|
+
const TAG_OFFSET = 46;
|
|
2087
|
+
const CIPHERTEXT_OFFSET = 62;
|
|
2088
|
+
const NONCE_LENGTH$1 = 12;
|
|
2089
|
+
const TAG_LENGTH$1 = 16;
|
|
2090
|
+
const KEY_LENGTH$1 = 32;
|
|
2091
|
+
const SECRET_LENGTH = 32;
|
|
2092
|
+
const AEAD_TAG_BITS$1 = TAG_LENGTH$1 * 8;
|
|
2093
|
+
const VIEW_KEY_INFO = new TextEncoder().encode("midnight:account:grant:view:v1");
|
|
2094
|
+
const concat$1 = (...parts) => {
|
|
2095
|
+
const joined = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
2096
|
+
let offset = 0;
|
|
2097
|
+
for (const part of parts) {
|
|
2098
|
+
joined.set(part, offset);
|
|
2099
|
+
offset += part.length;
|
|
2100
|
+
}
|
|
2101
|
+
return joined;
|
|
2102
|
+
};
|
|
2103
|
+
/**
|
|
2104
|
+
* The AES-256-GCM key that wraps one seal: HKDF-SHA256 over the X25519
|
|
2105
|
+
* shared secret with an empty salt and the view label followed by the
|
|
2106
|
+
* grant id, imported non-extractable; the shared secret is zeroed once
|
|
2107
|
+
* the key is derived.
|
|
2108
|
+
*/
|
|
2109
|
+
const sealingKey = async (shared, grantId) => {
|
|
2110
|
+
try {
|
|
2111
|
+
const material = await crypto.subtle.importKey("raw", shared, "HKDF", false, ["deriveKey"]);
|
|
2112
|
+
return await crypto.subtle.deriveKey({
|
|
2113
|
+
name: "HKDF",
|
|
2114
|
+
hash: "SHA-256",
|
|
2115
|
+
salt: new Uint8Array(0),
|
|
2116
|
+
info: concat$1(VIEW_KEY_INFO, grantId)
|
|
2117
|
+
}, material, {
|
|
2118
|
+
name: "AES-GCM",
|
|
2119
|
+
length: KEY_LENGTH$1 * 8
|
|
2120
|
+
}, false, ["encrypt"]);
|
|
2121
|
+
} finally {
|
|
2122
|
+
shared.fill(0);
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
/**
|
|
2126
|
+
* Seals an account encryption secret towards a grantee's read key as a
|
|
2127
|
+
* GrantViewSeal v1: the secret is wrapped under a key agreed between a
|
|
2128
|
+
* fresh ephemeral X25519 key and the read key and derived with the grant
|
|
2129
|
+
* id in its label, with the version, suite, ephemeral key, grant id and
|
|
2130
|
+
* account as associated data, so the container opens only for that read
|
|
2131
|
+
* key, under that grant and for that account. A read key of small order
|
|
2132
|
+
* yields an all-zero shared secret, which the key agreement refuses. The
|
|
2133
|
+
* ephemeral secret is zeroed before this returns; the account secret is
|
|
2134
|
+
* the caller's to zero.
|
|
2135
|
+
*/
|
|
2136
|
+
const sealGrantView = async (encSecret, { grantId, account, readPublicKey }) => {
|
|
2137
|
+
if (encSecret.length !== SECRET_LENGTH) throw new Error(`A grant view seals a ${SECRET_LENGTH}-byte secret, got ${encSecret.length} bytes.`);
|
|
2138
|
+
const grantIdBytes = require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(grantId));
|
|
2139
|
+
const accountBytes = require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(account));
|
|
2140
|
+
const readKey = require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(readPublicKey));
|
|
2141
|
+
const ephemeral = x25519.keygen();
|
|
2142
|
+
const seal = new Uint8Array(94);
|
|
2143
|
+
seal[0] = SEAL_VERSION;
|
|
2144
|
+
seal[1] = SEAL_SUITE;
|
|
2145
|
+
seal.set(ephemeral.publicKey, EPHEMERAL_KEY_OFFSET);
|
|
2146
|
+
try {
|
|
2147
|
+
const key = await sealingKey(x25519.getSharedSecret(ephemeral.secretKey, readKey), grantIdBytes);
|
|
2148
|
+
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH$1));
|
|
2149
|
+
const sealed = new Uint8Array(await crypto.subtle.encrypt({
|
|
2150
|
+
name: "AES-GCM",
|
|
2151
|
+
iv: nonce,
|
|
2152
|
+
additionalData: concat$1(seal.slice(0, NONCE_OFFSET), grantIdBytes, accountBytes),
|
|
2153
|
+
tagLength: AEAD_TAG_BITS$1
|
|
2154
|
+
}, key, encSecret));
|
|
2155
|
+
seal.set(nonce, NONCE_OFFSET);
|
|
2156
|
+
seal.set(sealed.subarray(SECRET_LENGTH), TAG_OFFSET);
|
|
2157
|
+
seal.set(sealed.subarray(0, SECRET_LENGTH), CIPHERTEXT_OFFSET);
|
|
2158
|
+
return seal;
|
|
2159
|
+
} finally {
|
|
2160
|
+
ephemeral.secretKey.fill(0);
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
//#endregion
|
|
2164
|
+
//#region ../../packages/module/passport-account/src/acc/envelopes.ts
|
|
2165
|
+
/**
|
|
2166
|
+
* The Lace record kinds an Account Custody Contract inbox carries beside
|
|
2167
|
+
* the coin entries senders write: device envelopes, which deliver the
|
|
2168
|
+
* account secret and the roster key to one device's reader key; device
|
|
2169
|
+
* registrations, which name an enrolled device's reader and device keys
|
|
2170
|
+
* under the roster key; and grant records, which keep the bookkeeping of
|
|
2171
|
+
* a grant this device issued under a key only it derives. Each is one
|
|
2172
|
+
* 192-byte entry led by a type byte no custody standard reader knows, so
|
|
2173
|
+
* every other reader skips it as noise, and each opens to null rather
|
|
2174
|
+
* than an error when it is malformed, tampered with or addressed to
|
|
2175
|
+
* someone else, since anyone may append any bytes to the inbox.
|
|
2176
|
+
*/
|
|
2177
|
+
/** Type byte of a device envelope. */
|
|
2178
|
+
const DEVICE_ENVELOPE_TYPE = 209;
|
|
2179
|
+
/** Type byte of a device registration. */
|
|
2180
|
+
const DEVICE_REGISTRATION_TYPE = 210;
|
|
2181
|
+
/** Type byte of a grant record. */
|
|
2182
|
+
const GRANT_RECORD_TYPE = 211;
|
|
2183
|
+
const SUITE = 2;
|
|
2184
|
+
const KEY_LENGTH = 32;
|
|
2185
|
+
const NONCE_LENGTH = 12;
|
|
2186
|
+
const TAG_LENGTH = 16;
|
|
2187
|
+
const RECIPIENT_TAG_LENGTH = 32;
|
|
2188
|
+
const AEAD_TAG_BITS = TAG_LENGTH * 8;
|
|
2189
|
+
const MAX_COIN_VALUE_LENGTH = 16;
|
|
2190
|
+
const BYTE_BITS = 8n;
|
|
2191
|
+
const BYTE_MASK = 255n;
|
|
2192
|
+
const RECIPIENT_TAG_OFFSET = 2;
|
|
2193
|
+
const RECIPIENT_TAG_END = RECIPIENT_TAG_OFFSET + RECIPIENT_TAG_LENGTH;
|
|
2194
|
+
const ENVELOPE_EPHEMERAL_OFFSET = RECIPIENT_TAG_END;
|
|
2195
|
+
const ENVELOPE_NONCE_OFFSET = ENVELOPE_EPHEMERAL_OFFSET + KEY_LENGTH;
|
|
2196
|
+
const ENVELOPE_PLAINTEXT_LENGTH = KEY_LENGTH * 2;
|
|
2197
|
+
const REGISTRATION_NONCE_OFFSET = 2;
|
|
2198
|
+
const REGISTRATION_PLAINTEXT_LENGTH = KEY_LENGTH * 2;
|
|
2199
|
+
const RECORD_NONCE_OFFSET = RECIPIENT_TAG_END;
|
|
2200
|
+
const RECORD_PLAINTEXT_LENGTH = 192 - (RECORD_NONCE_OFFSET + NONCE_LENGTH + TAG_LENGTH);
|
|
2201
|
+
const encoder = new TextEncoder();
|
|
2202
|
+
const ENVELOPE_TAG_DOMAIN = encoder.encode("lace:passport:envelope-tag:v2");
|
|
2203
|
+
const ENVELOPE_KEY_INFO = encoder.encode("lace:passport:device-envelope:v1");
|
|
2204
|
+
const REGISTRATION_KEY_INFO = encoder.encode("lace:passport:device-registration:v1");
|
|
2205
|
+
const RECORD_KEY_INFO = encoder.encode("lace:passport:grant-record:v1");
|
|
2206
|
+
const RECORD_TAG_KEY_INFO = encoder.encode("lace:passport:grant-record-tag:v1");
|
|
2207
|
+
/** The parts of a grant record, by the byte that leads its plaintext. */
|
|
2208
|
+
const RECORD_PARTS = [
|
|
2209
|
+
"grant",
|
|
2210
|
+
"object",
|
|
2211
|
+
"view"
|
|
2212
|
+
];
|
|
2213
|
+
/**
|
|
2214
|
+
* A Jubjub device public key in the compressed 32-byte form a device
|
|
2215
|
+
* registration carries, as lowercase hex: the y coordinate little-endian
|
|
2216
|
+
* with the parity of x in its top bit.
|
|
2217
|
+
*/
|
|
2218
|
+
const encodeDevicePublicKey = (point) => require_bytes_vo.HexBytes.fromByteArray(require_grant_rules.jubjub.Point.fromAffine(point).toBytes());
|
|
2219
|
+
/**
|
|
2220
|
+
* The affine Jubjub point a compressed device public key names, or
|
|
2221
|
+
* undefined for hex that is no point on the curve.
|
|
2222
|
+
*/
|
|
2223
|
+
const decodeDevicePublicKey = (hex) => {
|
|
2224
|
+
try {
|
|
2225
|
+
return require_grant_rules.jubjub.Point.fromBytes(require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(hex))).toAffine();
|
|
2226
|
+
} catch {
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
const concat = (...parts) => {
|
|
2231
|
+
const joined = new Uint8Array(parts.reduce((length, part) => length + part.length, 0));
|
|
2232
|
+
let offset = 0;
|
|
2233
|
+
for (const part of parts) {
|
|
2234
|
+
joined.set(part, offset);
|
|
2235
|
+
offset += part.length;
|
|
2236
|
+
}
|
|
2237
|
+
return joined;
|
|
2238
|
+
};
|
|
2239
|
+
const isZero = (bytes) => bytes.every((byte) => byte === 0);
|
|
2240
|
+
const equalBytes = (left, right) => left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
2241
|
+
/** Whether an entry is a 192-byte record of the given type and suite. */
|
|
2242
|
+
const isRecordOf = (entry, type) => entry.length === 192 && entry[0] === type && entry[1] === SUITE;
|
|
2243
|
+
/**
|
|
2244
|
+
* Whether an inbox entry is one of the Lace record kinds, which a coin
|
|
2245
|
+
* walk passes over rather than counts as noise.
|
|
2246
|
+
*/
|
|
2247
|
+
const isLaceRecord = (entry) => isRecordOf(entry, DEVICE_ENVELOPE_TYPE) || isRecordOf(entry, DEVICE_REGISTRATION_TYPE) || isRecordOf(entry, GRANT_RECORD_TYPE);
|
|
2248
|
+
/** Whether an inbox entry is a device registration. */
|
|
2249
|
+
const isDeviceRegistration = (entry) => isRecordOf(entry, DEVICE_REGISTRATION_TYPE);
|
|
2250
|
+
/**
|
|
2251
|
+
* The HMAC-SHA256 key HKDF-SHA256 derives from `material` under `info`
|
|
2252
|
+
* with an empty salt, imported non-extractable, so the key bytes never
|
|
2253
|
+
* exist outside it.
|
|
2254
|
+
*/
|
|
2255
|
+
const hmacKey = async (material, info) => {
|
|
2256
|
+
const key = await crypto.subtle.importKey("raw", material, "HKDF", false, ["deriveKey"]);
|
|
2257
|
+
return crypto.subtle.deriveKey({
|
|
2258
|
+
name: "HKDF",
|
|
2259
|
+
hash: "SHA-256",
|
|
2260
|
+
salt: new Uint8Array(0),
|
|
2261
|
+
info
|
|
2262
|
+
}, key, {
|
|
2263
|
+
name: "HMAC",
|
|
2264
|
+
hash: "SHA-256",
|
|
2265
|
+
length: KEY_LENGTH * 8
|
|
2266
|
+
}, false, ["sign"]);
|
|
2267
|
+
};
|
|
2268
|
+
/**
|
|
2269
|
+
* The AES-256-GCM key HKDF-SHA256 derives from `material` under `info`,
|
|
2270
|
+
* imported non-extractable, so the key bytes never exist outside it.
|
|
2271
|
+
*/
|
|
2272
|
+
const aeadKey = async (material, info) => {
|
|
2273
|
+
const key = await crypto.subtle.importKey("raw", material, "HKDF", false, ["deriveKey"]);
|
|
2274
|
+
return crypto.subtle.deriveKey({
|
|
2275
|
+
name: "HKDF",
|
|
2276
|
+
hash: "SHA-256",
|
|
2277
|
+
salt: new Uint8Array(0),
|
|
2278
|
+
info
|
|
2279
|
+
}, key, {
|
|
2280
|
+
name: "AES-GCM",
|
|
2281
|
+
length: KEY_LENGTH * 8
|
|
2282
|
+
}, false, ["encrypt", "decrypt"]);
|
|
2283
|
+
};
|
|
2284
|
+
/** A fresh AEAD nonce. */
|
|
2285
|
+
const freshNonce = () => crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
|
2286
|
+
/**
|
|
2287
|
+
* The associated data of an entry: every byte before its AEAD tag, less
|
|
2288
|
+
* the recipient tag when it carries one, since that tag is computed over
|
|
2289
|
+
* the sealed entry and binds it in turn.
|
|
2290
|
+
*/
|
|
2291
|
+
const associatedData = ({ entry, nonceOffset, isTagged }) => {
|
|
2292
|
+
const tagOffset = nonceOffset + NONCE_LENGTH;
|
|
2293
|
+
return isTagged ? concat(entry.subarray(0, RECIPIENT_TAG_OFFSET), entry.subarray(RECIPIENT_TAG_END, tagOffset)) : entry.slice(0, tagOffset);
|
|
2294
|
+
};
|
|
2295
|
+
/**
|
|
2296
|
+
* Encrypts `plaintext` into `entry` behind its header: the AEAD tag
|
|
2297
|
+
* after the nonce the entry already holds at `nonceOffset`, and the
|
|
2298
|
+
* ciphertext after that, with the header as associated data, see
|
|
2299
|
+
* {@link associatedData}, so the header must be complete before this
|
|
2300
|
+
* runs, its recipient tag aside.
|
|
2301
|
+
*/
|
|
2302
|
+
const sealInto = async ({ key, entry, nonceOffset, isTagged, plaintext }) => {
|
|
2303
|
+
const nonce = entry.slice(nonceOffset, nonceOffset + NONCE_LENGTH);
|
|
2304
|
+
const tagOffset = nonceOffset + NONCE_LENGTH;
|
|
2305
|
+
const sealed = new Uint8Array(await crypto.subtle.encrypt({
|
|
2306
|
+
name: "AES-GCM",
|
|
2307
|
+
iv: nonce,
|
|
2308
|
+
additionalData: associatedData({
|
|
2309
|
+
entry,
|
|
2310
|
+
nonceOffset,
|
|
2311
|
+
isTagged
|
|
2312
|
+
}),
|
|
2313
|
+
tagLength: AEAD_TAG_BITS
|
|
2314
|
+
}, key, plaintext));
|
|
2315
|
+
entry.set(sealed.subarray(plaintext.length), tagOffset);
|
|
2316
|
+
entry.set(sealed.subarray(0, plaintext.length), tagOffset + TAG_LENGTH);
|
|
2317
|
+
};
|
|
2318
|
+
/**
|
|
2319
|
+
* Decrypts the ciphertext behind an entry's header, or null when the
|
|
2320
|
+
* authentication fails or the padding after the ciphertext is not zero.
|
|
2321
|
+
*/
|
|
2322
|
+
const openFrom = async ({ key, entry, nonceOffset, isTagged, plaintextLength }) => {
|
|
2323
|
+
const tagOffset = nonceOffset + NONCE_LENGTH;
|
|
2324
|
+
const ciphertextOffset = tagOffset + TAG_LENGTH;
|
|
2325
|
+
if (!isZero(entry.subarray(ciphertextOffset + plaintextLength))) return null;
|
|
2326
|
+
try {
|
|
2327
|
+
return new Uint8Array(await crypto.subtle.decrypt({
|
|
2328
|
+
name: "AES-GCM",
|
|
2329
|
+
iv: entry.slice(nonceOffset, tagOffset),
|
|
2330
|
+
additionalData: associatedData({
|
|
2331
|
+
entry,
|
|
2332
|
+
nonceOffset,
|
|
2333
|
+
isTagged
|
|
2334
|
+
}),
|
|
2335
|
+
tagLength: AEAD_TAG_BITS
|
|
2336
|
+
}, key, concat(entry.subarray(ciphertextOffset, ciphertextOffset + plaintextLength), entry.subarray(tagOffset, ciphertextOffset))));
|
|
2337
|
+
} catch {
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
/**
|
|
2342
|
+
* The recipient tag of a device envelope: SHA-256 over the tag domain,
|
|
2343
|
+
* the type and suite bytes, the reader public key and every byte after
|
|
2344
|
+
* the tag, the ephemeral key, the nonce, the AEAD tag, the ciphertext
|
|
2345
|
+
* and the padding. A reader finds its envelopes by recomputing it, so
|
|
2346
|
+
* an entry that copies a genuine envelope's tag with any other byte
|
|
2347
|
+
* changed is not taken for one, and writing an entry that is takes the
|
|
2348
|
+
* reader public key. Two envelopes to the same reader share nothing on
|
|
2349
|
+
* chain, since each carries its own ephemeral key.
|
|
2350
|
+
*/
|
|
2351
|
+
const envelopeTag = (readerPublicKey, entry) => require_grant_rules.sha256(concat(ENVELOPE_TAG_DOMAIN, entry.subarray(0, RECIPIENT_TAG_OFFSET), readerPublicKey, entry.subarray(RECIPIENT_TAG_END)));
|
|
2352
|
+
/** Whether an entry is a device envelope addressed to this reader public key. */
|
|
2353
|
+
const isAddressedTo = (entry, readerPublicKey) => isRecordOf(entry, DEVICE_ENVELOPE_TYPE) && equalBytes(entry.subarray(RECIPIENT_TAG_OFFSET, RECIPIENT_TAG_END), envelopeTag(readerPublicKey, entry));
|
|
2354
|
+
/**
|
|
2355
|
+
* Every envelope in an inbox addressed to this device, newest first: the
|
|
2356
|
+
* ones a keyring carries. Anyone who knows a reader key can append
|
|
2357
|
+
* envelopes tagged for it, so none is left out, and the live one is
|
|
2358
|
+
* found however many land after it, at the cost of opening each.
|
|
2359
|
+
*/
|
|
2360
|
+
const deviceEnvelopes = (entries, readerPublicKey) => entries.filter((entry) => isAddressedTo(entry, readerPublicKey)).reverse();
|
|
2361
|
+
/**
|
|
2362
|
+
* The AES-256-GCM key of one envelope: HKDF-SHA256 over the X25519 shared
|
|
2363
|
+
* secret, zeroed once the key is derived.
|
|
2364
|
+
*/
|
|
2365
|
+
const envelopeKey = async (shared) => {
|
|
2366
|
+
try {
|
|
2367
|
+
return await aeadKey(shared, ENVELOPE_KEY_INFO);
|
|
2368
|
+
} finally {
|
|
2369
|
+
shared.fill(0);
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
/**
|
|
2373
|
+
* Seals the account secret and the roster key to one device's reader key
|
|
2374
|
+
* as a device envelope: [0] type, [1] suite, [2..34) recipient tag,
|
|
2375
|
+
* [34..66) ephemeral X25519 public key, [66..78) nonce, [78..94) AEAD
|
|
2376
|
+
* tag, [94..158) the secret then the roster key, encrypted, and zero
|
|
2377
|
+
* padding to 192 bytes. The key is agreed between a fresh ephemeral key
|
|
2378
|
+
* and the reader key, everything before the AEAD tag but the recipient
|
|
2379
|
+
* tag is associated data, and the recipient tag, computed last, covers
|
|
2380
|
+
* the whole sealed envelope. The envelope carries no round or
|
|
2381
|
+
* `enc_key`: a reader trusts the secret only once it derives the
|
|
2382
|
+
* `enc_key` the ledger holds. The ephemeral secret is zeroed before this
|
|
2383
|
+
* returns.
|
|
2384
|
+
*/
|
|
2385
|
+
const sealDeviceEnvelope = async (readerPublicKey, { secret, rosterKey }) => {
|
|
2386
|
+
const ephemeral = x25519.keygen();
|
|
2387
|
+
const plaintext = concat(secret, rosterKey);
|
|
2388
|
+
const entry = new Uint8Array(192);
|
|
2389
|
+
entry[0] = DEVICE_ENVELOPE_TYPE;
|
|
2390
|
+
entry[1] = SUITE;
|
|
2391
|
+
entry.set(ephemeral.publicKey, ENVELOPE_EPHEMERAL_OFFSET);
|
|
2392
|
+
entry.set(freshNonce(), ENVELOPE_NONCE_OFFSET);
|
|
2393
|
+
try {
|
|
2394
|
+
await sealInto({
|
|
2395
|
+
key: await envelopeKey(x25519.getSharedSecret(ephemeral.secretKey, readerPublicKey)),
|
|
2396
|
+
entry,
|
|
2397
|
+
nonceOffset: ENVELOPE_NONCE_OFFSET,
|
|
2398
|
+
isTagged: true,
|
|
2399
|
+
plaintext
|
|
2400
|
+
});
|
|
2401
|
+
entry.set(envelopeTag(readerPublicKey, entry), RECIPIENT_TAG_OFFSET);
|
|
2402
|
+
return entry;
|
|
2403
|
+
} finally {
|
|
2404
|
+
ephemeral.secretKey.fill(0);
|
|
2405
|
+
plaintext.fill(0);
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
/**
|
|
2409
|
+
* Opens a device envelope with this device's reader secret. Resolves the
|
|
2410
|
+
* account secret and roster key it delivers, which the caller zeroes once
|
|
2411
|
+
* used, or null for an entry that is not an envelope, is addressed to
|
|
2412
|
+
* another reader or fails to authenticate.
|
|
2413
|
+
*/
|
|
2414
|
+
const openDeviceEnvelope = async (readerSecret, entry) => {
|
|
2415
|
+
if (!isAddressedTo(entry, x25519.getPublicKey(readerSecret))) return null;
|
|
2416
|
+
let key;
|
|
2417
|
+
try {
|
|
2418
|
+
key = await envelopeKey(x25519.getSharedSecret(readerSecret, entry.slice(ENVELOPE_EPHEMERAL_OFFSET, ENVELOPE_NONCE_OFFSET)));
|
|
2419
|
+
} catch {
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
const plaintext = await openFrom({
|
|
2423
|
+
key,
|
|
2424
|
+
entry,
|
|
2425
|
+
nonceOffset: ENVELOPE_NONCE_OFFSET,
|
|
2426
|
+
isTagged: true,
|
|
2427
|
+
plaintextLength: ENVELOPE_PLAINTEXT_LENGTH
|
|
2428
|
+
});
|
|
2429
|
+
if (!plaintext) return null;
|
|
2430
|
+
try {
|
|
2431
|
+
return {
|
|
2432
|
+
secret: plaintext.slice(0, KEY_LENGTH),
|
|
2433
|
+
rosterKey: plaintext.slice(KEY_LENGTH)
|
|
2434
|
+
};
|
|
2435
|
+
} finally {
|
|
2436
|
+
plaintext.fill(0);
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
/**
|
|
2440
|
+
* Seals one enrolled device's reader and device keys under the roster key
|
|
2441
|
+
* as a device registration: [0] type, [1] suite, [2..14) nonce, [14..30)
|
|
2442
|
+
* AEAD tag, [30..94) the reader key then the device key, encrypted, and
|
|
2443
|
+
* zero padding to 192 bytes. Only a holder of the roster key, an enrolled
|
|
2444
|
+
* device, can write one that opens, and nothing on chain tells whose it
|
|
2445
|
+
* is.
|
|
2446
|
+
*/
|
|
2447
|
+
const sealDeviceRegistration = async (rosterKey, { readerPublicKey, devicePublicKey }) => {
|
|
2448
|
+
const entry = new Uint8Array(192);
|
|
2449
|
+
entry[0] = DEVICE_REGISTRATION_TYPE;
|
|
2450
|
+
entry[1] = SUITE;
|
|
2451
|
+
entry.set(freshNonce(), REGISTRATION_NONCE_OFFSET);
|
|
2452
|
+
await sealInto({
|
|
2453
|
+
key: await aeadKey(rosterKey, REGISTRATION_KEY_INFO),
|
|
2454
|
+
entry,
|
|
2455
|
+
nonceOffset: REGISTRATION_NONCE_OFFSET,
|
|
2456
|
+
isTagged: false,
|
|
2457
|
+
plaintext: concat(require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(readerPublicKey)), require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(devicePublicKey)))
|
|
2458
|
+
});
|
|
2459
|
+
return entry;
|
|
2460
|
+
};
|
|
2461
|
+
/**
|
|
2462
|
+
* Opens a device registration with the roster key: the device it names,
|
|
2463
|
+
* or null for an entry that is not a registration or was written under
|
|
2464
|
+
* another roster key.
|
|
2465
|
+
*/
|
|
2466
|
+
const openDeviceRegistration = async (rosterKey, entry) => {
|
|
2467
|
+
if (!isRecordOf(entry, DEVICE_REGISTRATION_TYPE)) return null;
|
|
2468
|
+
const plaintext = await openFrom({
|
|
2469
|
+
key: await aeadKey(rosterKey, REGISTRATION_KEY_INFO),
|
|
2470
|
+
entry,
|
|
2471
|
+
nonceOffset: REGISTRATION_NONCE_OFFSET,
|
|
2472
|
+
isTagged: false,
|
|
2473
|
+
plaintextLength: REGISTRATION_PLAINTEXT_LENGTH
|
|
2474
|
+
});
|
|
2475
|
+
return plaintext && {
|
|
2476
|
+
readerPublicKey: require_bytes_vo.HexBytes.fromByteArray(plaintext.slice(0, KEY_LENGTH)),
|
|
2477
|
+
devicePublicKey: require_bytes_vo.HexBytes.fromByteArray(plaintext.slice(KEY_LENGTH))
|
|
2478
|
+
};
|
|
2479
|
+
};
|
|
2480
|
+
/**
|
|
2481
|
+
* The keys of this device's grant records on one account, both derived
|
|
2482
|
+
* from its reader secret with the account address in the label: the
|
|
2483
|
+
* AES-256-GCM key and the key its recipient tags are computed under, so
|
|
2484
|
+
* only this device writes a record that opens or recognises one as its
|
|
2485
|
+
* own, and a record copied from another account is neither.
|
|
2486
|
+
*/
|
|
2487
|
+
const recordKeys = async (readerSecret, account) => {
|
|
2488
|
+
const accountBytes = require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(account));
|
|
2489
|
+
return {
|
|
2490
|
+
key: await aeadKey(readerSecret, concat(RECORD_KEY_INFO, accountBytes)),
|
|
2491
|
+
tagKey: await hmacKey(readerSecret, concat(RECORD_TAG_KEY_INFO, accountBytes))
|
|
2492
|
+
};
|
|
2493
|
+
};
|
|
2494
|
+
/**
|
|
2495
|
+
* The recipient tag of a grant record: HMAC-SHA256 under the tag key
|
|
2496
|
+
* over the type and suite bytes and every byte after the tag, the
|
|
2497
|
+
* nonce, the AEAD tag and the ciphertext, so a copied tag with any other
|
|
2498
|
+
* byte changed is not taken for this device's.
|
|
2499
|
+
*/
|
|
2500
|
+
const recordTag = async (tagKey, entry) => new Uint8Array(await crypto.subtle.sign("HMAC", tagKey, concat(entry.subarray(0, RECIPIENT_TAG_OFFSET), entry.subarray(RECIPIENT_TAG_END))));
|
|
2501
|
+
const encodeAmount = (value) => {
|
|
2502
|
+
const bytes = new Uint8Array(MAX_COIN_VALUE_LENGTH);
|
|
2503
|
+
let remaining = BigInt(value);
|
|
2504
|
+
for (let index = MAX_COIN_VALUE_LENGTH - 1; index >= 0; index -= 1) {
|
|
2505
|
+
bytes[index] = Number(remaining & BYTE_MASK);
|
|
2506
|
+
remaining >>= BYTE_BITS;
|
|
2507
|
+
}
|
|
2508
|
+
return bytes;
|
|
2509
|
+
};
|
|
2510
|
+
const decodeAmount = (bytes) => bytes.reduce((value, byte) => value << BYTE_BITS | BigInt(byte), 0n).toString();
|
|
2511
|
+
const hexBytes = (hex) => require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(hex));
|
|
2512
|
+
const encodeRecipient = (recipient) => concat(Uint8Array.of(Number(require_grant_rules.RECIPIENT_KINDS[recipient.kind])), recipient.kind === "any" ? new Uint8Array(KEY_LENGTH) : hexBytes(recipient.value));
|
|
2513
|
+
const RECIPIENT_BY_KIND = new Map(Object.entries(require_grant_rules.RECIPIENT_KINDS).map(([kind, value]) => [Number(value), kind]));
|
|
2514
|
+
const decodeRecipient = (bytes) => {
|
|
2515
|
+
const kind = RECIPIENT_BY_KIND.get(bytes[0]);
|
|
2516
|
+
const value = bytes.subarray(1);
|
|
2517
|
+
if (kind === void 0) return null;
|
|
2518
|
+
if (kind === "any") return isZero(value) ? { kind } : null;
|
|
2519
|
+
return {
|
|
2520
|
+
kind,
|
|
2521
|
+
value: require_bytes_vo.HexBytes.fromByteArray(value)
|
|
2522
|
+
};
|
|
2523
|
+
};
|
|
2524
|
+
/**
|
|
2525
|
+
* The plaintext of one grant record part, 130 bytes: the part byte and
|
|
2526
|
+
* the grant id, then for a grant the slot, the scope salt, the grantee
|
|
2527
|
+
* key and the read key (zero for none); for an object the colour, the
|
|
2528
|
+
* recipient kind and value, and the coin bound as a big-endian 128-bit
|
|
2529
|
+
* integer; for a view the leading bytes of `enc_key` and the 94-byte
|
|
2530
|
+
* seal; zero padding after.
|
|
2531
|
+
*/
|
|
2532
|
+
const encodeRecordPart = (part) => {
|
|
2533
|
+
const plaintext = new Uint8Array(RECORD_PLAINTEXT_LENGTH);
|
|
2534
|
+
plaintext[0] = RECORD_PARTS.indexOf(part.part);
|
|
2535
|
+
plaintext.set(hexBytes(part.grantId), 1);
|
|
2536
|
+
const body = 1 + KEY_LENGTH;
|
|
2537
|
+
switch (part.part) {
|
|
2538
|
+
case "grant":
|
|
2539
|
+
plaintext[body] = part.slot;
|
|
2540
|
+
plaintext.set(hexBytes(part.scopeSalt), body + 1);
|
|
2541
|
+
plaintext.set(hexBytes(part.granteePublicKey), body + 1 + KEY_LENGTH);
|
|
2542
|
+
if (part.readPublicKey !== void 0) plaintext.set(hexBytes(part.readPublicKey), body + 1 + KEY_LENGTH * 2);
|
|
2543
|
+
return plaintext;
|
|
2544
|
+
case "object":
|
|
2545
|
+
plaintext.set(hexBytes(part.object.color), body);
|
|
2546
|
+
plaintext.set(encodeRecipient(part.object.recipient), body + KEY_LENGTH);
|
|
2547
|
+
plaintext.set(encodeAmount(part.object.maxCoinValue), body + KEY_LENGTH * 2 + 1);
|
|
2548
|
+
return plaintext;
|
|
2549
|
+
case "view":
|
|
2550
|
+
plaintext.set(hexBytes(part.encKeyPrefix), body);
|
|
2551
|
+
plaintext.set(hexBytes(part.seal), body + 3);
|
|
2552
|
+
return plaintext;
|
|
2553
|
+
}
|
|
2554
|
+
};
|
|
2555
|
+
const decodeObject = (body) => {
|
|
2556
|
+
const recipient = decodeRecipient(body.subarray(KEY_LENGTH, KEY_LENGTH * 2 + 1));
|
|
2557
|
+
const amountOffset = KEY_LENGTH * 2 + 1;
|
|
2558
|
+
if (!recipient || !isZero(body.subarray(amountOffset + MAX_COIN_VALUE_LENGTH))) return null;
|
|
2559
|
+
return {
|
|
2560
|
+
color: require_bytes_vo.HexBytes.fromByteArray(body.subarray(0, KEY_LENGTH)),
|
|
2561
|
+
recipient,
|
|
2562
|
+
maxCoinValue: decodeAmount(body.subarray(amountOffset, amountOffset + MAX_COIN_VALUE_LENGTH))
|
|
2563
|
+
};
|
|
2564
|
+
};
|
|
2565
|
+
/** Reads a grant record part back, or null for a plaintext no writer produces. */
|
|
2566
|
+
const decodeRecordPart = (plaintext) => {
|
|
2567
|
+
const part = RECORD_PARTS[plaintext[0]];
|
|
2568
|
+
const grantId = require_grant_rules.GrantId(require_bytes_vo.HexBytes.fromByteArray(plaintext.slice(1, 33)));
|
|
2569
|
+
const body = plaintext.subarray(1 + KEY_LENGTH);
|
|
2570
|
+
switch (part) {
|
|
2571
|
+
case "grant": {
|
|
2572
|
+
const readKey = body.subarray(1 + KEY_LENGTH * 2);
|
|
2573
|
+
return {
|
|
2574
|
+
part,
|
|
2575
|
+
grantId,
|
|
2576
|
+
slot: require_grant_rules.GrantSlot(body[0]),
|
|
2577
|
+
scopeSalt: require_grant_rules.GrantScopeSalt(require_bytes_vo.HexBytes.fromByteArray(body.subarray(1, 1 + KEY_LENGTH))),
|
|
2578
|
+
granteePublicKey: require_bytes_vo.HexBytes.fromByteArray(body.subarray(1 + KEY_LENGTH, 1 + KEY_LENGTH * 2)),
|
|
2579
|
+
...isZero(readKey) ? {} : { readPublicKey: require_bytes_vo.HexBytes.fromByteArray(readKey) }
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
case "object": {
|
|
2583
|
+
const object = decodeObject(body);
|
|
2584
|
+
return object && {
|
|
2585
|
+
part,
|
|
2586
|
+
grantId,
|
|
2587
|
+
object
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
case "view": return {
|
|
2591
|
+
part,
|
|
2592
|
+
grantId,
|
|
2593
|
+
encKeyPrefix: require_bytes_vo.HexBytes.fromByteArray(body.subarray(0, 3)),
|
|
2594
|
+
seal: require_bytes_vo.HexBytes.fromByteArray(body.subarray(3, 97))
|
|
2595
|
+
};
|
|
2596
|
+
default: return null;
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
/**
|
|
2600
|
+
* Seals one part of a grant's bookkeeping for this device alone as a
|
|
2601
|
+
* grant record: [0] type, [1] suite, [2..34) recipient tag, [34..46)
|
|
2602
|
+
* nonce, [46..62) AEAD tag, [62..192) the 130-byte part, encrypted, with
|
|
2603
|
+
* everything before the AEAD tag but the recipient tag as associated
|
|
2604
|
+
* data. The key and the tag key are derived from this device's reader
|
|
2605
|
+
* secret and the account, so only this device writes a record that opens
|
|
2606
|
+
* and recognises its own on that account, and the tag, computed last
|
|
2607
|
+
* over the whole sealed record and its fresh nonce, links no two
|
|
2608
|
+
* records.
|
|
2609
|
+
*/
|
|
2610
|
+
const sealGrantRecord = async (readerSecret, account, part) => {
|
|
2611
|
+
const { key, tagKey } = await recordKeys(readerSecret, account);
|
|
2612
|
+
const plaintext = encodeRecordPart(part);
|
|
2613
|
+
const entry = new Uint8Array(192);
|
|
2614
|
+
entry[0] = GRANT_RECORD_TYPE;
|
|
2615
|
+
entry[1] = SUITE;
|
|
2616
|
+
entry.set(freshNonce(), RECORD_NONCE_OFFSET);
|
|
2617
|
+
try {
|
|
2618
|
+
await sealInto({
|
|
2619
|
+
key,
|
|
2620
|
+
entry,
|
|
2621
|
+
nonceOffset: RECORD_NONCE_OFFSET,
|
|
2622
|
+
isTagged: true,
|
|
2623
|
+
plaintext
|
|
2624
|
+
});
|
|
2625
|
+
entry.set(await recordTag(tagKey, entry), RECIPIENT_TAG_OFFSET);
|
|
2626
|
+
return entry;
|
|
2627
|
+
} finally {
|
|
2628
|
+
plaintext.fill(0);
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
/**
|
|
2632
|
+
* Opens a grant record of one account with this device's reader secret:
|
|
2633
|
+
* the part it holds, or null for an entry that is not a grant record,
|
|
2634
|
+
* carries another device's or another account's tag, fails to
|
|
2635
|
+
* authenticate or decodes to no part.
|
|
2636
|
+
*/
|
|
2637
|
+
const openGrantRecord = async (readerSecret, account, entry) => {
|
|
2638
|
+
if (!isRecordOf(entry, GRANT_RECORD_TYPE)) return null;
|
|
2639
|
+
const { key, tagKey } = await recordKeys(readerSecret, account);
|
|
2640
|
+
if (!equalBytes(await recordTag(tagKey, entry), entry.subarray(RECIPIENT_TAG_OFFSET, RECIPIENT_TAG_END))) return null;
|
|
2641
|
+
const plaintext = await openFrom({
|
|
2642
|
+
key,
|
|
2643
|
+
entry,
|
|
2644
|
+
nonceOffset: RECORD_NONCE_OFFSET,
|
|
2645
|
+
isTagged: true,
|
|
2646
|
+
plaintextLength: RECORD_PLAINTEXT_LENGTH
|
|
2647
|
+
});
|
|
2648
|
+
if (!plaintext) return null;
|
|
2649
|
+
try {
|
|
2650
|
+
return decodeRecordPart(plaintext);
|
|
2651
|
+
} catch {
|
|
2652
|
+
return null;
|
|
2653
|
+
} finally {
|
|
2654
|
+
plaintext.fill(0);
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
//#endregion
|
|
2658
|
+
Object.defineProperty(exports, "MAX_GRANTS", {
|
|
2659
|
+
enumerable: true,
|
|
2660
|
+
get: function() {
|
|
2661
|
+
return MAX_GRANTS;
|
|
2662
|
+
}
|
|
2663
|
+
});
|
|
2664
|
+
Object.defineProperty(exports, "MAX_INBOX_ENTRIES_PER_REQUEST", {
|
|
2665
|
+
enumerable: true,
|
|
2666
|
+
get: function() {
|
|
2667
|
+
return MAX_INBOX_ENTRIES_PER_REQUEST;
|
|
2668
|
+
}
|
|
2669
|
+
});
|
|
2670
|
+
Object.defineProperty(exports, "decodeDevicePublicKey", {
|
|
2671
|
+
enumerable: true,
|
|
2672
|
+
get: function() {
|
|
2673
|
+
return decodeDevicePublicKey;
|
|
2674
|
+
}
|
|
2675
|
+
});
|
|
2676
|
+
Object.defineProperty(exports, "deviceEnvelopes", {
|
|
2677
|
+
enumerable: true,
|
|
2678
|
+
get: function() {
|
|
2679
|
+
return deviceEnvelopes;
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
Object.defineProperty(exports, "encodeDevicePublicKey", {
|
|
2683
|
+
enumerable: true,
|
|
2684
|
+
get: function() {
|
|
2685
|
+
return encodeDevicePublicKey;
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
Object.defineProperty(exports, "isAddressedTo", {
|
|
2689
|
+
enumerable: true,
|
|
2690
|
+
get: function() {
|
|
2691
|
+
return isAddressedTo;
|
|
2692
|
+
}
|
|
2693
|
+
});
|
|
2694
|
+
Object.defineProperty(exports, "isDeviceRegistration", {
|
|
2695
|
+
enumerable: true,
|
|
2696
|
+
get: function() {
|
|
2697
|
+
return isDeviceRegistration;
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
Object.defineProperty(exports, "isLaceRecord", {
|
|
2701
|
+
enumerable: true,
|
|
2702
|
+
get: function() {
|
|
2703
|
+
return isLaceRecord;
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
Object.defineProperty(exports, "isSealableX25519Key", {
|
|
2707
|
+
enumerable: true,
|
|
2708
|
+
get: function() {
|
|
2709
|
+
return isSealableX25519Key;
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
2712
|
+
Object.defineProperty(exports, "openDeviceEnvelope", {
|
|
2713
|
+
enumerable: true,
|
|
2714
|
+
get: function() {
|
|
2715
|
+
return openDeviceEnvelope;
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
Object.defineProperty(exports, "openDeviceRegistration", {
|
|
2719
|
+
enumerable: true,
|
|
2720
|
+
get: function() {
|
|
2721
|
+
return openDeviceRegistration;
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
Object.defineProperty(exports, "openGrantRecord", {
|
|
2725
|
+
enumerable: true,
|
|
2726
|
+
get: function() {
|
|
2727
|
+
return openGrantRecord;
|
|
2728
|
+
}
|
|
2729
|
+
});
|
|
2730
|
+
Object.defineProperty(exports, "openInboxEntry", {
|
|
2731
|
+
enumerable: true,
|
|
2732
|
+
get: function() {
|
|
2733
|
+
return openInboxEntry;
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
Object.defineProperty(exports, "sealDeviceEnvelope", {
|
|
2737
|
+
enumerable: true,
|
|
2738
|
+
get: function() {
|
|
2739
|
+
return sealDeviceEnvelope;
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
Object.defineProperty(exports, "sealDeviceRegistration", {
|
|
2743
|
+
enumerable: true,
|
|
2744
|
+
get: function() {
|
|
2745
|
+
return sealDeviceRegistration;
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
Object.defineProperty(exports, "sealGrantRecord", {
|
|
2749
|
+
enumerable: true,
|
|
2750
|
+
get: function() {
|
|
2751
|
+
return sealGrantRecord;
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
Object.defineProperty(exports, "sealGrantView", {
|
|
2755
|
+
enumerable: true,
|
|
2756
|
+
get: function() {
|
|
2757
|
+
return sealGrantView;
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
Object.defineProperty(exports, "sealInboxEntry", {
|
|
2761
|
+
enumerable: true,
|
|
2762
|
+
get: function() {
|
|
2763
|
+
return sealInboxEntry;
|
|
2764
|
+
}
|
|
2765
|
+
});
|
|
2766
|
+
Object.defineProperty(exports, "validateGrantScope", {
|
|
2767
|
+
enumerable: true,
|
|
2768
|
+
get: function() {
|
|
2769
|
+
return validateGrantScope;
|
|
2770
|
+
}
|
|
2771
|
+
});
|
|
2772
|
+
Object.defineProperty(exports, "x25519", {
|
|
2773
|
+
enumerable: true,
|
|
2774
|
+
get: function() {
|
|
2775
|
+
return x25519;
|
|
2776
|
+
}
|
|
2777
|
+
});
|