-
Notifications
You must be signed in to change notification settings - Fork 88
Feature/lit 4041 Support Eip1271 #735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DashKash54
merged 5 commits into
master
from
feature/lit-4041-support-eip1271-on-the-sdk
Dec 11, 2024
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ec899c2
test: EIP1271 AuthSig with EthSign
DashKash54 253f386
test: Add failure case when EIP1271 contract returns 0xffffffff
DashKash54 a401416
test: EIP1271 SHA256 AuthSig suite
DashKash54 08c4f0e
refac: Prettier
DashKash54 3102961
Merge branch 'master' into feature/lit-4041-support-eip1271-on-the-sdk
DashKash54 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
local-tests/tests/testKeccakEip1271AuthSigToEncryptDecryptString.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| import { AuthSig, BaseSiweMessage, ILitNodeClient } from '@lit-protocol/types'; | ||
| import { AccessControlConditions } from 'local-tests/setup/accs/accs'; | ||
| import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; | ||
| import { log } from '@lit-protocol/misc'; | ||
| import { encryptString, decryptToString } from '@lit-protocol/encryption'; | ||
| import { CENTRALISATION_BY_NETWORK } from '@lit-protocol/constants'; | ||
| import { createSiweMessage } from '@lit-protocol/auth-helpers'; | ||
| import { hashMessage } from 'ethers/lib/utils'; | ||
| import { ethers } from 'ethers'; | ||
| import { createHash } from 'crypto'; | ||
|
|
||
| /** | ||
| * Test Commands: | ||
| * ✅ NETWORK=datil-dev yarn test:local --filter=testKeccakEip1271AuthSigToEncryptDecryptString | ||
| * ✅ NETWORK=datil-test yarn test:local --filter=testKeccakEip1271AuthSigToEncryptDecryptString | ||
| * ✅ NETWORK=custom yarn test:local --filter=testKeccakEip1271AuthSigToEncryptDecryptString | ||
| */ | ||
| export const testKeccakEip1271AuthSigToEncryptDecryptString = async ( | ||
| devEnv: TinnyEnvironment | ||
| ) => { | ||
| const dataToEncrypt = 'Decrypted from EIP1271 AuthSig'; | ||
| const contractAddress = '0x88105De2349f59767278Fd15c0858f806c08d615'; | ||
| const deployerAddress = '0x0b1C5E9E82393AD5d1d1e9a498BF7bAAC13b31Ee'; // No purpose other than to be a random address | ||
| const abi = [ | ||
| 'function setTempOwner(address _tempOwner) external', | ||
| 'function getTempOwner() external view returns (address)', | ||
| 'function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4)', | ||
| ]; | ||
|
|
||
| const alice = await devEnv.createRandomPerson(); | ||
| if (CENTRALISATION_BY_NETWORK[devEnv.network] === 'decentralised') { | ||
| // The capacity credits NFT owner automatically uses the capacity credits | ||
| // to pay for the encryption | ||
| await alice.mintCapacityCreditsNFT(); | ||
| } | ||
|
|
||
| let accs = AccessControlConditions.getEmvBasicAccessControlConditions({ | ||
| userAddress: contractAddress, | ||
| }); | ||
| accs[0].chain = 'yellowstone'; // Contract deployed on Yellowstone | ||
|
|
||
| const encryptRes = await encryptString( | ||
| { | ||
| accessControlConditions: accs, | ||
| dataToEncrypt, | ||
| }, | ||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||
| ); | ||
|
|
||
| // log('encryptRes:', encryptRes); | ||
|
|
||
| if (!encryptRes.ciphertext) { | ||
| throw new Error(`Expected "ciphertext" in encryptRes`); | ||
| } | ||
|
|
||
| if (!encryptRes.dataToEncryptHash) { | ||
| throw new Error(`Expected "dataToEncryptHash" to in encryptRes`); | ||
| } | ||
|
|
||
| // Craft the SiweMessage to be hashed & signed | ||
| const siweMessage = await createSiweMessage<BaseSiweMessage>({ | ||
| nonce: await devEnv.litNodeClient.getLatestBlockhash(), | ||
| walletAddress: alice.wallet.address, | ||
| }); | ||
|
|
||
| const siweSignature = await alice.wallet.signMessage(siweMessage); | ||
| log('siweSignature: ', siweSignature); | ||
|
|
||
| // Internally generate from wallet.signMessage | ||
| const hash = hashMessage(siweMessage); | ||
| const hashUint8Array = ethers.utils.arrayify(hash); | ||
| log('hash:', hash); | ||
| log('hashUint8Array: ', hashUint8Array); // Match it against the hash done on the nodes before calling verifySignature() | ||
|
|
||
| const eip1271AuthSig: AuthSig = { | ||
| address: contractAddress, | ||
| sig: siweSignature, | ||
| derivedVia: 'EIP1271', | ||
| signedMessage: siweMessage, | ||
| }; | ||
|
|
||
| // log(eip1271AuthSig); | ||
|
|
||
| // Test from the contract | ||
| const contract = new ethers.Contract(contractAddress, abi, alice.wallet); | ||
| const setDeployerAsTempOwnerTx = await contract.setTempOwner(deployerAddress); | ||
| await setDeployerAsTempOwnerTx.wait(); | ||
|
|
||
| log( | ||
| '0. isValidSignature should FAIL since Alice (AuthSig.sig) is not the tempOwner yet' | ||
| ); | ||
| try { | ||
| const isValid = await contract.isValidSignature(hash, siweSignature); | ||
| if (isValid === '0x1626ba7e') { | ||
| throw new Error( | ||
| `Expected isValidSignature to be 0xffffffff but got ${isValid}` | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| log('Error calling isValidSignature:', error); | ||
| throw error; | ||
| } | ||
|
|
||
| try { | ||
| const _decryptRes = await decryptToString( | ||
| { | ||
| accessControlConditions: accs, | ||
| ciphertext: encryptRes.ciphertext, | ||
| dataToEncryptHash: encryptRes.dataToEncryptHash, | ||
| authSig: eip1271AuthSig, | ||
| chain: 'yellowstone', // Deployed chain | ||
| }, | ||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||
| ); | ||
| } catch (error) { | ||
| if ( | ||
| !error.message.includes('NodeContractAuthsigUnauthorized') || | ||
| !error.message.includes('Access control failed for Smart contract') || | ||
| !error.message.includes( | ||
| 'validation error: Authsig failed for contract 0x88105De2349f59767278Fd15c0858f806c08d615. Return value was ffffffff.' | ||
| ) | ||
| ) { | ||
| throw new Error( | ||
| `Expected error message to contain specific EIP1271 validation failure, but got: ${error}` | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Should PASS now | ||
| log('1. Setting temp owner...'); | ||
| const setTempOwnerTx = await contract.setTempOwner(alice.wallet.address); | ||
| await setTempOwnerTx.wait(); | ||
| log('Set tempOwner transaction hash: ', setTempOwnerTx.hash); | ||
|
|
||
| const tempOwner = await contract.getTempOwner(); | ||
| if (tempOwner.toLowerCase() !== alice.wallet.address.toLowerCase()) { | ||
| throw new Error( | ||
| `Expected temp owner to be ${alice.wallet.address} but got ${tempOwner}` | ||
| ); | ||
| } | ||
|
|
||
| log('2. Checking isValidSignature...'); | ||
| try { | ||
| const isValid = await contract.isValidSignature(hash, siweSignature); | ||
| if (isValid !== '0x1626ba7e') { | ||
| throw new Error( | ||
| `Expected isValidSignature to be 0x1626ba7e but got ${isValid}` | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| log('Error calling isValidSignature:', error); | ||
| throw error; | ||
| } | ||
|
|
||
| // -- Decrypt the encrypted string | ||
| const decryptRes = await decryptToString( | ||
| { | ||
| accessControlConditions: accs, | ||
| ciphertext: encryptRes.ciphertext, | ||
| dataToEncryptHash: encryptRes.dataToEncryptHash, | ||
| authSig: eip1271AuthSig, | ||
| chain: 'yellowstone', // Deployed chain | ||
| }, | ||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||
| ); | ||
|
|
||
| if (decryptRes !== dataToEncrypt) { | ||
| throw new Error( | ||
| `Expected decryptRes to be ${dataToEncrypt} but got ${decryptRes}` | ||
| ); | ||
| } | ||
|
|
||
| log('✅ decryptRes:', decryptRes); | ||
| }; | ||
177 changes: 177 additions & 0 deletions
177
local-tests/tests/testShaEip1271AuthSigToEncryptDecryptString.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,177 @@ | ||||||
| import { AuthSig, BaseSiweMessage, ILitNodeClient } from '@lit-protocol/types'; | ||||||
| import { AccessControlConditions } from 'local-tests/setup/accs/accs'; | ||||||
| import { TinnyEnvironment } from 'local-tests/setup/tinny-environment'; | ||||||
| import { log } from '@lit-protocol/misc'; | ||||||
| import { encryptString, decryptToString } from '@lit-protocol/encryption'; | ||||||
| import { CENTRALISATION_BY_NETWORK } from '@lit-protocol/constants'; | ||||||
| import { createSiweMessage } from '@lit-protocol/auth-helpers'; | ||||||
| import { joinSignature } from 'ethers/lib/utils'; | ||||||
| import { ethers } from 'ethers'; | ||||||
| import { createHash } from 'crypto'; | ||||||
|
|
||||||
| /** | ||||||
| * Test Commands: | ||||||
| * ✅ NETWORK=datil-dev yarn test:local --filter=testShaEip1271AuthSigToEncryptDecryptString | ||||||
| * ✅ NETWORK=datil-test yarn test:local --filter=testShaEip1271AuthSigToEncryptDecryptString | ||||||
| * ✅ NETWORK=custom yarn test:local --filter=testShaEip1271AuthSigToEncryptDecryptString | ||||||
| */ | ||||||
| export const testShaEip1271AuthSigToEncryptDecryptString = async ( | ||||||
| devEnv: TinnyEnvironment | ||||||
| ) => { | ||||||
| const dataToEncrypt = 'Decrypted from EIP1271 AuthSig'; | ||||||
| const contractAddress = '0x88105De2349f59767278Fd15c0858f806c08d615'; | ||||||
| const deployerAddress = '0x0b1C5E9E82393AD5d1d1e9a498BF7bAAC13b31Ee'; // No purpose other than to be a random address | ||||||
| const abi = [ | ||||||
| 'function setTempOwner(address _tempOwner) external', | ||||||
| 'function getTempOwner() external view returns (address)', | ||||||
| 'function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4)', | ||||||
| ]; | ||||||
|
|
||||||
| const alice = await devEnv.createRandomPerson(); | ||||||
| if (CENTRALISATION_BY_NETWORK[devEnv.network] === 'decentralised') { | ||||||
| // The capacity credits NFT owner automatically uses the capacity credits | ||||||
| // to pay for the encryption | ||||||
| await alice.mintCapacityCreditsNFT(); | ||||||
| } | ||||||
|
|
||||||
| let accs = AccessControlConditions.getEmvBasicAccessControlConditions({ | ||||||
| userAddress: contractAddress, | ||||||
| }); | ||||||
| accs[0].chain = 'yellowstone'; // Contract deployed on Yellowstone | ||||||
|
|
||||||
| const encryptRes = await encryptString( | ||||||
| { | ||||||
| accessControlConditions: accs, | ||||||
| dataToEncrypt, | ||||||
| }, | ||||||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||||||
| ); | ||||||
|
|
||||||
| // log('encryptRes:', encryptRes); | ||||||
|
|
||||||
| if (!encryptRes.ciphertext) { | ||||||
| throw new Error(`Expected "ciphertext" in encryptRes`); | ||||||
| } | ||||||
|
|
||||||
| if (!encryptRes.dataToEncryptHash) { | ||||||
| throw new Error(`Expected "dataToEncryptHash" to in encryptRes`); | ||||||
| } | ||||||
|
|
||||||
| // Craft the SiweMessage to be hashed & signed | ||||||
| const siweMessage = await createSiweMessage<BaseSiweMessage>({ | ||||||
| nonce: await devEnv.litNodeClient.getLatestBlockhash(), | ||||||
| walletAddress: alice.wallet.address, | ||||||
| }); | ||||||
|
|
||||||
| // Explicitly SHA256 hash the SIWE message | ||||||
| const hash = createHash('sha256').update(siweMessage).digest(); | ||||||
| const hashHex = '0x' + hash.toString('hex'); | ||||||
| const hashUint8Array = ethers.utils.arrayify(hashHex); | ||||||
| log('hash:', hashHex); | ||||||
| log('hashUint8Array: ', hashUint8Array); // Match it against the hash done on the nodes before calling verifySignature() | ||||||
|
|
||||||
| const siweSignature = joinSignature( | ||||||
| alice.wallet._signingKey().signDigest(hashHex) | ||||||
| ); | ||||||
| log('siweSignature: ', siweSignature); | ||||||
|
|
||||||
| const eip1271AuthSig: AuthSig = { | ||||||
| address: contractAddress, | ||||||
| sig: siweSignature, | ||||||
| derivedVia: 'EIP1271_SHA256', | ||||||
| signedMessage: siweMessage, | ||||||
| }; | ||||||
|
|
||||||
| // log(eip1271AuthSig); | ||||||
|
|
||||||
| // Test from the contract | ||||||
| const contract = new ethers.Contract(contractAddress, abi, alice.wallet); | ||||||
| const setDeployerAsTempOwnerTx = await contract.setTempOwner(deployerAddress); | ||||||
| await setDeployerAsTempOwnerTx.wait(); | ||||||
|
|
||||||
| log( | ||||||
| '0. isValidSignature should FAIL since Alice (AuthSig.sig) is not the tempOwner yet' | ||||||
| ); | ||||||
| try { | ||||||
| const isValid = await contract.isValidSignature(hash, siweSignature); | ||||||
| if (isValid === '0x1626ba7e') { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Same as above |
||||||
| throw new Error( | ||||||
| `Expected isValidSignature to be 0xffffffff but got ${isValid}` | ||||||
| ); | ||||||
| } | ||||||
| } catch (error) { | ||||||
| log('Error calling isValidSignature:', error); | ||||||
| throw error; | ||||||
| } | ||||||
|
|
||||||
| try { | ||||||
| const _decryptRes = await decryptToString( | ||||||
| { | ||||||
| accessControlConditions: accs, | ||||||
| ciphertext: encryptRes.ciphertext, | ||||||
| dataToEncryptHash: encryptRes.dataToEncryptHash, | ||||||
| authSig: eip1271AuthSig, | ||||||
| chain: 'yellowstone', // Deployed chain | ||||||
| }, | ||||||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||||||
| ); | ||||||
| } catch (error) { | ||||||
| if ( | ||||||
| !error.message.includes('NodeContractAuthsigUnauthorized') || | ||||||
| !error.message.includes('Access control failed for Smart contract') || | ||||||
| !error.message.includes( | ||||||
| 'validation error: Authsig failed for contract 0x88105De2349f59767278Fd15c0858f806c08d615. Return value was ffffffff.' | ||||||
| ) | ||||||
| ) { | ||||||
| throw new Error( | ||||||
| `Expected error message to contain specific EIP1271 validation failure, but got: ${error}` | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Should PASS now | ||||||
| log('1. Setting temp owner...'); | ||||||
| const setTempOwnerTx = await contract.setTempOwner(alice.wallet.address); | ||||||
| await setTempOwnerTx.wait(); | ||||||
| log('Set tempOwner transaction hash: ', setTempOwnerTx.hash); | ||||||
|
|
||||||
| const tempOwner = await contract.getTempOwner(); | ||||||
| if (tempOwner.toLowerCase() !== alice.wallet.address.toLowerCase()) { | ||||||
| throw new Error( | ||||||
| `Expected temp owner to be ${alice.wallet.address} but got ${tempOwner}` | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| log('2. Checking isValidSignature...'); | ||||||
| try { | ||||||
| const isValid = await contract.isValidSignature(hash, siweSignature); | ||||||
| if (isValid !== '0x1626ba7e') { | ||||||
| throw new Error( | ||||||
| `Expected isValidSignature to be 0x1626ba7e but got ${isValid}` | ||||||
| ); | ||||||
| } | ||||||
| } catch (error) { | ||||||
| log('Error calling isValidSignature:', error); | ||||||
| throw error; | ||||||
| } | ||||||
|
|
||||||
| // -- Decrypt the encrypted string | ||||||
| const decryptRes = await decryptToString( | ||||||
| { | ||||||
| accessControlConditions: accs, | ||||||
| ciphertext: encryptRes.ciphertext, | ||||||
| dataToEncryptHash: encryptRes.dataToEncryptHash, | ||||||
| authSig: eip1271AuthSig, | ||||||
| chain: 'yellowstone', // Deployed chain | ||||||
| }, | ||||||
| devEnv.litNodeClient as unknown as ILitNodeClient | ||||||
| ); | ||||||
|
|
||||||
| if (decryptRes !== dataToEncrypt) { | ||||||
| throw new Error( | ||||||
| `Expected decryptRes to be ${dataToEncrypt} but got ${decryptRes}` | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| log('✅ decryptRes:', decryptRes); | ||||||
| }; | ||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message indicates this is the correct condition. However, as EIP-1271 only strictly defines the magic value for a valid signature, I would say the check should always have to be over that one. I think the real correction is to change the error message to something like
Expected isValidSignature to not be 0x1626ba7e (valid signature)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I think the whole point of comparing the exact value is that it is very specific
0x1626ba7e, and its use follows an unofficial yet widely accepted convention for implementing EIP-1271. Removing it would reduce the specificity and clarity for this "standard"