edge / wallet

The web wallet of the XE Blockchain.

Home Page:https://wallet.xe.network

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Migrate Node crypto to npm crypto-js

annybs opened this issue · comments

commented

PR #253 (migrating the build tooling to Vite) is currently blocked by issues packaging Node's crypto and buffer modules - which should not really be part of the app. I have been working on migrating the usage of crypto to crypto-js instead.

After spending another few hours today trying to find a way through this, I have not made much progress. I have put together a quick test script to demonstrate the problem, as follows:

const CryptoJS = require('crypto-js')
const crypto = require('crypto')
const xeUtils = require('@edge/xe-utils')

// encrypt using Node crypto
// string, string -> string, string
function encrypt(data, secret) {
  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv('aes-256-ctr', secret, iv)
  const buf = Buffer.concat([cipher.update(data), cipher.final()])
  return [buf.toString('hex'), iv.toString('hex')]
}

// decrypt using npm crypto-js
// string, string, string -> string
function decrypt(encData, iv, secret) {
  const waSecret = CryptoJS.enc.Utf8.parse(secret)
  const waIV = CryptoJS.enc.Hex.parse(iv)
  const data = CryptoJS.AES.decrypt(encData, waSecret, { iv: waIV })
  return CryptoJS.enc.Utf8.stringify(data)
}

const secret = 'my fantastic password'.padEnd(32, '0')
const { privateKey } = xeUtils.wallet.create()

const [encData, iv] = encrypt(privateKey, secret)

const data = decrypt(encData, iv, secret)

console.log('Private key:  ', privateKey)
console.log('Secret:       ', secret)
console.log('IV:           ', iv)
console.log('Encrypted key:', encData)
console.log('Decrypted key:', data)
console.log('Result:       ', data === privateKey ? 'PASS' : 'FAIL')

What we need that script to do is to encrypt the privateKey using Node crypto, and then decrypt it using crypto-js. The encryption method cannot be changed - only the decryption method. When the key can be successfully encrypted and then decrypted back, and the output matches the input, then we can move forward completing the migration work.

After extensive searching I have found that this issue has been encountered by others but there is no conclusive resolution there either.

I am unable to continue to spend more time on this problem right now, so I am offering a BUG BOUNTY of 250 XE to the first Edge community member who can demonstrate a working script based on the script above.

commented

@g45t345rt has offered up the following script on Discord, which uses the native Web Crypto API rather than crypto-js. I'll R&D this approach as soon as I have some time.

const CryptoJS = require('crypto-js')
const crypto = require('crypto')
const xeUtils = require('@edge/xe-utils')

const webcrypto = crypto.webcrypto

// encrypt using Node crypto
// string, string -> string, string
function encrypt(data, secret) {
  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv('aes-256-ctr', secret, iv)
  const buf = Buffer.concat([cipher.update(data), cipher.final()])
  return [buf.toString('hex'), iv.toString('hex')]
}

// decrypt using npm crypto-js
// string, string, string -> string
function decrypt(encData, iv, secret) {
  const waSecret = CryptoJS.enc.Utf8.parse(secret)
  const waIV = CryptoJS.enc.Hex.parse(iv)
  const data = CryptoJS.AES.decrypt(encData, waSecret, { iv: waIV })
  return CryptoJS.enc.Utf8.stringify(data)
}

async function decrypt2(encData, iv, secret) {
  const key = await webcrypto.subtle.importKey('raw', secret, {
    name: 'AES-CTR',
    length: 256
  }, false, ['decrypt'])

  const data = await webcrypto.subtle.decrypt({
    name: 'AES-CTR',
    counter: Buffer.from(iv, 'hex'),
    length: 64
  },
    key,
    Buffer.from(encData, 'hex')
  )

  return new TextDecoder().decode(data)
}

async function main() {
  const secret = 'my fantastic password'.padEnd(32, '0')
  const { privateKey } = xeUtils.wallet.create()

  const [encData, iv] = encrypt(privateKey, secret)

  //const data = decrypt(encData, iv, secret)
  const data = await decrypt2(encData, iv, secret)

  console.log('Private key:  ', privateKey)
  console.log('Secret:       ', secret)
  console.log('IV:           ', iv)
  console.log('Encrypted key:', encData)
  console.log('Decrypted key:', data)
  console.log('Result:       ', data === privateKey ? 'PASS' : 'FAIL')

}

main()
commented

It looks like we may have a winner. While the above script did not work as given and used Buffer when I said I was working to remove it, it still nudged me in a helpful direction, and I was still able to map in browser-friendly equivalent functionality. We now have a dual-mode decryption function that can work with or without a salt, allowing existing Wallet users to migrate from old to new storage methods invisibly.

This is deploying to testnet as I write, and a mainnet release should follow next week after I have had more time to test it.