mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
chore: improve signatures - bind w1/s1 with delegated keypairs
This commit is contained in:
parent
5e0622183e
commit
414747f396
106
package-lock.json
generated
106
package-lock.json
generated
@ -44,6 +44,7 @@
|
||||
"@reown/appkit-wallet-button": "^1.7.17",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||
"bitcoinjs-message": "^2.2.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@ -64,6 +65,7 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^0.9.3",
|
||||
"viem": "^2.37.1",
|
||||
"wagmi": "^2.16.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@ -8249,7 +8251,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
@ -8284,7 +8285,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
|
||||
"integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
@ -8322,7 +8322,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz",
|
||||
"integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"bech32": "^1.1.3",
|
||||
"bs58check": "^2.1.2",
|
||||
@ -8340,7 +8339,6 @@
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
|
||||
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
@ -8349,22 +8347,19 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bitcoinjs-message/node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bitcoinjs-message/node_modules/bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base-x": "^3.0.2"
|
||||
}
|
||||
@ -8374,7 +8369,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"bs58": "^4.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
@ -8387,7 +8381,6 @@
|
||||
"integrity": "sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"bip66": "^1.1.5",
|
||||
@ -8447,15 +8440,13 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buffer-xor": "^1.0.3",
|
||||
"cipher-base": "^1.0.0",
|
||||
@ -8561,7 +8552,6 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
|
||||
"integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -8570,8 +8560,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bufferutil": {
|
||||
"version": "4.0.9",
|
||||
@ -8808,7 +8797,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz",
|
||||
"integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"safe-buffer": "^5.2.1"
|
||||
@ -9356,7 +9344,6 @@
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
@ -9370,7 +9357,6 @@
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.3",
|
||||
"create-hash": "^1.1.0",
|
||||
@ -9827,7 +9813,6 @@
|
||||
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
|
||||
"integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"browserify-aes": "^1.0.6",
|
||||
"create-hash": "^1.1.2",
|
||||
@ -9942,7 +9927,6 @@
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
|
||||
"integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
@ -9957,8 +9941,7 @@
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/embla-carousel": {
|
||||
"version": "8.3.0",
|
||||
@ -10550,7 +10533,6 @@
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"md5.js": "^1.3.4",
|
||||
"safe-buffer": "^5.1.1"
|
||||
@ -10691,8 +10673,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
@ -11086,7 +11067,6 @@
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
@ -11101,7 +11081,6 @@
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
@ -11136,7 +11115,6 @@
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
@ -12178,7 +12156,6 @@
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
@ -12248,15 +12225,13 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
@ -12348,8 +12323,7 @@
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
|
||||
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
@ -13749,7 +13723,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1"
|
||||
@ -15045,9 +15018,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/viem": {
|
||||
"version": "2.33.2",
|
||||
"resolved": "https://registry.npmjs.org/viem/-/viem-2.33.2.tgz",
|
||||
"integrity": "sha512-/720OaM4dHWs8vXwNpyet+PRERhPaW+n/1UVSCzyb9jkmwwVfaiy/R6YfCFb4v+XXbo8s3Fapa3DM5yCRSkulA==",
|
||||
"version": "2.37.1",
|
||||
"resolved": "https://registry.npmjs.org/viem/-/viem-2.37.1.tgz",
|
||||
"integrity": "sha512-IzacdIXYlOvzDJwNKIVa53LP/LaP70qvBGAIoGH6R+n06S/ru/nnQxLNZ6+JImvIcxwNwgAl0jUA6FZEIQQWSw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -15056,14 +15029,14 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "1.9.2",
|
||||
"@noble/curves": "1.9.1",
|
||||
"@noble/hashes": "1.8.0",
|
||||
"@scure/bip32": "1.7.0",
|
||||
"@scure/bip39": "1.6.0",
|
||||
"abitype": "1.0.8",
|
||||
"isows": "1.0.7",
|
||||
"ox": "0.8.6",
|
||||
"ws": "8.18.2"
|
||||
"ox": "0.9.3",
|
||||
"ws": "8.18.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.4"
|
||||
@ -15074,6 +15047,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/@noble/curves": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
|
||||
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/@scure/bip32": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
|
||||
@ -15108,9 +15096,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/viem/node_modules/ox": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.8.6.tgz",
|
||||
"integrity": "sha512-eiKcgiVVEGDtEpEdFi1EGoVVI48j6icXHce9nFwCNM7CKG3uoCXKdr4TPhS00Iy1TR2aWSF1ltPD0x/YgqIL9w==",
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz",
|
||||
"integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -15121,11 +15109,11 @@
|
||||
"dependencies": {
|
||||
"@adraffy/ens-normalize": "^1.11.0",
|
||||
"@noble/ciphers": "^1.3.0",
|
||||
"@noble/curves": "^1.9.1",
|
||||
"@noble/curves": "1.9.1",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@scure/bip32": "^1.7.0",
|
||||
"@scure/bip39": "^1.6.0",
|
||||
"abitype": "^1.0.8",
|
||||
"abitype": "^1.0.9",
|
||||
"eventemitter3": "5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -15137,23 +15125,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"node_modules/viem/node_modules/ox/node_modules/abitype": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.9.tgz",
|
||||
"integrity": "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/wevm"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
"typescript": ">=5.0.4",
|
||||
"zod": "^3 >=3.22.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
"@reown/appkit-wallet-button": "^1.7.17",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||
"bitcoinjs-message": "^2.2.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@ -71,6 +72,7 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^0.9.3",
|
||||
"viem": "^2.37.1",
|
||||
"wagmi": "^2.16.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@ -27,7 +27,7 @@ import { useAppKitAccount, useDisconnect } from '@reown/appkit/react';
|
||||
import { WalletWizard } from '@/components/ui/wallet-wizard';
|
||||
|
||||
const Header = () => {
|
||||
const { currentUser, verificationStatus, isDelegationValid } = useAuth();
|
||||
const { currentUser, verificationStatus, getDelegationStatus } = useAuth();
|
||||
const { isNetworkConnected, isRefreshing } = useForum();
|
||||
const location = useLocation();
|
||||
const { toast } = useToast();
|
||||
@ -95,9 +95,9 @@ const Header = () => {
|
||||
case 'verified-none':
|
||||
return 'Read-Only Access';
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? 'Full Access' : 'Setup Key';
|
||||
return getDelegationStatus().isValid ? 'Full Access' : 'Setup Key';
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? 'Premium Access' : 'Setup Key';
|
||||
return getDelegationStatus().isValid ? 'Premium Access' : 'Setup Key';
|
||||
default:
|
||||
return 'Setup Account';
|
||||
}
|
||||
@ -112,13 +112,13 @@ const Header = () => {
|
||||
case 'verified-none':
|
||||
return <CircleSlash className="w-3 h-3" />;
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? (
|
||||
return getDelegationStatus().isValid ? (
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
) : (
|
||||
<Key className="w-3 h-3" />
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
);
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? (
|
||||
return getDelegationStatus().isValid ? (
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
) : (
|
||||
<Key className="w-3 h-3" />
|
||||
@ -137,9 +137,9 @@ const Header = () => {
|
||||
case 'verified-none':
|
||||
return 'secondary';
|
||||
case 'verified-basic':
|
||||
return isDelegationValid() ? 'default' : 'outline';
|
||||
return getDelegationStatus().isValid ? 'default' : 'outline';
|
||||
case 'verified-owner':
|
||||
return isDelegationValid() ? 'default' : 'outline';
|
||||
return getDelegationStatus().isValid ? 'default' : 'outline';
|
||||
default:
|
||||
return 'outline';
|
||||
}
|
||||
|
||||
@ -20,8 +20,7 @@ export function DelegationStep({
|
||||
const {
|
||||
currentUser,
|
||||
delegateKey,
|
||||
isDelegationValid,
|
||||
delegationTimeRemaining,
|
||||
getDelegationStatus,
|
||||
isAuthenticating,
|
||||
clearDelegation,
|
||||
} = useAuth();
|
||||
@ -162,23 +161,30 @@ export function DelegationStep({
|
||||
<div className="space-y-3">
|
||||
{/* Status */}
|
||||
<div className="flex items-center gap-2">
|
||||
{isDelegationValid() ? (
|
||||
{getDelegationStatus().isValid ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||
)}
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
isDelegationValid() ? 'text-green-400' : 'text-yellow-400'
|
||||
getDelegationStatus().isValid
|
||||
? 'text-green-400'
|
||||
: 'text-yellow-400'
|
||||
}`}
|
||||
>
|
||||
{isDelegationValid() ? 'Delegated' : 'Required'}
|
||||
{getDelegationStatus().isValid ? 'Delegated' : 'Required'}
|
||||
</span>
|
||||
{isDelegationValid() && (
|
||||
{getDelegationStatus().isValid && (
|
||||
<span className="text-xs text-neutral-400">
|
||||
{Math.floor(delegationTimeRemaining() / (1000 * 60 * 60))}h{' '}
|
||||
{Math.floor(
|
||||
(delegationTimeRemaining() % (1000 * 60 * 60)) / (1000 * 60)
|
||||
(getDelegationStatus().timeRemaining || 0) / (1000 * 60 * 60)
|
||||
)}
|
||||
h{' '}
|
||||
{Math.floor(
|
||||
((getDelegationStatus().timeRemaining || 0) %
|
||||
(1000 * 60 * 60)) /
|
||||
(1000 * 60)
|
||||
)}
|
||||
m remaining
|
||||
</span>
|
||||
@ -186,7 +192,7 @@ export function DelegationStep({
|
||||
</div>
|
||||
|
||||
{/* Duration Selection */}
|
||||
{!isDelegationValid() && (
|
||||
{!getDelegationStatus().isValid && (
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium text-neutral-300">
|
||||
Delegation Duration:
|
||||
@ -223,7 +229,7 @@ export function DelegationStep({
|
||||
)}
|
||||
|
||||
{/* Delegated Browser Public Key */}
|
||||
{isDelegationValid() && currentUser?.browserPubKey && (
|
||||
{getDelegationStatus().isValid && currentUser?.browserPubKey && (
|
||||
<div className="text-xs text-neutral-400">
|
||||
<div className="font-mono break-all bg-neutral-800 p-2 rounded">
|
||||
{currentUser.browserPubKey}
|
||||
@ -239,7 +245,7 @@ export function DelegationStep({
|
||||
)}
|
||||
|
||||
{/* Delete Button for Active Delegations */}
|
||||
{isDelegationValid() && (
|
||||
{getDelegationStatus().isValid && (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={clearDelegation}
|
||||
@ -257,7 +263,7 @@ export function DelegationStep({
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="mt-auto space-y-2">
|
||||
{isDelegationValid() ? (
|
||||
{getDelegationStatus().isValid ? (
|
||||
<Button
|
||||
onClick={handleComplete}
|
||||
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
||||
|
||||
@ -28,7 +28,8 @@ export function WalletWizard({
|
||||
}: WalletWizardProps) {
|
||||
const [currentStep, setCurrentStep] = React.useState<WizardStep>(1);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const { isAuthenticated, verificationStatus, isDelegationValid } = useAuth();
|
||||
const { isAuthenticated, verificationStatus, getDelegationStatus } =
|
||||
useAuth();
|
||||
const hasInitialized = React.useRef(false);
|
||||
|
||||
// Reset wizard when opened and determine starting step
|
||||
@ -48,7 +49,7 @@ export function WalletWizard({
|
||||
(verificationStatus === 'verified-owner' ||
|
||||
verificationStatus === 'verified-basic' ||
|
||||
verificationStatus === 'verified-none') &&
|
||||
!isDelegationValid()
|
||||
!getDelegationStatus().isValid
|
||||
) {
|
||||
setCurrentStep(3); // Start at delegation step if verified but no valid delegation
|
||||
} else {
|
||||
@ -59,7 +60,7 @@ export function WalletWizard({
|
||||
} else if (!open) {
|
||||
hasInitialized.current = false;
|
||||
}
|
||||
}, [open, isAuthenticated, verificationStatus, isDelegationValid]);
|
||||
}, [open, isAuthenticated, verificationStatus, getDelegationStatus]);
|
||||
|
||||
const handleStepComplete = (step: WizardStep) => {
|
||||
if (step < 3) {
|
||||
@ -100,7 +101,7 @@ export function WalletWizard({
|
||||
verificationStatus !== 'verified-none')
|
||||
)
|
||||
return 'disabled';
|
||||
if (isDelegationValid()) return 'complete';
|
||||
if (getDelegationStatus().isValid) return 'complete';
|
||||
return 'current';
|
||||
}
|
||||
return 'disabled';
|
||||
|
||||
@ -3,7 +3,11 @@ import { useToast } from '@/components/ui/use-toast';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { User, EVerificationStatus, DisplayPreference } from '@/types/identity';
|
||||
import { WalletManager } from '@/lib/wallet';
|
||||
import { DelegationManager, DelegationDuration, DelegationFullStatus } from '@/lib/delegation';
|
||||
import {
|
||||
DelegationManager,
|
||||
DelegationDuration,
|
||||
DelegationFullStatus,
|
||||
} from '@/lib/delegation';
|
||||
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
||||
|
||||
export type VerificationStatus =
|
||||
@ -25,7 +29,7 @@ interface AuthContextType {
|
||||
getDelegationStatus: () => DelegationFullStatus;
|
||||
clearDelegation: () => void;
|
||||
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
|
||||
verifyMessage: (message: OpchanMessage) => boolean;
|
||||
verifyMessage: (message: OpchanMessage) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
@ -170,7 +174,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
user.address,
|
||||
user.walletType,
|
||||
duration,
|
||||
(message) => walletManager.signMessage(message)
|
||||
message => walletManager.signMessage(message)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@ -447,7 +451,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
const getDelegationStatus = (): DelegationFullStatus => {
|
||||
return delegationManager.getStatus(currentUser?.address, currentUser?.walletType);
|
||||
return delegationManager.getStatus(
|
||||
currentUser?.address,
|
||||
currentUser?.walletType
|
||||
);
|
||||
};
|
||||
|
||||
const clearDelegation = (): void => {
|
||||
@ -477,8 +484,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
): Promise<OpchanMessage | null> => {
|
||||
return delegationManager.signMessage(message);
|
||||
},
|
||||
verifyMessage: (message: OpchanMessage): boolean => {
|
||||
return delegationManager.verify(message);
|
||||
verifyMessage: async (message: OpchanMessage): Promise<boolean> => {
|
||||
return await delegationManager.verify(message);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -105,10 +105,11 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
|
||||
// Transform message cache data to the expected types
|
||||
const updateStateFromCache = useCallback(() => {
|
||||
const updateStateFromCache = useCallback(async () => {
|
||||
// Use the verifyMessage function from delegationManager if available
|
||||
const verifyFn = isAuthenticated
|
||||
? (message: OpchanMessage) => delegationManager.verifyMessage(message)
|
||||
? async (message: OpchanMessage) =>
|
||||
await delegationManager.verify(message)
|
||||
: undefined;
|
||||
|
||||
// Build user verification status for relevance calculation
|
||||
@ -162,7 +163,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
relevanceCalculator.buildUserVerificationStatus(allUsers);
|
||||
|
||||
// Transform data with relevance calculation (initial pass)
|
||||
const { cells, posts, comments } = getDataFromCache(
|
||||
const { cells, posts, comments } = await getDataFromCache(
|
||||
verifyFn,
|
||||
initialStatus
|
||||
);
|
||||
@ -208,7 +209,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
});
|
||||
const enrichedStatus =
|
||||
relevanceCalculator.buildUserVerificationStatus(enrichedUsers);
|
||||
const transformed = getDataFromCache(verifyFn, enrichedStatus);
|
||||
const transformed = await getDataFromCache(verifyFn, enrichedStatus);
|
||||
setCells(transformed.cells);
|
||||
setPosts(transformed.posts);
|
||||
setComments(transformed.comments);
|
||||
@ -220,7 +221,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
// SDS handles message syncing automatically, just update UI
|
||||
updateStateFromCache();
|
||||
await updateStateFromCache();
|
||||
toast({
|
||||
title: 'Data Refreshed',
|
||||
description: 'Your view has been updated.',
|
||||
|
||||
@ -34,8 +34,9 @@ export const useDelegation = () => {
|
||||
hasDelegation: status.hasDelegation,
|
||||
isValid: status.isValid,
|
||||
timeRemaining: status.timeRemaining,
|
||||
expiresAt:
|
||||
status.timeRemaining ? new Date(Date.now() + status.timeRemaining) : undefined,
|
||||
expiresAt: status.timeRemaining
|
||||
? new Date(Date.now() + status.timeRemaining)
|
||||
: undefined,
|
||||
publicKey: status.publicKey,
|
||||
address: status.address,
|
||||
walletType: status.walletType,
|
||||
|
||||
@ -12,32 +12,32 @@ export const useMessageSigning = () => {
|
||||
const {
|
||||
signMessage: contextSignMessage,
|
||||
verifyMessage: contextVerifyMessage,
|
||||
isDelegationValid,
|
||||
getDelegationStatus,
|
||||
} = context;
|
||||
|
||||
const signMessage = useCallback(
|
||||
async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
||||
// Check if we have a valid delegation before attempting to sign
|
||||
if (!isDelegationValid()) {
|
||||
if (!getDelegationStatus().isValid) {
|
||||
console.warn('No valid delegation found. Cannot sign message.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return contextSignMessage(message);
|
||||
},
|
||||
[contextSignMessage, isDelegationValid]
|
||||
[contextSignMessage, getDelegationStatus]
|
||||
);
|
||||
|
||||
const verifyMessage = useCallback(
|
||||
(message: OpchanMessage): boolean => {
|
||||
return contextVerifyMessage(message);
|
||||
async (message: OpchanMessage): Promise<boolean> => {
|
||||
return await contextVerifyMessage(message);
|
||||
},
|
||||
[contextVerifyMessage]
|
||||
);
|
||||
|
||||
const canSignMessages = useCallback((): boolean => {
|
||||
return isDelegationValid();
|
||||
}, [isDelegationValid]);
|
||||
return getDelegationStatus().isValid;
|
||||
}, [getDelegationStatus]);
|
||||
|
||||
return {
|
||||
// Message signing
|
||||
|
||||
117
src/lib/delegation/crypto.ts
Normal file
117
src/lib/delegation/crypto.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import * as ed from '@noble/ed25519';
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { bytesToHex, hexToBytes } from '@/lib/utils';
|
||||
import { WalletManager } from '@/lib/wallet';
|
||||
|
||||
// Set up ed25519 with sha512
|
||||
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
||||
|
||||
/**
|
||||
* Delegation-specific cryptographic utilities
|
||||
* Handles all cryptographic operations: key generation, signing, verification
|
||||
*/
|
||||
export class DelegationCrypto {
|
||||
/**
|
||||
* Create a standardized delegation authorization message
|
||||
* @param browserPublicKey - The browser public key being authorized
|
||||
* @param walletAddress - The wallet address doing the authorization
|
||||
* @param expiryTimestamp - When the delegation expires
|
||||
* @param nonce - Unique nonce for replay protection
|
||||
* @returns string - The authorization message to be signed
|
||||
*/
|
||||
static createAuthMessage(
|
||||
browserPublicKey: string,
|
||||
walletAddress: string,
|
||||
expiryTimestamp: number,
|
||||
nonce: string
|
||||
): string {
|
||||
return `I, ${walletAddress}, authorize browser key ${browserPublicKey} until ${expiryTimestamp} (nonce: ${nonce})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a wallet signature using WalletManager
|
||||
* @param authMessage - The message that was signed
|
||||
* @param walletSignature - The signature to verify
|
||||
* @param walletAddress - The wallet address that signed
|
||||
* @param walletType - The type of wallet
|
||||
* @returns Promise<boolean> - True if signature is valid
|
||||
*/
|
||||
static async verifyWalletSignature(
|
||||
authMessage: string,
|
||||
walletSignature: string,
|
||||
walletAddress: string,
|
||||
walletType: 'bitcoin' | 'ethereum'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
return await WalletManager.verifySignature(
|
||||
authMessage,
|
||||
walletSignature,
|
||||
walletAddress,
|
||||
walletType
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error verifying wallet signature:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new browser-based keypair for signing messages
|
||||
* @returns Object with public and private keys in hex format
|
||||
*/
|
||||
static generateKeypair(): { publicKey: string; privateKey: string } {
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const privateKeyHex = bytesToHex(privateKey);
|
||||
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
const publicKeyHex = bytesToHex(publicKey);
|
||||
|
||||
return {
|
||||
privateKey: privateKeyHex,
|
||||
publicKey: publicKeyHex,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a raw string message using a private key
|
||||
* @param message - The message to sign
|
||||
* @param privateKeyHex - The private key in hex format
|
||||
* @returns The signature in hex format or null if signing fails
|
||||
*/
|
||||
static signRaw(message: string, privateKeyHex: string): string | null {
|
||||
try {
|
||||
const privateKeyBytes = hexToBytes(privateKeyHex);
|
||||
const messageBytes = new TextEncoder().encode(message);
|
||||
|
||||
const signature = ed.sign(messageBytes, privateKeyBytes);
|
||||
return bytesToHex(signature);
|
||||
} catch (error) {
|
||||
console.error('Error signing with private key:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature made with a public key
|
||||
* @param message - The original message
|
||||
* @param signature - The signature to verify in hex format
|
||||
* @param publicKey - The public key in hex format
|
||||
* @returns True if signature is valid
|
||||
*/
|
||||
static verifyRaw(
|
||||
message: string,
|
||||
signature: string,
|
||||
publicKey: string
|
||||
): boolean {
|
||||
try {
|
||||
const messageBytes = new TextEncoder().encode(message);
|
||||
const signatureBytes = hexToBytes(signature);
|
||||
const publicKeyBytes = hexToBytes(publicKey);
|
||||
|
||||
return ed.verify(signatureBytes, messageBytes, publicKeyBytes);
|
||||
} catch (error) {
|
||||
console.error('Error verifying signature:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,14 @@
|
||||
import * as ed from '@noble/ed25519';
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { bytesToHex, hexToBytes } from '@/lib/utils';
|
||||
import { OpchanMessage } from '@/types/forum';
|
||||
import { UnsignedMessage } from '@/types/waku';
|
||||
import { DelegationDuration, DelegationInfo, DelegationStatus } from './types';
|
||||
import {
|
||||
DelegationDuration,
|
||||
DelegationInfo,
|
||||
DelegationStatus,
|
||||
DelegationProof,
|
||||
} from './types';
|
||||
import { DelegationStorage } from './storage';
|
||||
import { DelegationCrypto } from './crypto';
|
||||
|
||||
// Set up ed25519 with sha512
|
||||
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
||||
|
||||
// Enhanced status interface that consolidates all delegation information
|
||||
export interface DelegationFullStatus extends DelegationStatus {
|
||||
publicKey?: string;
|
||||
address?: string;
|
||||
@ -17,15 +16,11 @@ export interface DelegationFullStatus extends DelegationStatus {
|
||||
}
|
||||
|
||||
export class DelegationManager {
|
||||
// Duration options in hours
|
||||
private static readonly DURATION_HOURS = {
|
||||
'7days': 24 * 7, // 168 hours
|
||||
'30days': 24 * 30, // 720 hours
|
||||
'7days': 24 * 7,
|
||||
'30days': 24 * 30,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Get the number of hours for a given duration
|
||||
*/
|
||||
static getDurationHours(duration: DelegationDuration): number {
|
||||
return DelegationManager.DURATION_HOURS[duration];
|
||||
}
|
||||
@ -35,12 +30,7 @@ export class DelegationManager {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a complete delegation with a single method call
|
||||
* @param address - Wallet address to delegate from
|
||||
* @param walletType - Type of wallet (bitcoin/ethereum)
|
||||
* @param duration - How long the delegation should last
|
||||
* @param signFunction - Function to sign the delegation message with the wallet
|
||||
* @returns Promise<boolean> - Success status
|
||||
* Create a delegation with cryptographic proof
|
||||
*/
|
||||
async delegate(
|
||||
address: string,
|
||||
@ -49,29 +39,34 @@ export class DelegationManager {
|
||||
signFunction: (message: string) => Promise<string>
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Generate new keypair
|
||||
const keypair = this.generateKeypair();
|
||||
|
||||
// Create delegation message with expiry
|
||||
const expiryHours = DelegationManager.getDurationHours(duration);
|
||||
const expiryTimestamp = Date.now() + expiryHours * 60 * 60 * 1000;
|
||||
const delegationMessage = this.createDelegationMessage(
|
||||
// Generate browser keypair
|
||||
const keypair = DelegationCrypto.generateKeypair();
|
||||
|
||||
// Create expiry and nonce
|
||||
const expiryTimestamp =
|
||||
Date.now() +
|
||||
DelegationManager.getDurationHours(duration) * 60 * 60 * 1000;
|
||||
const nonce = crypto.randomUUID();
|
||||
|
||||
// Create and sign authorization message
|
||||
const authMessage = DelegationCrypto.createAuthMessage(
|
||||
keypair.publicKey,
|
||||
address,
|
||||
expiryTimestamp
|
||||
);
|
||||
|
||||
// Sign the delegation message with wallet
|
||||
const signature = await signFunction(delegationMessage);
|
||||
|
||||
// Create and store the delegation
|
||||
const delegationInfo: DelegationInfo = {
|
||||
signature,
|
||||
expiryTimestamp,
|
||||
browserPublicKey: keypair.publicKey,
|
||||
browserPrivateKey: keypair.privateKey,
|
||||
nonce
|
||||
);
|
||||
const walletSignature = await signFunction(authMessage);
|
||||
|
||||
// Store delegation
|
||||
const delegationInfo: DelegationInfo = {
|
||||
authMessage,
|
||||
walletSignature,
|
||||
expiryTimestamp,
|
||||
walletAddress: address,
|
||||
walletType,
|
||||
browserPublicKey: keypair.publicKey,
|
||||
browserPrivateKey: keypair.privateKey,
|
||||
nonce,
|
||||
};
|
||||
|
||||
DelegationStorage.store(delegationInfo);
|
||||
@ -83,117 +78,115 @@ export class DelegationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive delegation status
|
||||
* @param currentAddress - Optional address to validate against
|
||||
* @param currentWalletType - Optional wallet type to validate against
|
||||
* @returns Complete delegation status information
|
||||
*/
|
||||
getStatus(
|
||||
currentAddress?: string,
|
||||
currentWalletType?: 'bitcoin' | 'ethereum'
|
||||
): DelegationFullStatus {
|
||||
const delegation = DelegationStorage.retrieve();
|
||||
|
||||
if (!delegation) {
|
||||
return {
|
||||
hasDelegation: false,
|
||||
isValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if delegation has expired
|
||||
const now = Date.now();
|
||||
const hasExpired = now >= delegation.expiryTimestamp;
|
||||
|
||||
// Check address/wallet type matching if provided
|
||||
const addressMatches = !currentAddress || delegation.walletAddress === currentAddress;
|
||||
const walletTypeMatches = !currentWalletType || delegation.walletType === currentWalletType;
|
||||
|
||||
const isValid = !hasExpired && addressMatches && walletTypeMatches;
|
||||
const timeRemaining = Math.max(0, delegation.expiryTimestamp - now);
|
||||
|
||||
return {
|
||||
hasDelegation: true,
|
||||
isValid,
|
||||
timeRemaining: timeRemaining > 0 ? timeRemaining : undefined,
|
||||
publicKey: delegation.browserPublicKey,
|
||||
address: delegation.walletAddress,
|
||||
walletType: delegation.walletType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the stored delegation
|
||||
*/
|
||||
clear(): void {
|
||||
DelegationStorage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message with the delegated browser key
|
||||
* @param message - Unsigned message to sign
|
||||
* @returns Signed message or null if delegation invalid
|
||||
* Sign a message with delegated key
|
||||
*/
|
||||
signMessage(message: UnsignedMessage): OpchanMessage | null {
|
||||
const status = this.getStatus();
|
||||
if (!status.isValid) {
|
||||
console.error('No valid key delegation found. Cannot sign message.');
|
||||
const delegation = DelegationStorage.retrieve();
|
||||
if (!delegation || Date.now() >= delegation.expiryTimestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const delegation = DelegationStorage.retrieve();
|
||||
if (!delegation) return null;
|
||||
|
||||
// Create the message content to sign (without signature fields)
|
||||
// Sign message content
|
||||
const messageToSign = JSON.stringify({
|
||||
...message,
|
||||
signature: undefined,
|
||||
browserPubKey: undefined,
|
||||
delegationProof: undefined,
|
||||
});
|
||||
|
||||
const signature = this.signRaw(messageToSign);
|
||||
const signature = DelegationCrypto.signRaw(
|
||||
messageToSign,
|
||||
delegation.browserPrivateKey
|
||||
);
|
||||
if (!signature) return null;
|
||||
|
||||
return {
|
||||
...message,
|
||||
signature,
|
||||
browserPubKey: delegation.browserPublicKey,
|
||||
delegationProof: this.createProof(delegation),
|
||||
} as OpchanMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a message signature
|
||||
* @param message - Signed message to verify
|
||||
* @returns True if signature is valid
|
||||
* Verify a signed message
|
||||
*/
|
||||
verify(message: OpchanMessage): boolean {
|
||||
// Check for required signature fields
|
||||
if (!message.signature || !message.browserPubKey) {
|
||||
const messageId = message.id || `${message.type}-${message.timestamp}`;
|
||||
console.warn('Message is missing signature information', messageId);
|
||||
async verify(message: OpchanMessage): Promise<boolean> {
|
||||
// Check required fields
|
||||
if (
|
||||
!message.signature ||
|
||||
!message.browserPubKey ||
|
||||
!message.delegationProof ||
|
||||
!message.author
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reconstruct the original signed content
|
||||
// Verify message signature
|
||||
const signedContent = JSON.stringify({
|
||||
...message,
|
||||
signature: undefined,
|
||||
browserPubKey: undefined,
|
||||
delegationProof: undefined,
|
||||
});
|
||||
|
||||
// Verify the signature
|
||||
const isValid = this.verifyRaw(
|
||||
signedContent,
|
||||
message.signature,
|
||||
message.browserPubKey
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
const messageId = message.id || `${message.type}-${message.timestamp}`;
|
||||
console.warn(`Invalid signature for message ${messageId}`);
|
||||
if (
|
||||
!DelegationCrypto.verifyRaw(
|
||||
signedContent,
|
||||
message.signature,
|
||||
message.browserPubKey
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
// Verify delegation proof
|
||||
return await this.verifyProof(
|
||||
message.delegationProof,
|
||||
message.browserPubKey,
|
||||
message.author
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delegation status
|
||||
*/
|
||||
getStatus(
|
||||
currentAddress?: string,
|
||||
currentWalletType?: 'bitcoin' | 'ethereum'
|
||||
): DelegationFullStatus {
|
||||
const delegation = DelegationStorage.retrieve();
|
||||
if (!delegation) {
|
||||
return { hasDelegation: false, isValid: false };
|
||||
}
|
||||
|
||||
// Check validity
|
||||
const now = Date.now();
|
||||
const hasExpired = now >= delegation.expiryTimestamp;
|
||||
const addressMatches =
|
||||
!currentAddress || delegation.walletAddress === currentAddress;
|
||||
const walletTypeMatches =
|
||||
!currentWalletType || delegation.walletType === currentWalletType;
|
||||
const isValid = !hasExpired && addressMatches && walletTypeMatches;
|
||||
|
||||
return {
|
||||
hasDelegation: true,
|
||||
isValid,
|
||||
timeRemaining: isValid
|
||||
? Math.max(0, delegation.expiryTimestamp - now)
|
||||
: undefined,
|
||||
publicKey: delegation.browserPublicKey,
|
||||
address: delegation.walletAddress,
|
||||
walletType: delegation.walletType,
|
||||
proof: isValid ? this.createProof(delegation) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear stored delegation
|
||||
*/
|
||||
clear(): void {
|
||||
DelegationStorage.clear();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -201,69 +194,53 @@ export class DelegationManager {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate a new browser-based keypair for signing messages
|
||||
* Create delegation proof from stored info
|
||||
*/
|
||||
private generateKeypair(): { publicKey: string; privateKey: string } {
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const privateKeyHex = bytesToHex(privateKey);
|
||||
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
const publicKeyHex = bytesToHex(publicKey);
|
||||
|
||||
private createProof(delegation: DelegationInfo): DelegationProof {
|
||||
return {
|
||||
privateKey: privateKeyHex,
|
||||
publicKey: publicKeyHex,
|
||||
authMessage: delegation.authMessage,
|
||||
walletSignature: delegation.walletSignature,
|
||||
expiryTimestamp: delegation.expiryTimestamp,
|
||||
walletAddress: delegation.walletAddress,
|
||||
walletType: delegation.walletType,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a delegation message to be signed by the wallet
|
||||
* Verify delegation proof
|
||||
*/
|
||||
private createDelegationMessage(
|
||||
browserPublicKey: string,
|
||||
walletAddress: string,
|
||||
expiryTimestamp: number
|
||||
): string {
|
||||
return `I, ${walletAddress}, delegate authority to this pubkey: ${browserPublicKey} until ${expiryTimestamp}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a raw string message using the browser-generated private key
|
||||
*/
|
||||
private signRaw(message: string): string | null {
|
||||
const delegation = DelegationStorage.retrieve();
|
||||
if (!delegation) return null;
|
||||
|
||||
try {
|
||||
const privateKeyBytes = hexToBytes(delegation.browserPrivateKey);
|
||||
const messageBytes = new TextEncoder().encode(message);
|
||||
|
||||
const signature = ed.sign(messageBytes, privateKeyBytes);
|
||||
return bytesToHex(signature);
|
||||
} catch (error) {
|
||||
console.error('Error signing with browser key:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature made with the browser key
|
||||
*/
|
||||
private verifyRaw(
|
||||
message: string,
|
||||
signature: string,
|
||||
publicKey: string
|
||||
): boolean {
|
||||
try {
|
||||
const messageBytes = new TextEncoder().encode(message);
|
||||
const signatureBytes = hexToBytes(signature);
|
||||
const publicKeyBytes = hexToBytes(publicKey);
|
||||
|
||||
return ed.verify(signatureBytes, messageBytes, publicKeyBytes);
|
||||
} catch (error) {
|
||||
console.error('Error verifying signature:', error);
|
||||
private async verifyProof(
|
||||
proof: DelegationProof,
|
||||
expectedBrowserKey: string,
|
||||
expectedWalletAddress: string
|
||||
): Promise<boolean> {
|
||||
// Basic validation
|
||||
if (
|
||||
!proof?.walletAddress ||
|
||||
!proof?.authMessage ||
|
||||
proof?.expiryTimestamp === undefined ||
|
||||
proof.walletAddress !== expectedWalletAddress ||
|
||||
Date.now() >= proof.expiryTimestamp
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify auth message format
|
||||
if (
|
||||
!proof.authMessage.includes(expectedWalletAddress) ||
|
||||
!proof.authMessage.includes(expectedBrowserKey) ||
|
||||
!proof.authMessage.includes(proof.expiryTimestamp.toString())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify wallet signature
|
||||
return await DelegationCrypto.verifyWalletSignature(
|
||||
proof.authMessage,
|
||||
proof.walletSignature,
|
||||
proof.walletAddress,
|
||||
proof.walletType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,24 @@ export class DelegationStorage {
|
||||
* Store delegation information in localStorage
|
||||
*/
|
||||
static store(delegation: DelegationInfo): void {
|
||||
console.log('DelegationStorage.store - storing delegation:', {
|
||||
hasAuthMessage: !!delegation.authMessage,
|
||||
hasWalletSignature: !!delegation.walletSignature,
|
||||
hasExpiryTimestamp: delegation.expiryTimestamp !== undefined,
|
||||
hasWalletAddress: !!delegation.walletAddress,
|
||||
hasWalletType: !!delegation.walletType,
|
||||
hasBrowserPublicKey: !!delegation.browserPublicKey,
|
||||
hasBrowserPrivateKey: !!delegation.browserPrivateKey,
|
||||
hasNonce: !!delegation.nonce,
|
||||
authMessage: delegation.authMessage,
|
||||
walletSignature: delegation.walletSignature,
|
||||
expiryTimestamp: delegation.expiryTimestamp,
|
||||
walletAddress: delegation.walletAddress,
|
||||
walletType: delegation.walletType,
|
||||
browserPublicKey: delegation.browserPublicKey,
|
||||
nonce: delegation.nonce,
|
||||
});
|
||||
|
||||
localStorage.setItem(
|
||||
DelegationStorage.STORAGE_KEY,
|
||||
JSON.stringify(delegation)
|
||||
@ -22,7 +40,25 @@ export class DelegationStorage {
|
||||
if (!delegationJson) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(delegationJson);
|
||||
const delegation = JSON.parse(delegationJson);
|
||||
console.log('DelegationStorage.retrieve - retrieved delegation:', {
|
||||
hasAuthMessage: !!delegation.authMessage,
|
||||
hasWalletSignature: !!delegation.walletSignature,
|
||||
hasExpiryTimestamp: delegation.expiryTimestamp !== undefined,
|
||||
hasWalletAddress: !!delegation.walletAddress,
|
||||
hasWalletType: !!delegation.walletType,
|
||||
hasBrowserPublicKey: !!delegation.browserPublicKey,
|
||||
hasBrowserPrivateKey: !!delegation.browserPrivateKey,
|
||||
hasNonce: !!delegation.nonce,
|
||||
authMessage: delegation.authMessage,
|
||||
walletSignature: delegation.walletSignature,
|
||||
expiryTimestamp: delegation.expiryTimestamp,
|
||||
walletAddress: delegation.walletAddress,
|
||||
walletType: delegation.walletType,
|
||||
browserPublicKey: delegation.browserPublicKey,
|
||||
nonce: delegation.nonce,
|
||||
});
|
||||
return delegation;
|
||||
} catch (e) {
|
||||
console.error('Failed to parse delegation information', e);
|
||||
return null;
|
||||
|
||||
@ -1,19 +1,31 @@
|
||||
export type DelegationDuration = '7days' | '30days';
|
||||
|
||||
export interface DelegationSignature {
|
||||
signature: string; // Signature from wallet
|
||||
/**
|
||||
* Cryptographic proof that a wallet authorized a browser key
|
||||
*/
|
||||
export interface DelegationProof {
|
||||
authMessage: string; // "I authorize browser key: 0xabc... until 1640995200"
|
||||
walletSignature: string; // Wallet's signature of authMessage
|
||||
expiryTimestamp: number; // When this delegation expires
|
||||
browserPublicKey: string; // Browser-generated public key that was delegated to
|
||||
walletAddress: string; // Wallet address that signed the delegation
|
||||
walletType: 'bitcoin' | 'ethereum'; // Type of wallet that created the delegation
|
||||
}
|
||||
|
||||
export interface DelegationInfo extends DelegationSignature {
|
||||
browserPrivateKey: string;
|
||||
/**
|
||||
* Complete delegation information including private key (stored locally)
|
||||
*/
|
||||
export interface DelegationInfo extends DelegationProof {
|
||||
browserPublicKey: string; // Browser-generated public key
|
||||
browserPrivateKey: string; // Browser-generated private key (never shared)
|
||||
nonce: string; // Unique nonce to prevent replay attacks
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of current delegation
|
||||
*/
|
||||
export interface DelegationStatus {
|
||||
hasDelegation: boolean;
|
||||
isValid: boolean;
|
||||
timeRemaining?: number;
|
||||
proof?: DelegationProof; // Include proof for verification
|
||||
}
|
||||
|
||||
@ -91,7 +91,9 @@ export class ForumActions {
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedPost = transformPost(result.message! as PostMessage);
|
||||
const transformedPost = await transformPost(
|
||||
result.message! as PostMessage
|
||||
);
|
||||
if (!transformedPost) {
|
||||
return {
|
||||
success: false,
|
||||
@ -166,7 +168,7 @@ export class ForumActions {
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedComment = transformComment(
|
||||
const transformedComment = await transformComment(
|
||||
result.message! as CommentMessage
|
||||
);
|
||||
if (!transformedComment) {
|
||||
@ -223,7 +225,9 @@ export class ForumActions {
|
||||
}
|
||||
|
||||
updateStateFromCache();
|
||||
const transformedCell = transformCell(result.message! as CellMessage);
|
||||
const transformedCell = await transformCell(
|
||||
result.message! as CellMessage
|
||||
);
|
||||
if (!transformedCell) {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@ -13,12 +13,12 @@ import { MessageValidator } from '@/lib/utils/MessageValidator';
|
||||
// Global validator instance for transformers
|
||||
const messageValidator = new MessageValidator();
|
||||
|
||||
export const transformCell = (
|
||||
export const transformCell = async (
|
||||
cellMessage: CellMessage,
|
||||
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
|
||||
userVerificationStatus?: UserVerificationStatus,
|
||||
posts?: Post[]
|
||||
): Cell | null => {
|
||||
): Promise<Cell | null> => {
|
||||
// MANDATORY: All messages must have valid signatures
|
||||
// Since CellMessage extends BaseMessage, it already has required signature fields
|
||||
// But we still need to verify the signature cryptographically
|
||||
@ -30,7 +30,8 @@ export const transformCell = (
|
||||
}
|
||||
|
||||
// Verify signature using the message validator's crypto service
|
||||
const validationReport = messageValidator.getValidationReport(cellMessage);
|
||||
const validationReport =
|
||||
await messageValidator.getValidationReport(cellMessage);
|
||||
if (!validationReport.hasValidSignature) {
|
||||
console.warn(
|
||||
`Cell message ${cellMessage.id} failed signature validation:`,
|
||||
@ -78,11 +79,11 @@ export const transformCell = (
|
||||
return transformedCell;
|
||||
};
|
||||
|
||||
export const transformPost = (
|
||||
export const transformPost = async (
|
||||
postMessage: PostMessage,
|
||||
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
|
||||
userVerificationStatus?: UserVerificationStatus
|
||||
): Post | null => {
|
||||
): Promise<Post | null> => {
|
||||
// MANDATORY: All messages must have valid signatures
|
||||
if (!postMessage.signature || !postMessage.browserPubKey) {
|
||||
console.warn(
|
||||
@ -92,7 +93,8 @@ export const transformPost = (
|
||||
}
|
||||
|
||||
// Verify signature using the message validator's crypto service
|
||||
const validationReport = messageValidator.getValidationReport(postMessage);
|
||||
const validationReport =
|
||||
await messageValidator.getValidationReport(postMessage);
|
||||
if (!validationReport.hasValidSignature) {
|
||||
console.warn(
|
||||
`Post message ${postMessage.id} failed signature validation:`,
|
||||
@ -105,23 +107,29 @@ export const transformPost = (
|
||||
vote => vote.targetId === postMessage.id
|
||||
);
|
||||
// MANDATORY: Filter out votes with invalid signatures
|
||||
const filteredVotes = votes.filter(vote => {
|
||||
if (!vote.signature || !vote.browserPubKey) {
|
||||
console.warn(`Vote ${vote.id} missing signature fields`);
|
||||
return false;
|
||||
}
|
||||
const voteValidation = messageValidator.getValidationReport(vote);
|
||||
if (!voteValidation.hasValidSignature) {
|
||||
console.warn(
|
||||
`Vote ${vote.id} failed signature validation:`,
|
||||
voteValidation.errors
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const upvotes = filteredVotes.filter(vote => vote.value === 1);
|
||||
const downvotes = filteredVotes.filter(vote => vote.value === -1);
|
||||
const filteredVotes = await Promise.all(
|
||||
votes.map(async vote => {
|
||||
if (!vote.signature || !vote.browserPubKey) {
|
||||
console.warn(`Vote ${vote.id} missing signature fields`);
|
||||
return null;
|
||||
}
|
||||
const voteValidation = await messageValidator.getValidationReport(vote);
|
||||
if (!voteValidation.hasValidSignature) {
|
||||
console.warn(
|
||||
`Vote ${vote.id} failed signature validation:`,
|
||||
voteValidation.errors
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return vote;
|
||||
})
|
||||
).then(votes => votes.filter((vote): vote is VoteMessage => vote !== null));
|
||||
const upvotes = filteredVotes.filter(
|
||||
(vote): vote is VoteMessage => vote !== null && vote.value === 1
|
||||
);
|
||||
const downvotes = filteredVotes.filter(
|
||||
(vote): vote is VoteMessage => vote !== null && vote.value === -1
|
||||
);
|
||||
|
||||
const modMsg = messageManager.messageCache.moderations[postMessage.id];
|
||||
const isPostModerated = !!modMsg && modMsg.targetType === 'post';
|
||||
@ -171,11 +179,13 @@ export const transformPost = (
|
||||
const relevanceCalculator = new RelevanceCalculator();
|
||||
|
||||
// Get comments for this post
|
||||
const comments = Object.values(messageManager.messageCache.comments)
|
||||
.map(comment =>
|
||||
const comments = await Promise.all(
|
||||
Object.values(messageManager.messageCache.comments).map(comment =>
|
||||
transformComment(comment, undefined, userVerificationStatus)
|
||||
)
|
||||
.filter(Boolean) as Comment[];
|
||||
).then(comments =>
|
||||
comments.filter((comment): comment is Comment => comment !== null)
|
||||
);
|
||||
const postComments = comments.filter(
|
||||
comment => comment.postId === postMessage.id
|
||||
);
|
||||
@ -215,11 +225,11 @@ export const transformPost = (
|
||||
return transformedPost;
|
||||
};
|
||||
|
||||
export const transformComment = (
|
||||
export const transformComment = async (
|
||||
commentMessage: CommentMessage,
|
||||
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
|
||||
userVerificationStatus?: UserVerificationStatus
|
||||
): Comment | null => {
|
||||
): Promise<Comment | null> => {
|
||||
// MANDATORY: All messages must have valid signatures
|
||||
if (!commentMessage.signature || !commentMessage.browserPubKey) {
|
||||
console.warn(
|
||||
@ -229,7 +239,8 @@ export const transformComment = (
|
||||
}
|
||||
|
||||
// Verify signature using the message validator's crypto service
|
||||
const validationReport = messageValidator.getValidationReport(commentMessage);
|
||||
const validationReport =
|
||||
await messageValidator.getValidationReport(commentMessage);
|
||||
if (!validationReport.hasValidSignature) {
|
||||
console.warn(
|
||||
`Comment message ${commentMessage.id} failed signature validation:`,
|
||||
@ -241,23 +252,29 @@ export const transformComment = (
|
||||
vote => vote.targetId === commentMessage.id
|
||||
);
|
||||
// MANDATORY: Filter out votes with invalid signatures
|
||||
const filteredVotes = votes.filter(vote => {
|
||||
if (!vote.signature || !vote.browserPubKey) {
|
||||
console.warn(`Vote ${vote.id} missing signature fields`);
|
||||
return false;
|
||||
}
|
||||
const voteValidation = messageValidator.getValidationReport(vote);
|
||||
if (!voteValidation.hasValidSignature) {
|
||||
console.warn(
|
||||
`Vote ${vote.id} failed signature validation:`,
|
||||
voteValidation.errors
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const upvotes = filteredVotes.filter(vote => vote.value === 1);
|
||||
const downvotes = filteredVotes.filter(vote => vote.value === -1);
|
||||
const filteredVotes = await Promise.all(
|
||||
votes.map(async vote => {
|
||||
if (!vote.signature || !vote.browserPubKey) {
|
||||
console.warn(`Vote ${vote.id} missing signature fields`);
|
||||
return null;
|
||||
}
|
||||
const voteValidation = await messageValidator.getValidationReport(vote);
|
||||
if (!voteValidation.hasValidSignature) {
|
||||
console.warn(
|
||||
`Vote ${vote.id} failed signature validation:`,
|
||||
voteValidation.errors
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return vote;
|
||||
})
|
||||
).then(votes => votes.filter((vote): vote is typeof vote => vote !== null));
|
||||
const upvotes = filteredVotes.filter(
|
||||
(vote): vote is VoteMessage => vote !== null && vote.value === 1
|
||||
);
|
||||
const downvotes = filteredVotes.filter(
|
||||
(vote): vote is VoteMessage => vote !== null && vote.value === -1
|
||||
);
|
||||
|
||||
const modMsg = messageManager.messageCache.moderations[commentMessage.id];
|
||||
const isCommentModerated = !!modMsg && modMsg.targetType === 'comment';
|
||||
@ -307,7 +324,7 @@ export const transformComment = (
|
||||
|
||||
const relevanceResult = relevanceCalculator.calculateCommentScore(
|
||||
transformedComment,
|
||||
filteredVotes,
|
||||
filteredVotes.filter((vote): vote is VoteMessage => vote !== null),
|
||||
userVerificationStatus
|
||||
);
|
||||
|
||||
@ -321,10 +338,10 @@ export const transformComment = (
|
||||
return transformedComment;
|
||||
};
|
||||
|
||||
export const transformVote = (
|
||||
export const transformVote = async (
|
||||
voteMessage: VoteMessage,
|
||||
_verifyMessage?: unknown // Deprecated parameter, kept for compatibility
|
||||
): VoteMessage | null => {
|
||||
): Promise<VoteMessage | null> => {
|
||||
// MANDATORY: All messages must have valid signatures
|
||||
if (!voteMessage.signature || !voteMessage.browserPubKey) {
|
||||
console.warn(
|
||||
@ -334,7 +351,8 @@ export const transformVote = (
|
||||
}
|
||||
|
||||
// Verify signature using the message validator's crypto service
|
||||
const validationReport = messageValidator.getValidationReport(voteMessage);
|
||||
const validationReport =
|
||||
await messageValidator.getValidationReport(voteMessage);
|
||||
if (!validationReport.hasValidSignature) {
|
||||
console.warn(
|
||||
`Vote message ${voteMessage.id} failed signature validation:`,
|
||||
@ -346,24 +364,32 @@ export const transformVote = (
|
||||
return voteMessage;
|
||||
};
|
||||
|
||||
export const getDataFromCache = (
|
||||
export const getDataFromCache = async (
|
||||
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
|
||||
userVerificationStatus?: UserVerificationStatus
|
||||
) => {
|
||||
): Promise<{ cells: Cell[]; posts: Post[]; comments: Comment[] }> => {
|
||||
// First transform posts and comments to get relevance scores
|
||||
// All validation is now handled internally by the transform functions
|
||||
const posts = Object.values(messageManager.messageCache.posts)
|
||||
.map(post => transformPost(post, undefined, userVerificationStatus))
|
||||
.filter(Boolean) as Post[];
|
||||
const posts = await Promise.all(
|
||||
Object.values(messageManager.messageCache.posts).map(post =>
|
||||
transformPost(post, undefined, userVerificationStatus)
|
||||
)
|
||||
).then(posts => posts.filter((post): post is Post => post !== null));
|
||||
|
||||
const comments = Object.values(messageManager.messageCache.comments)
|
||||
.map(c => transformComment(c, undefined, userVerificationStatus))
|
||||
.filter(Boolean) as Comment[];
|
||||
const comments = await Promise.all(
|
||||
Object.values(messageManager.messageCache.comments).map(c =>
|
||||
transformComment(c, undefined, userVerificationStatus)
|
||||
)
|
||||
).then(comments =>
|
||||
comments.filter((comment): comment is Comment => comment !== null)
|
||||
);
|
||||
|
||||
// Then transform cells with posts for relevance calculation
|
||||
const cells = Object.values(messageManager.messageCache.cells)
|
||||
.map(cell => transformCell(cell, undefined, userVerificationStatus, posts))
|
||||
.filter(Boolean) as Cell[];
|
||||
const cells = await Promise.all(
|
||||
Object.values(messageManager.messageCache.cells).map(cell =>
|
||||
transformCell(cell, undefined, userVerificationStatus, posts)
|
||||
)
|
||||
).then(cells => cells.filter((cell): cell is Cell => cell !== null));
|
||||
|
||||
return { cells, posts, comments };
|
||||
};
|
||||
|
||||
@ -11,7 +11,7 @@ export interface MessageResult {
|
||||
|
||||
export interface MessageServiceInterface {
|
||||
sendMessage(message: UnsignedMessage): Promise<MessageResult>;
|
||||
verifyMessage(message: OpchanMessage): boolean;
|
||||
verifyMessage(message: OpchanMessage): Promise<boolean>;
|
||||
}
|
||||
|
||||
export class MessageService implements MessageServiceInterface {
|
||||
@ -26,13 +26,12 @@ export class MessageService implements MessageServiceInterface {
|
||||
*/
|
||||
async sendMessage(message: UnsignedMessage): Promise<MessageResult> {
|
||||
try {
|
||||
const signedMessage =
|
||||
this.delegationManager.signMessageWithDelegatedKey(message);
|
||||
const signedMessage = this.delegationManager.signMessage(message);
|
||||
|
||||
if (!signedMessage) {
|
||||
// Check if delegation exists but is expired
|
||||
const isDelegationExpired =
|
||||
this.delegationManager.isDelegationValid() === false;
|
||||
const delegationStatus = this.delegationManager.getStatus();
|
||||
const isDelegationExpired = !delegationStatus.isValid;
|
||||
|
||||
return {
|
||||
success: false,
|
||||
@ -81,7 +80,7 @@ export class MessageService implements MessageServiceInterface {
|
||||
/**
|
||||
* Verify a message signature
|
||||
*/
|
||||
verifyMessage(message: OpchanMessage): boolean {
|
||||
return this.delegationManager.verifyMessage(message);
|
||||
async verifyMessage(message: OpchanMessage): Promise<boolean> {
|
||||
return await this.delegationManager.verify(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,14 +27,14 @@ export class MessageValidator {
|
||||
/**
|
||||
* Validates that a message has required signature fields and valid signature
|
||||
*/
|
||||
isValidMessage(message: unknown): message is OpchanMessage {
|
||||
async isValidMessage(message: unknown): Promise<boolean> {
|
||||
// Check basic structure
|
||||
if (!this.hasRequiredFields(message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify signature - we know it's safe to cast here since hasRequiredFields passed
|
||||
return this.delegationManager.verifyMessage(message as OpchanMessage);
|
||||
// Verify signature and delegation proof - we know it's safe to cast here since hasRequiredFields passed
|
||||
return await this.delegationManager.verify(message as OpchanMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +108,7 @@ export class MessageValidator {
|
||||
/**
|
||||
* Validates a batch of messages and returns only valid ones
|
||||
*/
|
||||
filterValidMessages(messages: unknown[]): OpchanMessage[] {
|
||||
async filterValidMessages(messages: unknown[]): Promise<OpchanMessage[]> {
|
||||
const validMessages: OpchanMessage[] = [];
|
||||
const invalidCount = {
|
||||
missingFields: 0,
|
||||
@ -123,7 +123,7 @@ export class MessageValidator {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.delegationManager.verifyMessage(message as OpchanMessage)) {
|
||||
if (!(await this.delegationManager.verify(message as OpchanMessage))) {
|
||||
invalidCount.invalidSignature++;
|
||||
continue;
|
||||
}
|
||||
@ -158,7 +158,7 @@ export class MessageValidator {
|
||||
/**
|
||||
* Strict validation that throws errors for invalid messages
|
||||
*/
|
||||
validateMessage(message: unknown): OpchanMessage {
|
||||
async validateMessage(message: unknown): Promise<OpchanMessage> {
|
||||
if (!this.hasRequiredFields(message)) {
|
||||
const partialMsg = message as PartialMessage;
|
||||
throw new Error(
|
||||
@ -166,7 +166,7 @@ export class MessageValidator {
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.delegationManager.verifyMessage(message as OpchanMessage)) {
|
||||
if (!(await this.delegationManager.verify(message as OpchanMessage))) {
|
||||
const partialMsg = message as PartialMessage;
|
||||
throw new Error(
|
||||
`Message validation failed: Invalid signature (messageId: ${partialMsg?.id})`
|
||||
@ -223,12 +223,12 @@ export class MessageValidator {
|
||||
/**
|
||||
* Creates a validation report for debugging
|
||||
*/
|
||||
getValidationReport(message: unknown): {
|
||||
async getValidationReport(message: unknown): Promise<{
|
||||
isValid: boolean;
|
||||
hasRequiredFields: boolean;
|
||||
hasValidSignature: boolean;
|
||||
errors: string[];
|
||||
} {
|
||||
}> {
|
||||
const errors: string[] = [];
|
||||
let hasRequiredFields = false;
|
||||
let hasValidSignature = false;
|
||||
@ -242,7 +242,7 @@ export class MessageValidator {
|
||||
}
|
||||
|
||||
if (hasRequiredFields) {
|
||||
hasValidSignature = this.delegationManager.verifyMessage(
|
||||
hasValidSignature = await this.delegationManager.verify(
|
||||
message as OpchanMessage
|
||||
);
|
||||
if (!hasValidSignature) {
|
||||
@ -271,11 +271,10 @@ export const messageValidator = new MessageValidator();
|
||||
|
||||
/**
|
||||
* Type guard function for convenient usage
|
||||
* Note: This is not a true type guard since it's async
|
||||
*/
|
||||
export function isValidOpchanMessage(
|
||||
message: unknown
|
||||
): message is OpchanMessage {
|
||||
return messageValidator.isValidMessage(message);
|
||||
export async function isValidOpchanMessage(message: unknown): Promise<boolean> {
|
||||
return await messageValidator.isValidMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,11 +4,11 @@ import { MessageType } from '../../types/waku';
|
||||
* Content topics for different message types
|
||||
*/
|
||||
export const CONTENT_TOPICS: Record<MessageType, string> = {
|
||||
[MessageType.CELL]: '/opchan-sds/1/cell/proto',
|
||||
[MessageType.POST]: '/opchan-sds/1/post/proto',
|
||||
[MessageType.COMMENT]: '/opchan-sds/1/comment/proto',
|
||||
[MessageType.VOTE]: '/opchan-sds/1/vote/proto',
|
||||
[MessageType.MODERATE]: '/opchan-sds/1/moderate/proto',
|
||||
[MessageType.CELL]: '/opchan-sds-ab/1/cell/proto',
|
||||
[MessageType.POST]: '/opchan-sds-ab/1/post/proto',
|
||||
[MessageType.COMMENT]: '/opchan-ab-xyz/1/comment/proto',
|
||||
[MessageType.VOTE]: '/opchan-sds-ab/1/vote/proto',
|
||||
[MessageType.MODERATE]: '/opchan-sds-ab/1/moderate/proto',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -33,8 +33,8 @@ export class CacheService {
|
||||
this.validator = new MessageValidator();
|
||||
}
|
||||
|
||||
public updateCache(message: unknown): boolean {
|
||||
if (!this.validator.isValidMessage(message)) {
|
||||
public async updateCache(message: unknown): Promise<boolean> {
|
||||
if (!(await this.validator.isValidMessage(message))) {
|
||||
const partialMsg = message as {
|
||||
id?: unknown;
|
||||
type?: unknown;
|
||||
|
||||
@ -22,8 +22,8 @@ export class MessageService {
|
||||
|
||||
private setupMessageHandling(): void {
|
||||
if (this.reliableMessaging) {
|
||||
this.reliableMessaging.onMessage(message => {
|
||||
const isNew = this.cacheService.updateCache(message);
|
||||
this.reliableMessaging.onMessage(async message => {
|
||||
const isNew = await this.cacheService.updateCache(message);
|
||||
if (isNew) {
|
||||
this.messageReceivedCallbacks.forEach(callback => callback(message));
|
||||
}
|
||||
@ -34,33 +34,43 @@ export class MessageService {
|
||||
public async sendMessage(
|
||||
message: OpchanMessage,
|
||||
statusCallback?: MessageStatusCallback
|
||||
): Promise<void> {
|
||||
): Promise<{ success: boolean; message?: OpchanMessage; error?: string }> {
|
||||
if (!this.reliableMessaging) {
|
||||
throw new Error('Reliable messaging not initialized');
|
||||
return { success: false, error: 'Reliable messaging not initialized' };
|
||||
}
|
||||
|
||||
if (!this.nodeManager.isReady) {
|
||||
throw new Error('Network not ready');
|
||||
return { success: false, error: 'Network not ready' };
|
||||
}
|
||||
|
||||
// Update cache optimistically
|
||||
this.cacheService.updateCache(message);
|
||||
try {
|
||||
// Update cache optimistically
|
||||
await this.cacheService.updateCache(message);
|
||||
|
||||
// Send via reliable messaging with status tracking
|
||||
await this.reliableMessaging.sendMessage(message, {
|
||||
onSent: id => {
|
||||
console.log(`Message ${id} sent`);
|
||||
statusCallback?.onSent?.(id);
|
||||
},
|
||||
onAcknowledged: id => {
|
||||
console.log(`Message ${id} acknowledged`);
|
||||
statusCallback?.onAcknowledged?.(id);
|
||||
},
|
||||
onError: (id, error) => {
|
||||
console.error(`Message ${id} failed:`, error);
|
||||
statusCallback?.onError?.(id, error);
|
||||
},
|
||||
});
|
||||
// Send via reliable messaging with status tracking
|
||||
await this.reliableMessaging.sendMessage(message, {
|
||||
onSent: id => {
|
||||
console.log(`Message ${id} sent`);
|
||||
statusCallback?.onSent?.(id);
|
||||
},
|
||||
onAcknowledged: id => {
|
||||
console.log(`Message ${id} acknowledged`);
|
||||
statusCallback?.onAcknowledged?.(id);
|
||||
},
|
||||
onError: (id, error) => {
|
||||
console.error(`Message ${id} failed:`, error);
|
||||
statusCallback?.onError?.(id, error);
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, message };
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public onMessageReceived(callback: MessageReceivedCallback): () => void {
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
||||
import { AppKit } from '@reown/appkit';
|
||||
import { getEnsName } from '@wagmi/core';
|
||||
import {
|
||||
getEnsName,
|
||||
verifyMessage as verifyEthereumMessage,
|
||||
} from '@wagmi/core';
|
||||
import { ChainNamespace } from '@reown/appkit-common';
|
||||
import { config } from './config';
|
||||
import { Provider } from '@reown/appkit-controllers';
|
||||
import { WalletInfo, ActiveWallet } from './types';
|
||||
import * as bitcoinMessage from 'bitcoinjs-message';
|
||||
|
||||
export class WalletManager {
|
||||
private static instance: WalletManager | null = null;
|
||||
@ -167,6 +171,64 @@ export class WalletManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a message signature against a wallet address
|
||||
* @param message - The original message that was signed
|
||||
* @param signature - The signature to verify
|
||||
* @param walletAddress - The expected signer's address
|
||||
* @param walletType - The type of wallet (bitcoin/ethereum)
|
||||
* @returns Promise<boolean> - True if signature is valid
|
||||
*/
|
||||
static async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
walletAddress: string,
|
||||
walletType: 'bitcoin' | 'ethereum'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
console.log('WalletManager.verifySignature - verifying signature:', {
|
||||
message,
|
||||
signature,
|
||||
walletAddress,
|
||||
walletType,
|
||||
});
|
||||
if (walletType === 'ethereum') {
|
||||
return await verifyEthereumMessage(config, {
|
||||
address: walletAddress as `0x${string}`,
|
||||
message,
|
||||
signature: signature as `0x${string}`,
|
||||
});
|
||||
} else if (walletType === 'bitcoin') {
|
||||
console.log(
|
||||
'WalletManager.verifySignature - verifying bitcoin signature:',
|
||||
{
|
||||
message,
|
||||
walletAddress,
|
||||
signature,
|
||||
}
|
||||
);
|
||||
const result = bitcoinMessage.verify(message, walletAddress, signature);
|
||||
console.log(
|
||||
'WalletManager.verifySignature - bitcoin signature result:',
|
||||
result
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
console.error(
|
||||
'WalletManager.verifySignature - unknown wallet type:',
|
||||
walletType
|
||||
);
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'WalletManager.verifySignature - error verifying signature:',
|
||||
error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive wallet info including ENS resolution for Ethereum
|
||||
*/
|
||||
@ -208,6 +270,7 @@ export const walletManager = {
|
||||
hasInstance: WalletManager.hasInstance,
|
||||
clear: WalletManager.clear,
|
||||
resolveENS: WalletManager.resolveENS,
|
||||
verifySignature: WalletManager.verifySignature,
|
||||
};
|
||||
|
||||
export * from './types';
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
ModerateMessage,
|
||||
} from '@/types/waku';
|
||||
import { EVerificationStatus } from './identity';
|
||||
import { DelegationProof } from '@/lib/delegation/types';
|
||||
|
||||
/**
|
||||
* Union type of all message types
|
||||
@ -92,6 +93,7 @@ export interface Comment extends CommentMessage {
|
||||
export interface SignedMessage {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
delegationProof?: DelegationProof; // Cryptographic proof that browser key was authorized
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user