UNPKG

soda-sdk

Version:

This SDK provides functionalities for AES and RSA encryption schemes, ECDSA signature scheme and some functionalities used for working with sodalabs blockchain.

347 lines (346 loc) 17.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = require("./crypto"); const fs_1 = __importDefault(require("fs")); const crypto_2 = __importDefault(require("crypto")); const utills_1 = require("./utills"); const ethereumjs_util_1 = require("ethereumjs-util"); const ethers_1 = require("ethers"); const assert = __importStar(require("node:assert")); function extractSignatureComponents(signatureBytes) { // Allocate buffers for r, s, and v let rBytes = Buffer.alloc(32); let sBytes = Buffer.alloc(32); let vByte = Buffer.alloc(1); // Copy the corresponding bytes from the signature signatureBytes.copy(rBytes, 0, 0, 32); signatureBytes.copy(sBytes, 0, 32, 64); signatureBytes.copy(vByte, 0, 64); // Return the components as an object return { rBytes, sBytes, vByte }; } function uint8ArrayToBigInt(uint8Array) { let value = BigInt(0); for (let i = 0; i < uint8Array.length; i++) { value = (value << 8n) | BigInt(uint8Array[i]); } return value; } describe('Crypto Tests', () => { // Test case for encrypt and decrypt it('should encrypt and decrypt successfully', () => { // Arrange const plaintextInteger = 100; const plaintextBuffer = Buffer.alloc(1); plaintextBuffer.writeUInt8(plaintextInteger); // Act const key = (0, crypto_1.generateAesKey)(); const { ciphertext, r } = (0, crypto_1.encrypt)(key, plaintextBuffer); const decryptedBuffer = (0, crypto_1.decrypt)(key, r, ciphertext); // Write Buffer to file to later check in Go fs_1.default.writeFileSync("test_tsEncryption.txt", key.toString('hex') + "\n" + ciphertext.toString('hex') + "\n" + r.toString('hex')); const decryptedInteger = uint8ArrayToBigInt(decryptedBuffer); // Assert assert.strictEqual(decryptedInteger, BigInt(plaintextInteger)); }); // Test case for load and write AES key it('should load and write AES key successfully', () => { // Arrange const key = (0, crypto_1.generateAesKey)(); // Act (0, utills_1.writeAesKey)('key.txt', key); const loadedKey = (0, utills_1.loadAesKey)('key.txt'); // Assert assert.deepStrictEqual(loadedKey, key); // Delete the key file fs_1.default.unlink('key.txt', (err) => { if (err) { console.error('Error deleting file:', err); } }); }); // Test case for invalid plaintext size it('should throw error for invalid plaintext size', () => { // Arrange const key = (0, crypto_1.generateAesKey)(); const plaintextBuffer = Buffer.alloc(20); // Bigger than 128 bits // Act and Assert assert.throws(() => (0, crypto_1.encrypt)(key, plaintextBuffer), RangeError); }); // Test case for invalid ciphertext size it('should throw error for invalid ciphertext size', () => { // Arrange const key = (0, crypto_1.generateAesKey)(); const ciphertext = Buffer.from([0x01, 0x02, 0x03]); // Smaller than 128 bits const r = Buffer.alloc(crypto_1.BLOCK_SIZE); // Act and Assert assert.throws(() => (0, crypto_1.decrypt)(key, r, ciphertext), RangeError); }); // Test case for invalid random size it('should throw error for invalid random size', () => { // Arrange const key = (0, crypto_1.generateAesKey)(); const r = Buffer.from([0x01, 0x02, 0x03]); // Smaller than 128 bits const ciphertext = Buffer.alloc(crypto_1.BLOCK_SIZE); // Act and Assert assert.throws(() => (0, crypto_1.decrypt)(key, r, ciphertext), RangeError); }); // Test case for invalid key size it('should throw error for invalid key size', () => { // Arrange const key = Buffer.from([0x01, 0x02, 0x03]); // Smaller than 128 bits // Act and Assert // Test invalid key size when writing key assert.throws(() => (0, utills_1.writeAesKey)('key.txt', key), RangeError); // Test invalid key size when encrypting const plaintextBuffer = Buffer.alloc(crypto_1.BLOCK_SIZE); assert.throws(() => (0, crypto_1.encrypt)(key, plaintextBuffer), RangeError); // Test invalid key size when decrypting const ciphertext = Buffer.alloc(crypto_1.BLOCK_SIZE); const r = Buffer.alloc(crypto_1.BLOCK_SIZE); assert.throws(() => (0, crypto_1.decrypt)(key, r, ciphertext), RangeError); }); // Test case for verify signature it('should sign and verify the signature', () => { // Arrange // Simulate the generation of random bytes const sender = crypto_2.default.randomBytes(crypto_1.ADDRESS_SIZE); const addr = crypto_2.default.randomBytes(crypto_1.ADDRESS_SIZE); const funcSig = crypto_2.default.randomBytes(crypto_1.FUNC_SIG_SIZE); let key = (0, crypto_1.generateECDSAPrivateKey)(); // Create a ciphertext const plaintextBuffer = Buffer.alloc(1); plaintextBuffer.writeUInt8(100); const aeskey = (0, crypto_1.generateAesKey)(); const { ciphertext, r } = (0, crypto_1.encrypt)(aeskey, plaintextBuffer); let ct = Buffer.concat([ciphertext, r]); // Act // Generate the signature const signatureBytes = (0, crypto_1.signIT)(sender, addr, funcSig, ct, key); const { rBytes, sBytes, vByte } = extractSignatureComponents(signatureBytes); // Convert v buffer back to integer let v = vByte.readUInt8(); // JS expects v to be 27 or 28. But in Ethereum, v is either 0 or 1. // In the sign function, 27 is subtracted from v in order to make it work with ethereum. // Now 27 should be added back to v to make it work with JS veification. if (v !== 27 && v !== 28) { v += 27; } // Verify the signature const expectedPublicKey = (0, ethereumjs_util_1.privateToPublic)(key); const expectedAddress = (0, ethereumjs_util_1.toChecksumAddress)('0x' + expectedPublicKey.toString('hex')); const message = Buffer.concat([sender, addr, funcSig, ct]); const hash = (0, ethereumjs_util_1.keccak256)(message); // Recover the public key from the signature const publicKey = (0, ethereumjs_util_1.ecrecover)(hash, v, rBytes, sBytes); // Derive the Ethereum address from the recovered public key const address = (0, ethereumjs_util_1.toChecksumAddress)('0x' + publicKey.toString('hex')); // Compare the derived address with the expected signer's address const isVerified = address === expectedAddress; // Assert assert.strictEqual(isVerified, true); }); // Test case for verify signature it('should sign and verify the EIP191 signature', () => { // Arrange // Simulate the generation of random bytes const sender = crypto_2.default.randomBytes(crypto_1.ADDRESS_SIZE); const addr = crypto_2.default.randomBytes(crypto_1.ADDRESS_SIZE); const funcSig = crypto_2.default.randomBytes(crypto_1.FUNC_SIG_SIZE); let key = (0, crypto_1.generateECDSAPrivateKey)(); // Create a ciphertext const plaintextBuffer = Buffer.alloc(1); plaintextBuffer.writeUInt8(100); const aeskey = (0, crypto_1.generateAesKey)(); const { ciphertext, r } = (0, crypto_1.encrypt)(aeskey, plaintextBuffer); let ct = Buffer.concat([ciphertext, r]); // Act // Generate the signature const signatureBytes = (0, crypto_1.signIT)(sender, addr, funcSig, ct, key, true); const { rBytes, sBytes, vByte } = extractSignatureComponents(signatureBytes); // Verify the signature const expectedPublicKey = (0, ethereumjs_util_1.privateToPublic)(key); const expectedAddress = (0, ethereumjs_util_1.toChecksumAddress)('0x' + expectedPublicKey.toString('hex')); const message = Buffer.concat([sender, addr, funcSig, ct]); const hash = (0, ethereumjs_util_1.hashPersonalMessage)(message); // Recover the public key from the signature const publicKey = (0, ethereumjs_util_1.ecrecover)(hash, vByte, rBytes, sBytes); // Derive the Ethereum address from the recovered public key const address = (0, ethereumjs_util_1.toChecksumAddress)('0x' + publicKey.toString('hex')); // Compare the derived address with the expected signer's address const isVerified = address === expectedAddress; // Assert assert.strictEqual(isVerified, true); }); // Test case for verify signature it('should sign a fixed message and write the signature to a file', () => { // Arrange // Simulate the generation of random bytes const sender = Buffer.from('d67fe7792f18fbd663e29818334a050240887c28', 'hex'); const addr = Buffer.from('69413851f025306dbe12c48ff2225016fc5bbe1b', 'hex'); const funcSig = Buffer.from('dc85563d', 'hex'); const ct = Buffer.from('f8765e191e03bf341c1422e0899d092674fc73beb624845199cd6e14b7895882', 'hex'); const key = Buffer.from('3840f44be5805af188e9b42dda56eb99eefc88d7a6db751017ff16d0c5f8143e', 'hex'); // Act // Generate the signature const signature = (0, crypto_1.signIT)(sender, addr, funcSig, ct, key); const filename = 'test_tsSignature.txt'; // Name of the file to write to // Convert hexadecimal string to buffer let sigString = signature.toString('hex'); // Write buffer to the file, this simulates the communication between the evm (golang) and the user (python/js) fs_1.default.writeFile(filename, sigString, (err) => { if (err) { console.error('Error writing to file:', err); return; } }); }); it('should prepareMessage using fixed data', async () => { // Arrange // Simulate the generation of random bytes const plaintext = BigInt("100"); const userKey = 'b3c3fe73c1bb91862b166a29fe1d63e9'; const senderAddress = '0x8f01160c98e5cdfa625197849c85cf5fc1f76b1b'; const contractAddress = '0x69413851f025306dbe12c48ff2225016fc5bbe1b'; const funcSig = 'test(bytes)'; const signingKey = '0x3840f44be5805af188e9b42dda56eb99eefc88d7a6db751017ff16d0c5f8143e'; // Act // Generate the signature const functionSelector = (0, crypto_1.getFuncSig)(funcSig); const { message } = (0, crypto_1.prepareMessage)(plaintext, senderAddress, userKey, contractAddress, '0x' + functionSelector.toString('hex')); const wallet = new ethers_1.ethers.Wallet(signingKey); const signature = await wallet.signMessage(message); const recoveredAddress = ethers_1.ethers.verifyMessage(message, signature); assert.strictEqual(recoveredAddress.toLowerCase(), senderAddress.toLowerCase()); }); // Test case for verify signature it('should prepare IT using fixed data', () => { // Arrange // Simulate the generation of random bytes const plaintext = BigInt("100"); const userKey = Buffer.from('b3c3fe73c1bb91862b166a29fe1d63e9', 'hex'); ; const sender = new ethereumjs_util_1.Address((0, ethereumjs_util_1.toBuffer)(Buffer.from('d67fe7792f18fbd663e29818334a050240887c28', 'hex'))); const contract = new ethereumjs_util_1.Address((0, ethereumjs_util_1.toBuffer)(Buffer.from('69413851f025306dbe12c48ff2225016fc5bbe1b', 'hex'))); const funcSig = 'test(bytes)'; const signingKey = Buffer.from('3840f44be5805af188e9b42dda56eb99eefc88d7a6db751017ff16d0c5f8143e', 'hex'); // Act // Generate the signature const hash_func = (0, crypto_1.getFuncSig)(funcSig); const { ctInt, signature } = (0, crypto_1.prepareIT)(plaintext, userKey, sender.toBuffer(), contract.toBuffer(), hash_func, signingKey); const ctHex = ctInt.toString(crypto_1.HEX_BASE); // Create a Buffer to hold the bytes const ctBuffer = Buffer.from(ctHex, 'hex'); // Write Buffer to file to later check in Go fs_1.default.writeFileSync("test_tsIT.txt", ctHex + "\n" + signature.toString('hex')); // Decrypt the ct and check the decrypted value is equal to the plaintext const decryptedBuffer = (0, crypto_1.decrypt)(userKey, ctBuffer.subarray(crypto_1.BLOCK_SIZE, ctBuffer.length), ctBuffer.subarray(0, crypto_1.BLOCK_SIZE)); // Convert the plaintext to bytes const hexString = plaintext.toString(16); const plaintextBytes = Buffer.from(hexString, 'hex'); // Assert const expectedBytes = decryptedBuffer.subarray(decryptedBuffer.length - plaintextBytes.length, decryptedBuffer.length); assert.deepStrictEqual(plaintextBytes.toString('hex'), Buffer.from(expectedBytes).toString('hex')); const intResult = uint8ArrayToBigInt(decryptedBuffer); assert.deepStrictEqual(plaintext, intResult); }); // Test case for test rsa encryption scheme it('should encrypt and decrypt a message using RSA scheme', () => { // Arrange const plaintext = 'hello world'; const plaintextBuffer = Buffer.from(plaintext); const { publicKey, privateKey } = (0, crypto_1.generateRSAKeyPair)(); // Act const ciphertext = (0, crypto_1.encryptRSA)(publicKey, plaintext); const hexString = privateKey.toString('hex') + "\n" + publicKey.toString('hex'); // Write buffer to the file const filename = 'test_tsRSAEncryption.txt'; // Name of the file to write to fs_1.default.writeFile(filename, hexString, (err) => { if (err) { console.error('Error writing to file:', err); return; } }); const decrypted = (0, crypto_1.decryptRSA)(privateKey, Buffer.from(ciphertext).toString('hex')); // Assert assert.deepStrictEqual(plaintextBuffer, Buffer.from(decrypted)); }); function readHexFromFile(filename) { return new Promise((resolve, reject) => { fs_1.default.readFile(filename, 'utf8', (err, data) => { if (err) { reject(err); return; } const lines = data.trim().split('\n'); if (lines.length >= 3) { const hexData1 = lines[0].trim(); const hexData2 = lines[1].trim(); const hexData3 = lines[2].trim(); resolve([hexData1, hexData2, hexData3]); } else { reject(new Error('Not enough lines in the file.')); } }); }); } // Test case for test rsa decryption scheme // skipped the same as in the js version it.skip('should decrypt a message using RSA scheme', () => { // Arrange const plaintext = Buffer.from('hello world'); // Act // Read private key and ciphertext // Reading from file simulates the communication between the evm (golang) and the user (python/js) readHexFromFile('test_tsRSAEncryption.txt') .then((value) => { const [hexData1, hexData2, hexData3] = value; const privateKey = Buffer.from(hexData1, 'hex'); const ciphertext = Buffer.from(hexData3, 'hex').toString('hex'); const decrypted = (0, crypto_1.decryptRSA)(privateKey, hexData3); // Assert assert.deepStrictEqual(plaintext, decrypted); }) .catch(error => { console.error("Error reading file:", error); }); fs_1.default.unlinkSync('test_tsRSAEncryption.txt'); }); // Test case for test function signature it('should hash a function signature', () => { // Arrange const functionSig = 'sign(bytes)'; // Act const hash = (0, crypto_1.getFuncSig)(functionSig); const filename = 'test_tsFunctionKeccak.txt'; // Name of the file to write to // Write Buffer to file fs_1.default.writeFileSync(filename, hash.toString('hex')); }); });