init
This commit is contained in:
32
scripts/crypto/crypto.test.ts
Normal file
32
scripts/crypto/crypto.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
import { keyPairFromSeed } from '@ton/crypto';
|
||||
import { encryptContent, decryptContent } from './crypto';
|
||||
import { client, getKeypair, getWallet } from '../toolchain';
|
||||
import { WalletContractV4 } from '@ton/ton';
|
||||
|
||||
describe('Crypto functions', () => {
|
||||
test('encrypt and decrypt content', async () => {
|
||||
const sender = await getKeypair();
|
||||
const recipient = keyPairFromSeed(randomBytes(32));
|
||||
|
||||
const originalContent = 'Secret message';
|
||||
const encrypted = await encryptContent(sender.secretKey, recipient.publicKey, originalContent);
|
||||
const decrypted = await decryptContent(recipient.secretKey, sender.publicKey, encrypted);
|
||||
|
||||
expect(decrypted).toBe(originalContent);
|
||||
});
|
||||
|
||||
test('decrypt with wrong key fails', async () => {
|
||||
const senderSeed = Buffer.from(randomBytes(32));
|
||||
const recipientSeed = Buffer.from(randomBytes(32));
|
||||
const wrongRecipientSeed = Buffer.from(randomBytes(32));
|
||||
const sender = keyPairFromSeed(senderSeed);
|
||||
const recipient = await getKeypair();
|
||||
const wrongRecipient = keyPairFromSeed(wrongRecipientSeed);
|
||||
|
||||
const originalContent = 'Secret message';
|
||||
const encrypted = await encryptContent(sender.secretKey, recipient.publicKey, originalContent);
|
||||
|
||||
await expect(decryptContent(wrongRecipient.secretKey, sender.publicKey, encrypted)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
168
scripts/crypto/crypto.ts
Normal file
168
scripts/crypto/crypto.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { keyPairFromSeed, keyPairFromSecretKey, sign, signVerify, KeyPair, getSecureRandomBytes } from '@ton/crypto';
|
||||
import nacl from 'tweetnacl';
|
||||
const ed2curve = require('ed2curve');
|
||||
import { client, getKeypair } from '../toolchain';
|
||||
import { Content } from '../../build/Gift/Gift_Gift';
|
||||
import { Address } from '@ton/core';
|
||||
import { WalletContractV4 } from '@ton/ton';
|
||||
|
||||
/**
|
||||
* Converts a bigint (uint256) to Buffer (32 bytes, big-endian).
|
||||
* @param bigIntValue The bigint value to convert.
|
||||
* @param byteLength The byte length (default 32 for uint256).
|
||||
* @returns Buffer The converted byte buffer.
|
||||
*/
|
||||
function bigIntToBuffer(bigIntValue: bigint, byteLength: number = 32): Buffer {
|
||||
const buffer = Buffer.alloc(byteLength);
|
||||
for (let i = 0; i < byteLength; i++) {
|
||||
buffer[byteLength - 1 - i] = Number((bigIntValue >> BigInt(i * 8)) & 0xffn);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public key of the recipient wallet.
|
||||
* @param address The recipient's TON address.
|
||||
* @returns Promise<Buffer> The recipient's public key as Buffer.
|
||||
*/
|
||||
export async function getRecipientPublicKey(address: Address): Promise<Buffer> {
|
||||
console.log(`Fetching public key for: ${address.toString()}`);
|
||||
return client.callGetMethod(address, 'get_public_key')
|
||||
.then((result) => {
|
||||
return bigIntToBuffer(result.stack.readBigNumber());
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching recipient public key:', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a Content message using sender's private key and recipient's public key.
|
||||
* @param content The Content to encrypt.
|
||||
* @param senderSecretKey The sender's secret key.
|
||||
* @param recipientPublicKey The recipient's public key.
|
||||
* @returns Promise<Uint8Array> The encrypted message data (nonce + encrypted).
|
||||
*/
|
||||
export async function encryptMessage(
|
||||
content: Content,
|
||||
senderSecretKey: Buffer,
|
||||
recipientPublicKey: Buffer,
|
||||
): Promise<Uint8Array> {
|
||||
// Serialize Content to JSON string
|
||||
const contentString = JSON.stringify(content);
|
||||
const contentBuffer = new Uint8Array(Buffer.from(contentString, 'utf-8'));
|
||||
|
||||
// Convert Ed25519 keys to Curve25519
|
||||
const senderSecretCurve = ed2curve.convertSecretKey(senderSecretKey);
|
||||
const recipientPublicCurve = ed2curve.convertPublicKey(recipientPublicKey);
|
||||
if (!senderSecretCurve || !recipientPublicCurve) {
|
||||
throw new Error('Invalid key conversion');
|
||||
}
|
||||
|
||||
// Generate a random nonce
|
||||
const nonceBuffer = await getSecureRandomBytes(24);
|
||||
const nonce = new Uint8Array(nonceBuffer);
|
||||
|
||||
// Encrypt using NaCl box
|
||||
const encrypted = nacl.box(contentBuffer, nonce, recipientPublicCurve, senderSecretCurve);
|
||||
|
||||
// Return nonce + encrypted
|
||||
const combined = new Uint8Array(nonce.length + encrypted.length);
|
||||
combined.set(nonce, 0);
|
||||
combined.set(encrypted, nonce.length);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an encrypted message using recipient's private key and sender's public key.
|
||||
* @param encryptedData The encrypted data (nonce + encrypted).
|
||||
* @param recipientSecretKey The recipient's secret key.
|
||||
* @param senderPublicKey The sender's public key.
|
||||
* @returns Promise<Content> The decrypted Content.
|
||||
*/
|
||||
export async function decryptMessage(
|
||||
encryptedData: Uint8Array,
|
||||
recipientSecretKey: Buffer,
|
||||
senderPublicKey: Buffer,
|
||||
): Promise<Content> {
|
||||
// Extract nonce (first 24 bytes)
|
||||
const nonce = encryptedData.slice(0, 24);
|
||||
const encrypted = encryptedData.slice(24);
|
||||
|
||||
// Convert Ed25519 keys to Curve25519
|
||||
const recipientSecretCurve = ed2curve.convertSecretKey(recipientSecretKey);
|
||||
const senderPublicCurve = ed2curve.convertPublicKey(senderPublicKey);
|
||||
if (!recipientSecretCurve || !senderPublicCurve) {
|
||||
throw new Error('Invalid key conversion');
|
||||
}
|
||||
|
||||
// Decrypt using NaCl box open
|
||||
const decrypted = nacl.box.open(encrypted, nonce, senderPublicCurve, recipientSecretCurve);
|
||||
if (!decrypted) {
|
||||
throw new Error('Decryption failed');
|
||||
}
|
||||
|
||||
const contentString = new Buffer(decrypted).toString('utf-8');
|
||||
const content: Content = JSON.parse(contentString);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string content using sender's private key and recipient's public key.
|
||||
* @param senderSecretKey The sender's secret key.
|
||||
* @param recipientPublicKey The recipient's public key.
|
||||
* @param content The string to encrypt.
|
||||
* @returns Promise<Uint8Array> The encrypted data.
|
||||
*/
|
||||
export async function encryptContent(
|
||||
senderSecretKey: Buffer,
|
||||
recipientPublicKey: Buffer,
|
||||
content: string,
|
||||
): Promise<Uint8Array> {
|
||||
const contentBuffer = new Uint8Array(Buffer.from(content, 'utf-8'));
|
||||
|
||||
// Convert Ed25519 keys to Curve25519
|
||||
const senderSecretCurve = ed2curve.convertSecretKey(senderSecretKey);
|
||||
const recipientPublicCurve = ed2curve.convertPublicKey(recipientPublicKey);
|
||||
if (!senderSecretCurve || !recipientPublicCurve) {
|
||||
throw new Error('Invalid key conversion');
|
||||
}
|
||||
|
||||
const nonceBuffer = await getSecureRandomBytes(24);
|
||||
const nonce = new Uint8Array(nonceBuffer);
|
||||
const encrypted = nacl.box(contentBuffer, nonce, recipientPublicCurve, senderSecretCurve);
|
||||
const combined = new Uint8Array(nonce.length + encrypted.length);
|
||||
combined.set(nonce, 0);
|
||||
combined.set(encrypted, nonce.length);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt encrypted content using recipient's private key and sender's public key.
|
||||
* @param recipientSecretKey The recipient's secret key.
|
||||
* @param senderPublicKey The sender's public key.
|
||||
* @param encrypted The encrypted data.
|
||||
* @returns Promise<string> The decrypted string.
|
||||
*/
|
||||
export async function decryptContent(
|
||||
recipientSecretKey: Buffer,
|
||||
senderPublicKey: Buffer,
|
||||
encrypted: Uint8Array,
|
||||
): Promise<string> {
|
||||
// Convert Ed25519 keys to Curve25519
|
||||
const recipientSecretCurve = ed2curve.convertSecretKey(recipientSecretKey);
|
||||
const senderPublicCurve = ed2curve.convertPublicKey(senderPublicKey);
|
||||
if (!recipientSecretCurve || !senderPublicCurve) {
|
||||
throw new Error('Invalid key conversion');
|
||||
}
|
||||
|
||||
const nonce = encrypted.slice(0, 24);
|
||||
const encryptedPart = encrypted.slice(24);
|
||||
const decrypted = nacl.box.open(encryptedPart, nonce, senderPublicCurve, recipientSecretCurve);
|
||||
if (!decrypted) {
|
||||
throw new Error('Decryption failed');
|
||||
}
|
||||
return new Buffer(decrypted).toString('utf-8');
|
||||
}
|
||||
Reference in New Issue
Block a user