feat: Convert Ether1 wallet to PaperclipWallet
- Update package.json with PaperclipWallet branding
- Replace Ethereum Web3 with Tendermint RPC client
- Implement CLIPS address format (CLIP-{64-hex})
- Add Ed25519 key management with tweetnacl
- Create PaperclipChain blockchain interface
- Support gas system and transaction types
- Add smart contract and multisig support
This commit is contained in:
236
modules/clips-crypto.js
Normal file
236
modules/clips-crypto.js
Normal file
@@ -0,0 +1,236 @@
|
||||
const nacl = require('tweetnacl');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class ClipsCrypto {
|
||||
constructor() {
|
||||
this.addressPrefix = 'CLIP-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Ed25519 key pair
|
||||
* @returns {Object} Contains publicKey, privateKey, and address
|
||||
*/
|
||||
generateKeyPair() {
|
||||
const keyPair = nacl.sign.keyPair();
|
||||
|
||||
return {
|
||||
publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
|
||||
privateKey: Buffer.from(keyPair.secretKey).toString('hex'),
|
||||
address: this.publicKeyToAddress(keyPair.publicKey)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert public key to CLIPS address format
|
||||
* @param {Uint8Array|Buffer|string} publicKey - The public key
|
||||
* @returns {string} CLIPS address
|
||||
*/
|
||||
publicKeyToAddress(publicKey) {
|
||||
let pubKeyBuffer;
|
||||
|
||||
if (typeof publicKey === 'string') {
|
||||
pubKeyBuffer = Buffer.from(publicKey, 'hex');
|
||||
} else if (publicKey instanceof Uint8Array) {
|
||||
pubKeyBuffer = Buffer.from(publicKey);
|
||||
} else {
|
||||
pubKeyBuffer = publicKey;
|
||||
}
|
||||
|
||||
return this.addressPrefix + pubKeyBuffer.toString('hex').toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate CLIPS address format
|
||||
* @param {string} address - The address to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
isValidAddress(address) {
|
||||
if (!address || typeof address !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check prefix
|
||||
if (!address.startsWith(this.addressPrefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check hex part (should be 64 characters after prefix)
|
||||
const hexPart = address.slice(this.addressPrefix.length);
|
||||
if (hexPart.length !== 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if hex is valid
|
||||
return /^[0-9A-Fa-f]+$/.test(hexPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a transaction
|
||||
* @param {string} privateKeyHex - The private key in hex format
|
||||
* @param {Object} transaction - Transaction data
|
||||
* @returns {string} Signature in hex format
|
||||
*/
|
||||
signTransaction(privateKeyHex, transaction) {
|
||||
const privateKey = Buffer.from(privateKeyHex, 'hex');
|
||||
|
||||
// Create transaction message to sign
|
||||
const txMessage = this.createTransactionMessage(transaction);
|
||||
const messageBuffer = Buffer.from(txMessage, 'utf8');
|
||||
|
||||
// Sign the message
|
||||
const signature = nacl.sign.detached(messageBuffer, privateKey);
|
||||
|
||||
return Buffer.from(signature).toString('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transaction message for signing
|
||||
* @param {Object} tx - Transaction object
|
||||
* @returns {string} Message to sign
|
||||
*/
|
||||
createTransactionMessage(tx) {
|
||||
// Format: sender:receiver:amount:nonce:type:data
|
||||
const parts = [
|
||||
tx.sender || '',
|
||||
tx.receiver || '',
|
||||
tx.amount || '0',
|
||||
tx.nonce || '0',
|
||||
tx.type || 'transfer',
|
||||
tx.data || ''
|
||||
];
|
||||
|
||||
return parts.join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a complete transaction object
|
||||
* @param {Object} params - Transaction parameters
|
||||
* @returns {Object} Complete transaction object
|
||||
*/
|
||||
createTransaction(params) {
|
||||
const {
|
||||
sender,
|
||||
receiver,
|
||||
amount,
|
||||
nonce,
|
||||
type = 'transfer',
|
||||
data = '',
|
||||
privateKey
|
||||
} = params;
|
||||
|
||||
const transaction = {
|
||||
sender,
|
||||
receiver,
|
||||
amount: parseInt(amount),
|
||||
nonce: parseInt(nonce),
|
||||
type,
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Sign the transaction
|
||||
if (privateKey) {
|
||||
transaction.signature = this.signTransaction(privateKey, transaction);
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a transaction signature
|
||||
* @param {Object} transaction - The transaction object
|
||||
* @param {string} publicKeyHex - The public key to verify against
|
||||
* @returns {boolean} True if signature is valid
|
||||
*/
|
||||
verifyTransaction(transaction, publicKeyHex) {
|
||||
if (!transaction.signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const publicKey = Buffer.from(publicKeyHex, 'hex');
|
||||
const signature = Buffer.from(transaction.signature, 'hex');
|
||||
|
||||
// Recreate the message that was signed
|
||||
const txMessage = this.createTransactionMessage(transaction);
|
||||
const messageBuffer = Buffer.from(txMessage, 'utf8');
|
||||
|
||||
return nacl.sign.detached.verify(messageBuffer, signature, publicKey);
|
||||
} catch (error) {
|
||||
console.error('Error verifying transaction:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert transaction to hex string for broadcasting
|
||||
* @param {Object} transaction - The transaction object
|
||||
* @returns {string} Transaction as hex string
|
||||
*/
|
||||
transactionToHex(transaction) {
|
||||
const txString = JSON.stringify(transaction);
|
||||
return Buffer.from(txString, 'utf8').toString('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex string back to transaction object
|
||||
* @param {string} txHex - Transaction in hex format
|
||||
* @returns {Object} Transaction object
|
||||
*/
|
||||
hexToTransaction(txHex) {
|
||||
const txString = Buffer.from(txHex, 'hex').toString('utf8');
|
||||
return JSON.parse(txString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import private key and return key pair info
|
||||
* @param {string} privateKeyHex - Private key in hex format
|
||||
* @returns {Object} Key pair information
|
||||
*/
|
||||
importPrivateKey(privateKeyHex) {
|
||||
try {
|
||||
const privateKey = Buffer.from(privateKeyHex, 'hex');
|
||||
|
||||
// Generate public key from private key
|
||||
const keyPair = nacl.sign.keyPair.fromSecretKey(privateKey);
|
||||
|
||||
return {
|
||||
publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
|
||||
privateKey: privateKeyHex,
|
||||
address: this.publicKeyToAddress(keyPair.publicKey)
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error('Invalid private key format');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random seed phrase (12 words)
|
||||
* @returns {string} Seed phrase
|
||||
*/
|
||||
generateSeedPhrase() {
|
||||
// Simple word list for demo purposes
|
||||
const wordList = [
|
||||
'abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract',
|
||||
'absurd', 'abuse', 'access', 'accident', 'account', 'accuse', 'achieve', 'acid',
|
||||
'acoustic', 'acquire', 'across', 'act', 'action', 'actor', 'actress', 'actual',
|
||||
'adapt', 'add', 'addict', 'address', 'adjust', 'admit', 'adult', 'advance',
|
||||
'advice', 'aerobic', 'affair', 'afford', 'afraid', 'again', 'against', 'agent',
|
||||
'agree', 'ahead', 'aim', 'air', 'airport', 'aisle', 'alarm', 'album',
|
||||
'alcohol', 'alert', 'alien', 'all', 'alley', 'allow', 'almost', 'alone',
|
||||
'alpha', 'already', 'also', 'alter', 'always', 'amateur', 'amazing', 'among',
|
||||
'amount', 'amused', 'analyst', 'anchor', 'ancient', 'anger', 'angle', 'angry',
|
||||
'animal', 'ankle', 'announce', 'annual', 'another', 'answer', 'antenna', 'antique'
|
||||
];
|
||||
|
||||
const words = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const randomIndex = crypto.randomInt(0, wordList.length);
|
||||
words.push(wordList[randomIndex]);
|
||||
}
|
||||
|
||||
return words.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ClipsCrypto();
|
||||
Reference in New Issue
Block a user