mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-19 11:15:49 +00:00
223 lines
7.4 KiB
JavaScript
223 lines
7.4 KiB
JavaScript
const crypto = require("crypto")
|
|
const fs = require('fs')
|
|
const fernet = require("fernet");
|
|
const open = require('open');
|
|
const path = require('path')
|
|
const prompt = require('prompt')
|
|
const { generateSeed, deriveAddress, deriveKeypair } = require("ripple-keypairs/dist/")
|
|
const QRCode = require('qrcode')
|
|
const xrpl = require('xrpl')
|
|
|
|
const demoAccountSeed = 'sskwYQmxT7SA37ceRaGXA5PhQYrDS'
|
|
const demoAccountAddress = 'rEDd3Wy76Ta1WqfDP2DcnBKHu31SpSiUQrS'
|
|
|
|
const demoDestinationSeed = 'sEdVokfq7fVXXjZTii2WhtpqGbJni6s'
|
|
const demoDestinationAddress = 'rBgNowfkmPczhMjHRYnBPsuSodDHWHQLdj'
|
|
|
|
const FEE = '12'
|
|
const LEDGER_OFFSET = 300
|
|
const WALLET_DIR = 'Wallet'
|
|
|
|
/**
|
|
* Generates a new (unfunded) wallet
|
|
*
|
|
* @returns {{address: *, seed: *}}
|
|
*/
|
|
createWallet = function () {
|
|
const seed = generateSeed()
|
|
const {publicKey, privateKey} = deriveKeypair(seed)
|
|
const address = deriveAddress(publicKey)
|
|
|
|
console.log(
|
|
"XRP Wallet Credentials " +
|
|
"Wallet Address: " + address +
|
|
"Seed: " + seed
|
|
)
|
|
|
|
return {address, seed}
|
|
}
|
|
|
|
/**
|
|
* Signs transaction and returns signed transaction blob in QR code
|
|
*
|
|
* @param xrpAmount
|
|
* @param destination
|
|
* @param ledgerSequence
|
|
* @param walletSequence
|
|
* @param password
|
|
* @returns {Promise<void>}
|
|
*/
|
|
signTransaction = async function (xrpAmount, destination, ledgerSequence, walletSequence, password) {
|
|
|
|
const salt = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt')).toString()
|
|
|
|
const encodedSeed = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt')).toString()
|
|
|
|
// Hashing salted password using Password-Based Key Derivation Function 2
|
|
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
|
|
|
|
// Generate a Fernet secret we can use for symmetric encryption
|
|
const secret = new fernet.Secret(derivedKey.toString('base64'));
|
|
|
|
// Generate decryption token
|
|
const token = new fernet.Token({
|
|
secret: secret,
|
|
token: encodedSeed,
|
|
ttl: 0
|
|
})
|
|
const seed = token.decode();
|
|
|
|
const wallet = xrpl.Wallet.fromSeed(seed)
|
|
|
|
const paymentTx = {
|
|
'TransactionType': 'Payment',
|
|
'Account': wallet.classicAddress,
|
|
'Amount': xrpl.xrpToDrops(xrpAmount),
|
|
'Destination': destination
|
|
}
|
|
|
|
// Normally we would fetch certain needed values like Fee,
|
|
// LastLedgerSequence snd programmatically, like so:
|
|
//
|
|
// const preparedTx = await client.autofill(paymentTx)
|
|
//
|
|
// But since this is an airgapped wallet without internet
|
|
// connection, we have to do it manually:
|
|
//
|
|
// paymentTx.Sequence is set in setNextValidSequenceNumber() via sugar/autofill
|
|
// paymentTx.LastLedgerSequence is set in setLatestValidatedLedgerSequence() via sugar/autofill
|
|
// paymentTx.Fee is set in getFeeXrp() via sugar/getFeeXrp
|
|
|
|
paymentTx.Sequence = walletSequence
|
|
paymentTx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
|
|
paymentTx.Fee = FEE
|
|
|
|
const signedTx = wallet.sign(paymentTx)
|
|
|
|
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'tx_blob.txt'), signedTx.tx_blob)
|
|
QRCode.toFile(path.join(__dirname, WALLET_DIR , 'tx_blob.png'), signedTx.tx_blob)
|
|
|
|
open(path.join(__dirname, WALLET_DIR , 'tx_blob.png'))
|
|
}
|
|
|
|
main = async function () {
|
|
|
|
if (!fs.existsSync(WALLET_DIR )) {
|
|
// Create Wallet directory in case it does not exist yet
|
|
fs.mkdirSync(path.join(__dirname, WALLET_DIR ));
|
|
}
|
|
|
|
if (!fs.existsSync(path.join(__dirname, WALLET_DIR , 'address.txt'))) {
|
|
// Generate a new (unfunded) Wallet
|
|
const {address, seed} = createWallet()
|
|
|
|
prompt.start();
|
|
|
|
const {password} = await prompt.get([{
|
|
name: 'password',
|
|
description: 'Creating a brand new Wallet, please enter a new password \n Enter Password:',
|
|
type: 'string',
|
|
required: true
|
|
}])
|
|
|
|
prompt.stop();
|
|
|
|
const salt = crypto.randomBytes(20).toString('hex')
|
|
|
|
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt'), salt);
|
|
|
|
// Hashing salted password using Password-Based Key Derivation Function 2
|
|
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
|
|
|
|
// Generate a Fernet secret we can use for symmetric encryption
|
|
const secret = new fernet.Secret(derivedKey.toString('base64'));
|
|
|
|
// Generate encryption token with secret, time and initialization vector
|
|
// In a real-world use case we would have current time and a random IV,
|
|
// but for demo purposes being deterministic is just fine
|
|
const token = new fernet.Token({
|
|
secret: secret,
|
|
time: Date.parse(1),
|
|
iv: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
})
|
|
|
|
const privateKey = token.encode(seed)
|
|
|
|
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt'), privateKey)
|
|
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'address.txt'), address)
|
|
QRCode.toFile(path.join(__dirname, WALLET_DIR , 'address.png'), address)
|
|
|
|
console.log(''
|
|
+ 'Finished generating an account.\n'
|
|
+ 'Wallet Address: ' + address + '\n'
|
|
+ 'Please scan the QR code on your phone and use https://test.bithomp.com/faucet/ to fund the account.\n'
|
|
+ 'After that, you\'re able to sign transactions and transmit them to Machine 2 (online machine).')
|
|
|
|
return
|
|
}
|
|
|
|
prompt.start();
|
|
|
|
console.log(''
|
|
+ '1. Transact XRP.\n'
|
|
+ '2. Generate an XRP wallet (read only)\n'
|
|
+ '3. Showcase XRP Wallet Address (QR Code)\n'
|
|
+ '4. Exit')
|
|
|
|
const {menu} = await prompt.get([{
|
|
name: 'menu',
|
|
description: 'Enter Index:',
|
|
type: 'integer',
|
|
required: true
|
|
}])
|
|
|
|
if (menu === 1) {
|
|
const {
|
|
password,
|
|
xrpAmount,
|
|
destinationAddress,
|
|
accountSequence,
|
|
ledgerSequence
|
|
} = await prompt.get([{
|
|
name: 'password',
|
|
description: 'Enter Password',
|
|
type: 'string',
|
|
required: true
|
|
}, {
|
|
name: 'xrpAmount',
|
|
description: 'Enter XRP To Send',
|
|
type: 'number',
|
|
required: true
|
|
}, {
|
|
name: 'destinationAddress',
|
|
description: 'If you just want to try it out, you can use the faucet account rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe. Enter Destination',
|
|
type: 'string',
|
|
required: true
|
|
}, {
|
|
name: 'accountSequence',
|
|
description: 'Look up the \'Next Sequence\' for the account using test.bithomp.com and enter it',
|
|
type: 'integer',
|
|
required: true
|
|
}, {
|
|
name: 'ledgerSequence',
|
|
description: 'Look up the latest ledger sequence on testnet.xrpl.org and enter it below!',
|
|
type: 'integer',
|
|
required: true
|
|
}])
|
|
|
|
await signTransaction(xrpAmount, destinationAddress, ledgerSequence, accountSequence, password)
|
|
} else if (menu === 2) {
|
|
const {address, seed} = createWallet()
|
|
console.log('Generated readonly Wallet (address: ' + address + ' seed: ' + seed + ')')
|
|
} else if (menu === 3) {
|
|
const address = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'address.txt')).toString()
|
|
console.log('Wallet Address: ' + address)
|
|
open(path.join(__dirname, WALLET_DIR , 'address.png'))
|
|
} else {
|
|
return
|
|
}
|
|
|
|
prompt.stop();
|
|
}
|
|
|
|
main() |