Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions local-tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { testUseInvalidLitActionIpfsCodeToGenerateSessionSigs } from './tests/te
import { testSolAuthSigToEncryptDecryptString } from './tests/testSolAuthSigToEncryptDecryptString';
import { testEthAuthSigToEncryptDecryptString } from './tests/testEthAuthSigToEncryptDecryptString';
import { testCosmosAuthSigToEncryptDecryptString } from './tests/testCosmosAuthSigToEncryptDecryptString';
import { testKeccakEip1271AuthSigToEncryptDecryptString } from './tests/testKeccakEip1271AuthSigToEncryptDecryptString';
import { testShaEip1271AuthSigToEncryptDecryptString } from './tests/testShaEip1271AuthSigToEncryptDecryptString';
import { testPkpEthersWithEoaSessionSigsToSignMessage } from './tests/testPkpEthersWithEoaSessionSigsToSignMessage';
import { testPkpEthersWithEoaSessionSigsToSignWithAuthContext } from './tests/testPkpEthersWithEoaSessionSigsToSignWithAuthContext';
import { testPkpEthersWithEoaSessionSigsToEthSign } from './tests/testPkpEthersWithEoaSessionSigsToEthSign';
Expand Down Expand Up @@ -232,6 +234,11 @@ setLitActionsCodeToLocal();
testCosmosAuthSigToEncryptDecryptString,
};

const eip1271AuthSigTests = {
testKeccakEip1271AuthSigToEncryptDecryptString,
testShaEip1271AuthSigToEncryptDecryptString,
};

const pkpEthersTest = {
eoaSessionSigs: {
testPkpEthersWithEoaSessionSigsToSignWithAuthContext,
Expand Down Expand Up @@ -294,6 +301,7 @@ setLitActionsCodeToLocal();
...litActionIpfsIdSessionSigsTests,
...capacityDelegationTests,
...bareAuthSigTests,
...eip1271AuthSigTests,

...pkpEthersTest.eoaSessionSigs,
...pkpEthersTest.pkpSessionSigs,
Expand Down
174 changes: 174 additions & 0 deletions local-tests/tests/testKeccakEip1271AuthSigToEncryptDecryptString.ts
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') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isValid === '0x1626ba7e') {
if (isValid !== '0xffffffff') {

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)

Copy link
Collaborator

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"

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 local-tests/tests/testShaEip1271AuthSigToEncryptDecryptString.ts
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') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isValid === '0x1626ba7e') {
if (isValid !== '0xffffffff') {

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);
};
Loading