chore: improve signatures - bind w1/s1 with delegated keypairs

This commit is contained in:
Danish Arora 2025-09-02 14:27:54 +05:30
parent 5e0622183e
commit 414747f396
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
22 changed files with 645 additions and 394 deletions

106
package-lock.json generated
View File

@ -44,6 +44,7 @@
"@reown/appkit-wallet-button": "^1.7.17", "@reown/appkit-wallet-button": "^1.7.17",
"@tanstack/react-query": "^5.84.1", "@tanstack/react-query": "^5.84.1",
"@waku/sdk": "^0.0.35-67a7287.0", "@waku/sdk": "^0.0.35-67a7287.0",
"bitcoinjs-message": "^2.2.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
@ -64,6 +65,7 @@
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vaul": "^0.9.3", "vaul": "^0.9.3",
"viem": "^2.37.1",
"wagmi": "^2.16.1", "wagmi": "^2.16.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
@ -8249,7 +8251,6 @@
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"file-uri-to-path": "1.0.0" "file-uri-to-path": "1.0.0"
} }
@ -8284,7 +8285,6 @@
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
"integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
@ -8322,7 +8322,6 @@
"resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz", "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz",
"integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==", "integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"bech32": "^1.1.3", "bech32": "^1.1.3",
"bs58check": "^2.1.2", "bs58check": "^2.1.2",
@ -8340,7 +8339,6 @@
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
@ -8349,22 +8347,19 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/bitcoinjs-message/node_modules/bn.js": { "node_modules/bitcoinjs-message/node_modules/bn.js": {
"version": "4.12.2", "version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/bitcoinjs-message/node_modules/bs58": { "node_modules/bitcoinjs-message/node_modules/bs58": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"base-x": "^3.0.2" "base-x": "^3.0.2"
} }
@ -8374,7 +8369,6 @@
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"bs58": "^4.0.0", "bs58": "^4.0.0",
"create-hash": "^1.1.0", "create-hash": "^1.1.0",
@ -8387,7 +8381,6 @@
"integrity": "sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==", "integrity": "sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"bindings": "^1.5.0", "bindings": "^1.5.0",
"bip66": "^1.1.5", "bip66": "^1.1.5",
@ -8447,15 +8440,13 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/browserify-aes": { "node_modules/browserify-aes": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"buffer-xor": "^1.0.3", "buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0", "cipher-base": "^1.0.0",
@ -8561,7 +8552,6 @@
"resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
"integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==", "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==",
"license": "MIT", "license": "MIT",
"optional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -8570,8 +8560,7 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/bufferutil": { "node_modules/bufferutil": {
"version": "4.0.9", "version": "4.0.9",
@ -8808,7 +8797,6 @@
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz",
"integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"inherits": "^2.0.4", "inherits": "^2.0.4",
"safe-buffer": "^5.2.1" "safe-buffer": "^5.2.1"
@ -9356,7 +9344,6 @@
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"cipher-base": "^1.0.1", "cipher-base": "^1.0.1",
"inherits": "^2.0.1", "inherits": "^2.0.1",
@ -9370,7 +9357,6 @@
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"cipher-base": "^1.0.3", "cipher-base": "^1.0.3",
"create-hash": "^1.1.0", "create-hash": "^1.1.0",
@ -9827,7 +9813,6 @@
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
"integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"browserify-aes": "^1.0.6", "browserify-aes": "^1.0.6",
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
@ -9942,7 +9927,6 @@
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
"integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"bn.js": "^4.11.9", "bn.js": "^4.11.9",
"brorand": "^1.1.0", "brorand": "^1.1.0",
@ -9957,8 +9941,7 @@
"version": "4.12.2", "version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/embla-carousel": { "node_modules/embla-carousel": {
"version": "8.3.0", "version": "8.3.0",
@ -10550,7 +10533,6 @@
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"md5.js": "^1.3.4", "md5.js": "^1.3.4",
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
@ -10691,8 +10673,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "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==", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
@ -11086,7 +11067,6 @@
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"inherits": "^2.0.4", "inherits": "^2.0.4",
"readable-stream": "^3.6.0", "readable-stream": "^3.6.0",
@ -11101,7 +11081,6 @@
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"inherits": "^2.0.3", "inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1" "minimalistic-assert": "^1.0.1"
@ -11136,7 +11115,6 @@
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"hash.js": "^1.0.3", "hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.0",
@ -12178,7 +12156,6 @@
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"hash-base": "^3.0.0", "hash-base": "^3.0.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
@ -12248,15 +12225,13 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC", "license": "ISC"
"optional": true
}, },
"node_modules/minimalistic-crypto-utils": { "node_modules/minimalistic-crypto-utils": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
@ -12348,8 +12323,7 @@
"version": "2.23.0", "version": "2.23.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
@ -13749,7 +13723,6 @@
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"hash-base": "^3.0.0", "hash-base": "^3.0.0",
"inherits": "^2.0.1" "inherits": "^2.0.1"
@ -15045,9 +15018,9 @@
} }
}, },
"node_modules/viem": { "node_modules/viem": {
"version": "2.33.2", "version": "2.37.1",
"resolved": "https://registry.npmjs.org/viem/-/viem-2.33.2.tgz", "resolved": "https://registry.npmjs.org/viem/-/viem-2.37.1.tgz",
"integrity": "sha512-/720OaM4dHWs8vXwNpyet+PRERhPaW+n/1UVSCzyb9jkmwwVfaiy/R6YfCFb4v+XXbo8s3Fapa3DM5yCRSkulA==", "integrity": "sha512-IzacdIXYlOvzDJwNKIVa53LP/LaP70qvBGAIoGH6R+n06S/ru/nnQxLNZ6+JImvIcxwNwgAl0jUA6FZEIQQWSw==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -15056,14 +15029,14 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/curves": "1.9.2", "@noble/curves": "1.9.1",
"@noble/hashes": "1.8.0", "@noble/hashes": "1.8.0",
"@scure/bip32": "1.7.0", "@scure/bip32": "1.7.0",
"@scure/bip39": "1.6.0", "@scure/bip39": "1.6.0",
"abitype": "1.0.8", "abitype": "1.0.8",
"isows": "1.0.7", "isows": "1.0.7",
"ox": "0.8.6", "ox": "0.9.3",
"ws": "8.18.2" "ws": "8.18.3"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=5.0.4" "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": { "node_modules/viem/node_modules/@scure/bip32": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
@ -15108,9 +15096,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/viem/node_modules/ox": { "node_modules/viem/node_modules/ox": {
"version": "0.8.6", "version": "0.9.3",
"resolved": "https://registry.npmjs.org/ox/-/ox-0.8.6.tgz", "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.3.tgz",
"integrity": "sha512-eiKcgiVVEGDtEpEdFi1EGoVVI48j6icXHce9nFwCNM7CKG3uoCXKdr4TPhS00Iy1TR2aWSF1ltPD0x/YgqIL9w==", "integrity": "sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -15121,11 +15109,11 @@
"dependencies": { "dependencies": {
"@adraffy/ens-normalize": "^1.11.0", "@adraffy/ens-normalize": "^1.11.0",
"@noble/ciphers": "^1.3.0", "@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.1", "@noble/curves": "1.9.1",
"@noble/hashes": "^1.8.0", "@noble/hashes": "^1.8.0",
"@scure/bip32": "^1.7.0", "@scure/bip32": "^1.7.0",
"@scure/bip39": "^1.6.0", "@scure/bip39": "^1.6.0",
"abitype": "^1.0.8", "abitype": "^1.0.9",
"eventemitter3": "5.0.1" "eventemitter3": "5.0.1"
}, },
"peerDependencies": { "peerDependencies": {
@ -15137,23 +15125,23 @@
} }
} }
}, },
"node_modules/viem/node_modules/ws": { "node_modules/viem/node_modules/ox/node_modules/abitype": {
"version": "8.18.2", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.9.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "integrity": "sha512-oN0S++TQmlwWuB+rkA6aiEefLv3SP+2l/tC5mux/TLj6qdA6rF15Vbpex4fHovLsMkwLwTIRj8/Q8vXCS3GfOg==",
"license": "MIT", "license": "MIT",
"engines": { "funding": {
"node": ">=10.0.0" "url": "https://github.com/sponsors/wevm"
}, },
"peerDependencies": { "peerDependencies": {
"bufferutil": "^4.0.1", "typescript": ">=5.0.4",
"utf-8-validate": ">=5.0.2" "zod": "^3 >=3.22.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"bufferutil": { "typescript": {
"optional": true "optional": true
}, },
"utf-8-validate": { "zod": {
"optional": true "optional": true
} }
} }

View File

@ -51,6 +51,7 @@
"@reown/appkit-wallet-button": "^1.7.17", "@reown/appkit-wallet-button": "^1.7.17",
"@tanstack/react-query": "^5.84.1", "@tanstack/react-query": "^5.84.1",
"@waku/sdk": "^0.0.35-67a7287.0", "@waku/sdk": "^0.0.35-67a7287.0",
"bitcoinjs-message": "^2.2.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
@ -71,6 +72,7 @@
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vaul": "^0.9.3", "vaul": "^0.9.3",
"viem": "^2.37.1",
"wagmi": "^2.16.1", "wagmi": "^2.16.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },

View File

@ -27,7 +27,7 @@ import { useAppKitAccount, useDisconnect } from '@reown/appkit/react';
import { WalletWizard } from '@/components/ui/wallet-wizard'; import { WalletWizard } from '@/components/ui/wallet-wizard';
const Header = () => { const Header = () => {
const { currentUser, verificationStatus, isDelegationValid } = useAuth(); const { currentUser, verificationStatus, getDelegationStatus } = useAuth();
const { isNetworkConnected, isRefreshing } = useForum(); const { isNetworkConnected, isRefreshing } = useForum();
const location = useLocation(); const location = useLocation();
const { toast } = useToast(); const { toast } = useToast();
@ -95,9 +95,9 @@ const Header = () => {
case 'verified-none': case 'verified-none':
return 'Read-Only Access'; return 'Read-Only Access';
case 'verified-basic': case 'verified-basic':
return isDelegationValid() ? 'Full Access' : 'Setup Key'; return getDelegationStatus().isValid ? 'Full Access' : 'Setup Key';
case 'verified-owner': case 'verified-owner':
return isDelegationValid() ? 'Premium Access' : 'Setup Key'; return getDelegationStatus().isValid ? 'Premium Access' : 'Setup Key';
default: default:
return 'Setup Account'; return 'Setup Account';
} }
@ -112,13 +112,13 @@ const Header = () => {
case 'verified-none': case 'verified-none':
return <CircleSlash className="w-3 h-3" />; return <CircleSlash className="w-3 h-3" />;
case 'verified-basic': case 'verified-basic':
return isDelegationValid() ? ( return getDelegationStatus().isValid ? (
<CheckCircle className="w-3 h-3" /> <CheckCircle className="w-3 h-3" />
) : ( ) : (
<Key className="w-3 h-3" /> <CheckCircle className="w-3 h-3" />
); );
case 'verified-owner': case 'verified-owner':
return isDelegationValid() ? ( return getDelegationStatus().isValid ? (
<CheckCircle className="w-3 h-3" /> <CheckCircle className="w-3 h-3" />
) : ( ) : (
<Key className="w-3 h-3" /> <Key className="w-3 h-3" />
@ -137,9 +137,9 @@ const Header = () => {
case 'verified-none': case 'verified-none':
return 'secondary'; return 'secondary';
case 'verified-basic': case 'verified-basic':
return isDelegationValid() ? 'default' : 'outline'; return getDelegationStatus().isValid ? 'default' : 'outline';
case 'verified-owner': case 'verified-owner':
return isDelegationValid() ? 'default' : 'outline'; return getDelegationStatus().isValid ? 'default' : 'outline';
default: default:
return 'outline'; return 'outline';
} }

View File

@ -20,8 +20,7 @@ export function DelegationStep({
const { const {
currentUser, currentUser,
delegateKey, delegateKey,
isDelegationValid, getDelegationStatus,
delegationTimeRemaining,
isAuthenticating, isAuthenticating,
clearDelegation, clearDelegation,
} = useAuth(); } = useAuth();
@ -162,23 +161,30 @@ export function DelegationStep({
<div className="space-y-3"> <div className="space-y-3">
{/* Status */} {/* Status */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{isDelegationValid() ? ( {getDelegationStatus().isValid ? (
<CheckCircle className="h-4 w-4 text-green-500" /> <CheckCircle className="h-4 w-4 text-green-500" />
) : ( ) : (
<AlertCircle className="h-4 w-4 text-yellow-500" /> <AlertCircle className="h-4 w-4 text-yellow-500" />
)} )}
<span <span
className={`text-sm font-medium ${ 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> </span>
{isDelegationValid() && ( {getDelegationStatus().isValid && (
<span className="text-xs text-neutral-400"> <span className="text-xs text-neutral-400">
{Math.floor(delegationTimeRemaining() / (1000 * 60 * 60))}h{' '}
{Math.floor( {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 m remaining
</span> </span>
@ -186,7 +192,7 @@ export function DelegationStep({
</div> </div>
{/* Duration Selection */} {/* Duration Selection */}
{!isDelegationValid() && ( {!getDelegationStatus().isValid && (
<div className="space-y-3"> <div className="space-y-3">
<label className="text-sm font-medium text-neutral-300"> <label className="text-sm font-medium text-neutral-300">
Delegation Duration: Delegation Duration:
@ -223,7 +229,7 @@ export function DelegationStep({
)} )}
{/* Delegated Browser Public Key */} {/* Delegated Browser Public Key */}
{isDelegationValid() && currentUser?.browserPubKey && ( {getDelegationStatus().isValid && currentUser?.browserPubKey && (
<div className="text-xs text-neutral-400"> <div className="text-xs text-neutral-400">
<div className="font-mono break-all bg-neutral-800 p-2 rounded"> <div className="font-mono break-all bg-neutral-800 p-2 rounded">
{currentUser.browserPubKey} {currentUser.browserPubKey}
@ -239,7 +245,7 @@ export function DelegationStep({
)} )}
{/* Delete Button for Active Delegations */} {/* Delete Button for Active Delegations */}
{isDelegationValid() && ( {getDelegationStatus().isValid && (
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
onClick={clearDelegation} onClick={clearDelegation}
@ -257,7 +263,7 @@ export function DelegationStep({
{/* Action Buttons */} {/* Action Buttons */}
<div className="mt-auto space-y-2"> <div className="mt-auto space-y-2">
{isDelegationValid() ? ( {getDelegationStatus().isValid ? (
<Button <Button
onClick={handleComplete} onClick={handleComplete}
className="w-full bg-green-600 hover:bg-green-700 text-white" className="w-full bg-green-600 hover:bg-green-700 text-white"

View File

@ -28,7 +28,8 @@ export function WalletWizard({
}: WalletWizardProps) { }: WalletWizardProps) {
const [currentStep, setCurrentStep] = React.useState<WizardStep>(1); const [currentStep, setCurrentStep] = React.useState<WizardStep>(1);
const [isLoading, setIsLoading] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false);
const { isAuthenticated, verificationStatus, isDelegationValid } = useAuth(); const { isAuthenticated, verificationStatus, getDelegationStatus } =
useAuth();
const hasInitialized = React.useRef(false); const hasInitialized = React.useRef(false);
// Reset wizard when opened and determine starting step // Reset wizard when opened and determine starting step
@ -48,7 +49,7 @@ export function WalletWizard({
(verificationStatus === 'verified-owner' || (verificationStatus === 'verified-owner' ||
verificationStatus === 'verified-basic' || verificationStatus === 'verified-basic' ||
verificationStatus === 'verified-none') && verificationStatus === 'verified-none') &&
!isDelegationValid() !getDelegationStatus().isValid
) { ) {
setCurrentStep(3); // Start at delegation step if verified but no valid delegation setCurrentStep(3); // Start at delegation step if verified but no valid delegation
} else { } else {
@ -59,7 +60,7 @@ export function WalletWizard({
} else if (!open) { } else if (!open) {
hasInitialized.current = false; hasInitialized.current = false;
} }
}, [open, isAuthenticated, verificationStatus, isDelegationValid]); }, [open, isAuthenticated, verificationStatus, getDelegationStatus]);
const handleStepComplete = (step: WizardStep) => { const handleStepComplete = (step: WizardStep) => {
if (step < 3) { if (step < 3) {
@ -100,7 +101,7 @@ export function WalletWizard({
verificationStatus !== 'verified-none') verificationStatus !== 'verified-none')
) )
return 'disabled'; return 'disabled';
if (isDelegationValid()) return 'complete'; if (getDelegationStatus().isValid) return 'complete';
return 'current'; return 'current';
} }
return 'disabled'; return 'disabled';

View File

@ -3,7 +3,11 @@ import { useToast } from '@/components/ui/use-toast';
import { OpchanMessage } from '@/types/forum'; import { OpchanMessage } from '@/types/forum';
import { User, EVerificationStatus, DisplayPreference } from '@/types/identity'; import { User, EVerificationStatus, DisplayPreference } from '@/types/identity';
import { WalletManager } from '@/lib/wallet'; 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'; import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
export type VerificationStatus = export type VerificationStatus =
@ -25,7 +29,7 @@ interface AuthContextType {
getDelegationStatus: () => DelegationFullStatus; getDelegationStatus: () => DelegationFullStatus;
clearDelegation: () => void; clearDelegation: () => void;
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>; signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
verifyMessage: (message: OpchanMessage) => boolean; verifyMessage: (message: OpchanMessage) => Promise<boolean>;
} }
const AuthContext = createContext<AuthContextType | undefined>(undefined); const AuthContext = createContext<AuthContextType | undefined>(undefined);
@ -170,7 +174,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
user.address, user.address,
user.walletType, user.walletType,
duration, duration,
(message) => walletManager.signMessage(message) message => walletManager.signMessage(message)
); );
} catch (error) { } catch (error) {
console.error( console.error(
@ -447,7 +451,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
}; };
const getDelegationStatus = (): DelegationFullStatus => { const getDelegationStatus = (): DelegationFullStatus => {
return delegationManager.getStatus(currentUser?.address, currentUser?.walletType); return delegationManager.getStatus(
currentUser?.address,
currentUser?.walletType
);
}; };
const clearDelegation = (): void => { const clearDelegation = (): void => {
@ -477,8 +484,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
): Promise<OpchanMessage | null> => { ): Promise<OpchanMessage | null> => {
return delegationManager.signMessage(message); return delegationManager.signMessage(message);
}, },
verifyMessage: (message: OpchanMessage): boolean => { verifyMessage: async (message: OpchanMessage): Promise<boolean> => {
return delegationManager.verify(message); return await delegationManager.verify(message);
}, },
}; };

View File

@ -105,10 +105,11 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
); );
// Transform message cache data to the expected types // Transform message cache data to the expected types
const updateStateFromCache = useCallback(() => { const updateStateFromCache = useCallback(async () => {
// Use the verifyMessage function from delegationManager if available // Use the verifyMessage function from delegationManager if available
const verifyFn = isAuthenticated const verifyFn = isAuthenticated
? (message: OpchanMessage) => delegationManager.verifyMessage(message) ? async (message: OpchanMessage) =>
await delegationManager.verify(message)
: undefined; : undefined;
// Build user verification status for relevance calculation // Build user verification status for relevance calculation
@ -162,7 +163,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
relevanceCalculator.buildUserVerificationStatus(allUsers); relevanceCalculator.buildUserVerificationStatus(allUsers);
// Transform data with relevance calculation (initial pass) // Transform data with relevance calculation (initial pass)
const { cells, posts, comments } = getDataFromCache( const { cells, posts, comments } = await getDataFromCache(
verifyFn, verifyFn,
initialStatus initialStatus
); );
@ -208,7 +209,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
}); });
const enrichedStatus = const enrichedStatus =
relevanceCalculator.buildUserVerificationStatus(enrichedUsers); relevanceCalculator.buildUserVerificationStatus(enrichedUsers);
const transformed = getDataFromCache(verifyFn, enrichedStatus); const transformed = await getDataFromCache(verifyFn, enrichedStatus);
setCells(transformed.cells); setCells(transformed.cells);
setPosts(transformed.posts); setPosts(transformed.posts);
setComments(transformed.comments); setComments(transformed.comments);
@ -220,7 +221,7 @@ export function ForumProvider({ children }: { children: React.ReactNode }) {
setIsRefreshing(true); setIsRefreshing(true);
try { try {
// SDS handles message syncing automatically, just update UI // SDS handles message syncing automatically, just update UI
updateStateFromCache(); await updateStateFromCache();
toast({ toast({
title: 'Data Refreshed', title: 'Data Refreshed',
description: 'Your view has been updated.', description: 'Your view has been updated.',

View File

@ -34,8 +34,9 @@ export const useDelegation = () => {
hasDelegation: status.hasDelegation, hasDelegation: status.hasDelegation,
isValid: status.isValid, isValid: status.isValid,
timeRemaining: status.timeRemaining, timeRemaining: status.timeRemaining,
expiresAt: expiresAt: status.timeRemaining
status.timeRemaining ? new Date(Date.now() + status.timeRemaining) : undefined, ? new Date(Date.now() + status.timeRemaining)
: undefined,
publicKey: status.publicKey, publicKey: status.publicKey,
address: status.address, address: status.address,
walletType: status.walletType, walletType: status.walletType,

View File

@ -12,32 +12,32 @@ export const useMessageSigning = () => {
const { const {
signMessage: contextSignMessage, signMessage: contextSignMessage,
verifyMessage: contextVerifyMessage, verifyMessage: contextVerifyMessage,
isDelegationValid, getDelegationStatus,
} = context; } = context;
const signMessage = useCallback( const signMessage = useCallback(
async (message: OpchanMessage): Promise<OpchanMessage | null> => { async (message: OpchanMessage): Promise<OpchanMessage | null> => {
// Check if we have a valid delegation before attempting to sign // 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.'); console.warn('No valid delegation found. Cannot sign message.');
return null; return null;
} }
return contextSignMessage(message); return contextSignMessage(message);
}, },
[contextSignMessage, isDelegationValid] [contextSignMessage, getDelegationStatus]
); );
const verifyMessage = useCallback( const verifyMessage = useCallback(
(message: OpchanMessage): boolean => { async (message: OpchanMessage): Promise<boolean> => {
return contextVerifyMessage(message); return await contextVerifyMessage(message);
}, },
[contextVerifyMessage] [contextVerifyMessage]
); );
const canSignMessages = useCallback((): boolean => { const canSignMessages = useCallback((): boolean => {
return isDelegationValid(); return getDelegationStatus().isValid;
}, [isDelegationValid]); }, [getDelegationStatus]);
return { return {
// Message signing // Message signing

View 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;
}
}
}

View File

@ -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 { OpchanMessage } from '@/types/forum';
import { UnsignedMessage } from '@/types/waku'; import { UnsignedMessage } from '@/types/waku';
import { DelegationDuration, DelegationInfo, DelegationStatus } from './types'; import {
DelegationDuration,
DelegationInfo,
DelegationStatus,
DelegationProof,
} from './types';
import { DelegationStorage } from './storage'; 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 { export interface DelegationFullStatus extends DelegationStatus {
publicKey?: string; publicKey?: string;
address?: string; address?: string;
@ -17,15 +16,11 @@ export interface DelegationFullStatus extends DelegationStatus {
} }
export class DelegationManager { export class DelegationManager {
// Duration options in hours
private static readonly DURATION_HOURS = { private static readonly DURATION_HOURS = {
'7days': 24 * 7, // 168 hours '7days': 24 * 7,
'30days': 24 * 30, // 720 hours '30days': 24 * 30,
} as const; } as const;
/**
* Get the number of hours for a given duration
*/
static getDurationHours(duration: DelegationDuration): number { static getDurationHours(duration: DelegationDuration): number {
return DelegationManager.DURATION_HOURS[duration]; return DelegationManager.DURATION_HOURS[duration];
} }
@ -35,12 +30,7 @@ export class DelegationManager {
// ============================================================================ // ============================================================================
/** /**
* Create a complete delegation with a single method call * Create a delegation with cryptographic proof
* @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
*/ */
async delegate( async delegate(
address: string, address: string,
@ -49,29 +39,34 @@ export class DelegationManager {
signFunction: (message: string) => Promise<string> signFunction: (message: string) => Promise<string>
): Promise<boolean> { ): Promise<boolean> {
try { try {
// Generate new keypair // Generate browser keypair
const keypair = this.generateKeypair(); const keypair = DelegationCrypto.generateKeypair();
// Create delegation message with expiry // Create expiry and nonce
const expiryHours = DelegationManager.getDurationHours(duration); const expiryTimestamp =
const expiryTimestamp = Date.now() + expiryHours * 60 * 60 * 1000; Date.now() +
const delegationMessage = this.createDelegationMessage( DelegationManager.getDurationHours(duration) * 60 * 60 * 1000;
const nonce = crypto.randomUUID();
// Create and sign authorization message
const authMessage = DelegationCrypto.createAuthMessage(
keypair.publicKey, keypair.publicKey,
address, address,
expiryTimestamp
);
// Sign the delegation message with wallet
const signature = await signFunction(delegationMessage);
// Create and store the delegation
const delegationInfo: DelegationInfo = {
signature,
expiryTimestamp, expiryTimestamp,
browserPublicKey: keypair.publicKey, nonce
browserPrivateKey: keypair.privateKey, );
const walletSignature = await signFunction(authMessage);
// Store delegation
const delegationInfo: DelegationInfo = {
authMessage,
walletSignature,
expiryTimestamp,
walletAddress: address, walletAddress: address,
walletType, walletType,
browserPublicKey: keypair.publicKey,
browserPrivateKey: keypair.privateKey,
nonce,
}; };
DelegationStorage.store(delegationInfo); DelegationStorage.store(delegationInfo);
@ -83,117 +78,115 @@ export class DelegationManager {
} }
/** /**
* Get comprehensive delegation status * Sign a message with delegated key
* @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
*/ */
signMessage(message: UnsignedMessage): OpchanMessage | null { signMessage(message: UnsignedMessage): OpchanMessage | null {
const status = this.getStatus(); const delegation = DelegationStorage.retrieve();
if (!status.isValid) { if (!delegation || Date.now() >= delegation.expiryTimestamp) {
console.error('No valid key delegation found. Cannot sign message.');
return null; return null;
} }
const delegation = DelegationStorage.retrieve(); // Sign message content
if (!delegation) return null;
// Create the message content to sign (without signature fields)
const messageToSign = JSON.stringify({ const messageToSign = JSON.stringify({
...message, ...message,
signature: undefined, signature: undefined,
browserPubKey: undefined, browserPubKey: undefined,
delegationProof: undefined,
}); });
const signature = this.signRaw(messageToSign); const signature = DelegationCrypto.signRaw(
messageToSign,
delegation.browserPrivateKey
);
if (!signature) return null; if (!signature) return null;
return { return {
...message, ...message,
signature, signature,
browserPubKey: delegation.browserPublicKey, browserPubKey: delegation.browserPublicKey,
delegationProof: this.createProof(delegation),
} as OpchanMessage; } as OpchanMessage;
} }
/** /**
* Verify a message signature * Verify a signed message
* @param message - Signed message to verify
* @returns True if signature is valid
*/ */
verify(message: OpchanMessage): boolean { async verify(message: OpchanMessage): Promise<boolean> {
// Check for required signature fields // Check required fields
if (!message.signature || !message.browserPubKey) { if (
const messageId = message.id || `${message.type}-${message.timestamp}`; !message.signature ||
console.warn('Message is missing signature information', messageId); !message.browserPubKey ||
!message.delegationProof ||
!message.author
) {
return false; return false;
} }
// Reconstruct the original signed content // Verify message signature
const signedContent = JSON.stringify({ const signedContent = JSON.stringify({
...message, ...message,
signature: undefined, signature: undefined,
browserPubKey: undefined, browserPubKey: undefined,
delegationProof: undefined,
}); });
// Verify the signature if (
const isValid = this.verifyRaw( !DelegationCrypto.verifyRaw(
signedContent, signedContent,
message.signature, message.signature,
message.browserPubKey message.browserPubKey
); )
) {
if (!isValid) { return false;
const messageId = message.id || `${message.type}-${message.timestamp}`;
console.warn(`Invalid signature for message ${messageId}`);
} }
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 } { private createProof(delegation: DelegationInfo): DelegationProof {
const privateKey = ed.utils.randomPrivateKey();
const privateKeyHex = bytesToHex(privateKey);
const publicKey = ed.getPublicKey(privateKey);
const publicKeyHex = bytesToHex(publicKey);
return { return {
privateKey: privateKeyHex, authMessage: delegation.authMessage,
publicKey: publicKeyHex, 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( private async verifyProof(
browserPublicKey: string, proof: DelegationProof,
walletAddress: string, expectedBrowserKey: string,
expiryTimestamp: number expectedWalletAddress: string
): string { ): Promise<boolean> {
return `I, ${walletAddress}, delegate authority to this pubkey: ${browserPublicKey} until ${expiryTimestamp}`; // Basic validation
} if (
!proof?.walletAddress ||
/** !proof?.authMessage ||
* Sign a raw string message using the browser-generated private key proof?.expiryTimestamp === undefined ||
*/ proof.walletAddress !== expectedWalletAddress ||
private signRaw(message: string): string | null { Date.now() >= proof.expiryTimestamp
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);
return false; 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
);
} }
} }

View File

@ -8,6 +8,24 @@ export class DelegationStorage {
* Store delegation information in localStorage * Store delegation information in localStorage
*/ */
static store(delegation: DelegationInfo): void { 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( localStorage.setItem(
DelegationStorage.STORAGE_KEY, DelegationStorage.STORAGE_KEY,
JSON.stringify(delegation) JSON.stringify(delegation)
@ -22,7 +40,25 @@ export class DelegationStorage {
if (!delegationJson) return null; if (!delegationJson) return null;
try { 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) { } catch (e) {
console.error('Failed to parse delegation information', e); console.error('Failed to parse delegation information', e);
return null; return null;

View File

@ -1,19 +1,31 @@
export type DelegationDuration = '7days' | '30days'; 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 expiryTimestamp: number; // When this delegation expires
browserPublicKey: string; // Browser-generated public key that was delegated to
walletAddress: string; // Wallet address that signed the delegation walletAddress: string; // Wallet address that signed the delegation
walletType: 'bitcoin' | 'ethereum'; // Type of wallet that created 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 { export interface DelegationStatus {
hasDelegation: boolean; hasDelegation: boolean;
isValid: boolean; isValid: boolean;
timeRemaining?: number; timeRemaining?: number;
proof?: DelegationProof; // Include proof for verification
} }

View File

@ -91,7 +91,9 @@ export class ForumActions {
} }
updateStateFromCache(); updateStateFromCache();
const transformedPost = transformPost(result.message! as PostMessage); const transformedPost = await transformPost(
result.message! as PostMessage
);
if (!transformedPost) { if (!transformedPost) {
return { return {
success: false, success: false,
@ -166,7 +168,7 @@ export class ForumActions {
} }
updateStateFromCache(); updateStateFromCache();
const transformedComment = transformComment( const transformedComment = await transformComment(
result.message! as CommentMessage result.message! as CommentMessage
); );
if (!transformedComment) { if (!transformedComment) {
@ -223,7 +225,9 @@ export class ForumActions {
} }
updateStateFromCache(); updateStateFromCache();
const transformedCell = transformCell(result.message! as CellMessage); const transformedCell = await transformCell(
result.message! as CellMessage
);
if (!transformedCell) { if (!transformedCell) {
return { return {
success: false, success: false,

View File

@ -13,12 +13,12 @@ import { MessageValidator } from '@/lib/utils/MessageValidator';
// Global validator instance for transformers // Global validator instance for transformers
const messageValidator = new MessageValidator(); const messageValidator = new MessageValidator();
export const transformCell = ( export const transformCell = async (
cellMessage: CellMessage, cellMessage: CellMessage,
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility _verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
userVerificationStatus?: UserVerificationStatus, userVerificationStatus?: UserVerificationStatus,
posts?: Post[] posts?: Post[]
): Cell | null => { ): Promise<Cell | null> => {
// MANDATORY: All messages must have valid signatures // MANDATORY: All messages must have valid signatures
// Since CellMessage extends BaseMessage, it already has required signature fields // Since CellMessage extends BaseMessage, it already has required signature fields
// But we still need to verify the signature cryptographically // 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 // Verify signature using the message validator's crypto service
const validationReport = messageValidator.getValidationReport(cellMessage); const validationReport =
await messageValidator.getValidationReport(cellMessage);
if (!validationReport.hasValidSignature) { if (!validationReport.hasValidSignature) {
console.warn( console.warn(
`Cell message ${cellMessage.id} failed signature validation:`, `Cell message ${cellMessage.id} failed signature validation:`,
@ -78,11 +79,11 @@ export const transformCell = (
return transformedCell; return transformedCell;
}; };
export const transformPost = ( export const transformPost = async (
postMessage: PostMessage, postMessage: PostMessage,
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility _verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
userVerificationStatus?: UserVerificationStatus userVerificationStatus?: UserVerificationStatus
): Post | null => { ): Promise<Post | null> => {
// MANDATORY: All messages must have valid signatures // MANDATORY: All messages must have valid signatures
if (!postMessage.signature || !postMessage.browserPubKey) { if (!postMessage.signature || !postMessage.browserPubKey) {
console.warn( console.warn(
@ -92,7 +93,8 @@ export const transformPost = (
} }
// Verify signature using the message validator's crypto service // Verify signature using the message validator's crypto service
const validationReport = messageValidator.getValidationReport(postMessage); const validationReport =
await messageValidator.getValidationReport(postMessage);
if (!validationReport.hasValidSignature) { if (!validationReport.hasValidSignature) {
console.warn( console.warn(
`Post message ${postMessage.id} failed signature validation:`, `Post message ${postMessage.id} failed signature validation:`,
@ -105,23 +107,29 @@ export const transformPost = (
vote => vote.targetId === postMessage.id vote => vote.targetId === postMessage.id
); );
// MANDATORY: Filter out votes with invalid signatures // MANDATORY: Filter out votes with invalid signatures
const filteredVotes = votes.filter(vote => { const filteredVotes = await Promise.all(
if (!vote.signature || !vote.browserPubKey) { votes.map(async vote => {
console.warn(`Vote ${vote.id} missing signature fields`); if (!vote.signature || !vote.browserPubKey) {
return false; console.warn(`Vote ${vote.id} missing signature fields`);
} return null;
const voteValidation = messageValidator.getValidationReport(vote); }
if (!voteValidation.hasValidSignature) { const voteValidation = await messageValidator.getValidationReport(vote);
console.warn( if (!voteValidation.hasValidSignature) {
`Vote ${vote.id} failed signature validation:`, console.warn(
voteValidation.errors `Vote ${vote.id} failed signature validation:`,
); voteValidation.errors
return false; );
} return null;
return true; }
}); return vote;
const upvotes = filteredVotes.filter(vote => vote.value === 1); })
const downvotes = filteredVotes.filter(vote => vote.value === -1); ).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 modMsg = messageManager.messageCache.moderations[postMessage.id];
const isPostModerated = !!modMsg && modMsg.targetType === 'post'; const isPostModerated = !!modMsg && modMsg.targetType === 'post';
@ -171,11 +179,13 @@ export const transformPost = (
const relevanceCalculator = new RelevanceCalculator(); const relevanceCalculator = new RelevanceCalculator();
// Get comments for this post // Get comments for this post
const comments = Object.values(messageManager.messageCache.comments) const comments = await Promise.all(
.map(comment => Object.values(messageManager.messageCache.comments).map(comment =>
transformComment(comment, undefined, userVerificationStatus) transformComment(comment, undefined, userVerificationStatus)
) )
.filter(Boolean) as Comment[]; ).then(comments =>
comments.filter((comment): comment is Comment => comment !== null)
);
const postComments = comments.filter( const postComments = comments.filter(
comment => comment.postId === postMessage.id comment => comment.postId === postMessage.id
); );
@ -215,11 +225,11 @@ export const transformPost = (
return transformedPost; return transformedPost;
}; };
export const transformComment = ( export const transformComment = async (
commentMessage: CommentMessage, commentMessage: CommentMessage,
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility _verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
userVerificationStatus?: UserVerificationStatus userVerificationStatus?: UserVerificationStatus
): Comment | null => { ): Promise<Comment | null> => {
// MANDATORY: All messages must have valid signatures // MANDATORY: All messages must have valid signatures
if (!commentMessage.signature || !commentMessage.browserPubKey) { if (!commentMessage.signature || !commentMessage.browserPubKey) {
console.warn( console.warn(
@ -229,7 +239,8 @@ export const transformComment = (
} }
// Verify signature using the message validator's crypto service // Verify signature using the message validator's crypto service
const validationReport = messageValidator.getValidationReport(commentMessage); const validationReport =
await messageValidator.getValidationReport(commentMessage);
if (!validationReport.hasValidSignature) { if (!validationReport.hasValidSignature) {
console.warn( console.warn(
`Comment message ${commentMessage.id} failed signature validation:`, `Comment message ${commentMessage.id} failed signature validation:`,
@ -241,23 +252,29 @@ export const transformComment = (
vote => vote.targetId === commentMessage.id vote => vote.targetId === commentMessage.id
); );
// MANDATORY: Filter out votes with invalid signatures // MANDATORY: Filter out votes with invalid signatures
const filteredVotes = votes.filter(vote => { const filteredVotes = await Promise.all(
if (!vote.signature || !vote.browserPubKey) { votes.map(async vote => {
console.warn(`Vote ${vote.id} missing signature fields`); if (!vote.signature || !vote.browserPubKey) {
return false; console.warn(`Vote ${vote.id} missing signature fields`);
} return null;
const voteValidation = messageValidator.getValidationReport(vote); }
if (!voteValidation.hasValidSignature) { const voteValidation = await messageValidator.getValidationReport(vote);
console.warn( if (!voteValidation.hasValidSignature) {
`Vote ${vote.id} failed signature validation:`, console.warn(
voteValidation.errors `Vote ${vote.id} failed signature validation:`,
); voteValidation.errors
return false; );
} return null;
return true; }
}); return vote;
const upvotes = filteredVotes.filter(vote => vote.value === 1); })
const downvotes = filteredVotes.filter(vote => vote.value === -1); ).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 modMsg = messageManager.messageCache.moderations[commentMessage.id];
const isCommentModerated = !!modMsg && modMsg.targetType === 'comment'; const isCommentModerated = !!modMsg && modMsg.targetType === 'comment';
@ -307,7 +324,7 @@ export const transformComment = (
const relevanceResult = relevanceCalculator.calculateCommentScore( const relevanceResult = relevanceCalculator.calculateCommentScore(
transformedComment, transformedComment,
filteredVotes, filteredVotes.filter((vote): vote is VoteMessage => vote !== null),
userVerificationStatus userVerificationStatus
); );
@ -321,10 +338,10 @@ export const transformComment = (
return transformedComment; return transformedComment;
}; };
export const transformVote = ( export const transformVote = async (
voteMessage: VoteMessage, voteMessage: VoteMessage,
_verifyMessage?: unknown // Deprecated parameter, kept for compatibility _verifyMessage?: unknown // Deprecated parameter, kept for compatibility
): VoteMessage | null => { ): Promise<VoteMessage | null> => {
// MANDATORY: All messages must have valid signatures // MANDATORY: All messages must have valid signatures
if (!voteMessage.signature || !voteMessage.browserPubKey) { if (!voteMessage.signature || !voteMessage.browserPubKey) {
console.warn( console.warn(
@ -334,7 +351,8 @@ export const transformVote = (
} }
// Verify signature using the message validator's crypto service // Verify signature using the message validator's crypto service
const validationReport = messageValidator.getValidationReport(voteMessage); const validationReport =
await messageValidator.getValidationReport(voteMessage);
if (!validationReport.hasValidSignature) { if (!validationReport.hasValidSignature) {
console.warn( console.warn(
`Vote message ${voteMessage.id} failed signature validation:`, `Vote message ${voteMessage.id} failed signature validation:`,
@ -346,24 +364,32 @@ export const transformVote = (
return voteMessage; return voteMessage;
}; };
export const getDataFromCache = ( export const getDataFromCache = async (
_verifyMessage?: unknown, // Deprecated parameter, kept for compatibility _verifyMessage?: unknown, // Deprecated parameter, kept for compatibility
userVerificationStatus?: UserVerificationStatus userVerificationStatus?: UserVerificationStatus
) => { ): Promise<{ cells: Cell[]; posts: Post[]; comments: Comment[] }> => {
// First transform posts and comments to get relevance scores // First transform posts and comments to get relevance scores
// All validation is now handled internally by the transform functions // All validation is now handled internally by the transform functions
const posts = Object.values(messageManager.messageCache.posts) const posts = await Promise.all(
.map(post => transformPost(post, undefined, userVerificationStatus)) Object.values(messageManager.messageCache.posts).map(post =>
.filter(Boolean) as Post[]; transformPost(post, undefined, userVerificationStatus)
)
).then(posts => posts.filter((post): post is Post => post !== null));
const comments = Object.values(messageManager.messageCache.comments) const comments = await Promise.all(
.map(c => transformComment(c, undefined, userVerificationStatus)) Object.values(messageManager.messageCache.comments).map(c =>
.filter(Boolean) as Comment[]; transformComment(c, undefined, userVerificationStatus)
)
).then(comments =>
comments.filter((comment): comment is Comment => comment !== null)
);
// Then transform cells with posts for relevance calculation // Then transform cells with posts for relevance calculation
const cells = Object.values(messageManager.messageCache.cells) const cells = await Promise.all(
.map(cell => transformCell(cell, undefined, userVerificationStatus, posts)) Object.values(messageManager.messageCache.cells).map(cell =>
.filter(Boolean) as Cell[]; transformCell(cell, undefined, userVerificationStatus, posts)
)
).then(cells => cells.filter((cell): cell is Cell => cell !== null));
return { cells, posts, comments }; return { cells, posts, comments };
}; };

View File

@ -11,7 +11,7 @@ export interface MessageResult {
export interface MessageServiceInterface { export interface MessageServiceInterface {
sendMessage(message: UnsignedMessage): Promise<MessageResult>; sendMessage(message: UnsignedMessage): Promise<MessageResult>;
verifyMessage(message: OpchanMessage): boolean; verifyMessage(message: OpchanMessage): Promise<boolean>;
} }
export class MessageService implements MessageServiceInterface { export class MessageService implements MessageServiceInterface {
@ -26,13 +26,12 @@ export class MessageService implements MessageServiceInterface {
*/ */
async sendMessage(message: UnsignedMessage): Promise<MessageResult> { async sendMessage(message: UnsignedMessage): Promise<MessageResult> {
try { try {
const signedMessage = const signedMessage = this.delegationManager.signMessage(message);
this.delegationManager.signMessageWithDelegatedKey(message);
if (!signedMessage) { if (!signedMessage) {
// Check if delegation exists but is expired // Check if delegation exists but is expired
const isDelegationExpired = const delegationStatus = this.delegationManager.getStatus();
this.delegationManager.isDelegationValid() === false; const isDelegationExpired = !delegationStatus.isValid;
return { return {
success: false, success: false,
@ -81,7 +80,7 @@ export class MessageService implements MessageServiceInterface {
/** /**
* Verify a message signature * Verify a message signature
*/ */
verifyMessage(message: OpchanMessage): boolean { async verifyMessage(message: OpchanMessage): Promise<boolean> {
return this.delegationManager.verifyMessage(message); return await this.delegationManager.verify(message);
} }
} }

View File

@ -27,14 +27,14 @@ export class MessageValidator {
/** /**
* Validates that a message has required signature fields and valid signature * 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 // Check basic structure
if (!this.hasRequiredFields(message)) { if (!this.hasRequiredFields(message)) {
return false; return false;
} }
// Verify signature - we know it's safe to cast here since hasRequiredFields passed // Verify signature and delegation proof - we know it's safe to cast here since hasRequiredFields passed
return this.delegationManager.verifyMessage(message as OpchanMessage); 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 * Validates a batch of messages and returns only valid ones
*/ */
filterValidMessages(messages: unknown[]): OpchanMessage[] { async filterValidMessages(messages: unknown[]): Promise<OpchanMessage[]> {
const validMessages: OpchanMessage[] = []; const validMessages: OpchanMessage[] = [];
const invalidCount = { const invalidCount = {
missingFields: 0, missingFields: 0,
@ -123,7 +123,7 @@ export class MessageValidator {
continue; continue;
} }
if (!this.delegationManager.verifyMessage(message as OpchanMessage)) { if (!(await this.delegationManager.verify(message as OpchanMessage))) {
invalidCount.invalidSignature++; invalidCount.invalidSignature++;
continue; continue;
} }
@ -158,7 +158,7 @@ export class MessageValidator {
/** /**
* Strict validation that throws errors for invalid messages * Strict validation that throws errors for invalid messages
*/ */
validateMessage(message: unknown): OpchanMessage { async validateMessage(message: unknown): Promise<OpchanMessage> {
if (!this.hasRequiredFields(message)) { if (!this.hasRequiredFields(message)) {
const partialMsg = message as PartialMessage; const partialMsg = message as PartialMessage;
throw new Error( 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; const partialMsg = message as PartialMessage;
throw new Error( throw new Error(
`Message validation failed: Invalid signature (messageId: ${partialMsg?.id})` `Message validation failed: Invalid signature (messageId: ${partialMsg?.id})`
@ -223,12 +223,12 @@ export class MessageValidator {
/** /**
* Creates a validation report for debugging * Creates a validation report for debugging
*/ */
getValidationReport(message: unknown): { async getValidationReport(message: unknown): Promise<{
isValid: boolean; isValid: boolean;
hasRequiredFields: boolean; hasRequiredFields: boolean;
hasValidSignature: boolean; hasValidSignature: boolean;
errors: string[]; errors: string[];
} { }> {
const errors: string[] = []; const errors: string[] = [];
let hasRequiredFields = false; let hasRequiredFields = false;
let hasValidSignature = false; let hasValidSignature = false;
@ -242,7 +242,7 @@ export class MessageValidator {
} }
if (hasRequiredFields) { if (hasRequiredFields) {
hasValidSignature = this.delegationManager.verifyMessage( hasValidSignature = await this.delegationManager.verify(
message as OpchanMessage message as OpchanMessage
); );
if (!hasValidSignature) { if (!hasValidSignature) {
@ -271,11 +271,10 @@ export const messageValidator = new MessageValidator();
/** /**
* Type guard function for convenient usage * Type guard function for convenient usage
* Note: This is not a true type guard since it's async
*/ */
export function isValidOpchanMessage( export async function isValidOpchanMessage(message: unknown): Promise<boolean> {
message: unknown return await messageValidator.isValidMessage(message);
): message is OpchanMessage {
return messageValidator.isValidMessage(message);
} }
/** /**

View File

@ -4,11 +4,11 @@ import { MessageType } from '../../types/waku';
* Content topics for different message types * Content topics for different message types
*/ */
export const CONTENT_TOPICS: Record<MessageType, string> = { export const CONTENT_TOPICS: Record<MessageType, string> = {
[MessageType.CELL]: '/opchan-sds/1/cell/proto', [MessageType.CELL]: '/opchan-sds-ab/1/cell/proto',
[MessageType.POST]: '/opchan-sds/1/post/proto', [MessageType.POST]: '/opchan-sds-ab/1/post/proto',
[MessageType.COMMENT]: '/opchan-sds/1/comment/proto', [MessageType.COMMENT]: '/opchan-ab-xyz/1/comment/proto',
[MessageType.VOTE]: '/opchan-sds/1/vote/proto', [MessageType.VOTE]: '/opchan-sds-ab/1/vote/proto',
[MessageType.MODERATE]: '/opchan-sds/1/moderate/proto', [MessageType.MODERATE]: '/opchan-sds-ab/1/moderate/proto',
}; };
/** /**

View File

@ -33,8 +33,8 @@ export class CacheService {
this.validator = new MessageValidator(); this.validator = new MessageValidator();
} }
public updateCache(message: unknown): boolean { public async updateCache(message: unknown): Promise<boolean> {
if (!this.validator.isValidMessage(message)) { if (!(await this.validator.isValidMessage(message))) {
const partialMsg = message as { const partialMsg = message as {
id?: unknown; id?: unknown;
type?: unknown; type?: unknown;

View File

@ -22,8 +22,8 @@ export class MessageService {
private setupMessageHandling(): void { private setupMessageHandling(): void {
if (this.reliableMessaging) { if (this.reliableMessaging) {
this.reliableMessaging.onMessage(message => { this.reliableMessaging.onMessage(async message => {
const isNew = this.cacheService.updateCache(message); const isNew = await this.cacheService.updateCache(message);
if (isNew) { if (isNew) {
this.messageReceivedCallbacks.forEach(callback => callback(message)); this.messageReceivedCallbacks.forEach(callback => callback(message));
} }
@ -34,33 +34,43 @@ export class MessageService {
public async sendMessage( public async sendMessage(
message: OpchanMessage, message: OpchanMessage,
statusCallback?: MessageStatusCallback statusCallback?: MessageStatusCallback
): Promise<void> { ): Promise<{ success: boolean; message?: OpchanMessage; error?: string }> {
if (!this.reliableMessaging) { if (!this.reliableMessaging) {
throw new Error('Reliable messaging not initialized'); return { success: false, error: 'Reliable messaging not initialized' };
} }
if (!this.nodeManager.isReady) { if (!this.nodeManager.isReady) {
throw new Error('Network not ready'); return { success: false, error: 'Network not ready' };
} }
// Update cache optimistically try {
this.cacheService.updateCache(message); // Update cache optimistically
await this.cacheService.updateCache(message);
// Send via reliable messaging with status tracking // Send via reliable messaging with status tracking
await this.reliableMessaging.sendMessage(message, { await this.reliableMessaging.sendMessage(message, {
onSent: id => { onSent: id => {
console.log(`Message ${id} sent`); console.log(`Message ${id} sent`);
statusCallback?.onSent?.(id); statusCallback?.onSent?.(id);
}, },
onAcknowledged: id => { onAcknowledged: id => {
console.log(`Message ${id} acknowledged`); console.log(`Message ${id} acknowledged`);
statusCallback?.onAcknowledged?.(id); statusCallback?.onAcknowledged?.(id);
}, },
onError: (id, error) => { onError: (id, error) => {
console.error(`Message ${id} failed:`, error); console.error(`Message ${id} failed:`, error);
statusCallback?.onError?.(id, 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 { public onMessageReceived(callback: MessageReceivedCallback): () => void {

View File

@ -1,10 +1,14 @@
import { UseAppKitAccountReturn } from '@reown/appkit/react'; import { UseAppKitAccountReturn } from '@reown/appkit/react';
import { AppKit } from '@reown/appkit'; 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 { ChainNamespace } from '@reown/appkit-common';
import { config } from './config'; import { config } from './config';
import { Provider } from '@reown/appkit-controllers'; import { Provider } from '@reown/appkit-controllers';
import { WalletInfo, ActiveWallet } from './types'; import { WalletInfo, ActiveWallet } from './types';
import * as bitcoinMessage from 'bitcoinjs-message';
export class WalletManager { export class WalletManager {
private static instance: WalletManager | null = null; 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 * Get comprehensive wallet info including ENS resolution for Ethereum
*/ */
@ -208,6 +270,7 @@ export const walletManager = {
hasInstance: WalletManager.hasInstance, hasInstance: WalletManager.hasInstance,
clear: WalletManager.clear, clear: WalletManager.clear,
resolveENS: WalletManager.resolveENS, resolveENS: WalletManager.resolveENS,
verifySignature: WalletManager.verifySignature,
}; };
export * from './types'; export * from './types';

View File

@ -6,6 +6,7 @@ import {
ModerateMessage, ModerateMessage,
} from '@/types/waku'; } from '@/types/waku';
import { EVerificationStatus } from './identity'; import { EVerificationStatus } from './identity';
import { DelegationProof } from '@/lib/delegation/types';
/** /**
* Union type of all message types * Union type of all message types
@ -92,6 +93,7 @@ export interface Comment extends CommentMessage {
export interface SignedMessage { export interface SignedMessage {
signature: string; signature: string;
browserPubKey: string; browserPubKey: string;
delegationProof?: DelegationProof; // Cryptographic proof that browser key was authorized
} }
/** /**