From 01c6c37be75525a3451d0e0f40d7391065075e04 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 20:58:25 +0100 Subject: [PATCH 01/12] misc: copy pasta from bip38 package --- packages/crypto/package.json | 2 - packages/crypto/src/crypto/bip38.ts | 263 +++++++++++++++++++++++++ packages/crypto/src/crypto/index.ts | 3 +- packages/crypto/src/models/delegate.ts | 2 +- 4 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 packages/crypto/src/crypto/bip38.ts diff --git a/packages/crypto/package.json b/packages/crypto/package.json index a8382520c7..cd9f694bd9 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -36,7 +36,6 @@ }, "dependencies": { "@types/bip32": "^1.0.0", - "@types/bip38": "^2.0.0", "@types/bip39": "^2.4.1", "@types/bytebuffer": "^5.0.37", "@types/create-hash": "^1.2.0", @@ -53,7 +52,6 @@ "@types/wif": "^2.0.1", "bignumber.js": "^8.0.1", "bip32": "^1.0.2", - "bip38": "^2.0.2", "bip39": "^2.5.0", "bs58check": "^2.1.2", "bytebuffer": "^5.0.1", diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts new file mode 100644 index 0000000000..e38bb9d462 --- /dev/null +++ b/packages/crypto/src/crypto/bip38.ts @@ -0,0 +1,263 @@ +// tslint:disable:no-var-requires no-bitwise variable-name + +const aes = require("browserify-aes"); +const assert = require("assert"); +const bs58check = require("bs58check"); +const createHash = require("create-hash"); +const scrypt = require("scryptsy"); +const xor = require("buffer-xor/inplace"); + +const ecurve = require("ecurve"); +const curve = ecurve.getCurveByName("secp256k1"); + +const BigInteger = require("bigi"); + +// constants +const SCRYPT_PARAMS = { + N: 16384, // specified by BIP38 + r: 8, + p: 8, +}; +const NULL = Buffer.alloc(0); + +function hash160(buffer) { + return createHash("rmd160") + .update( + createHash("sha256") + .update(buffer) + .digest(), + ) + .digest(); +} + +function hash256(buffer) { + return createHash("sha256") + .update( + createHash("sha256") + .update(buffer) + .digest(), + ) + .digest(); +} + +function getAddress(d, compressed) { + const Q = curve.G.multiply(d).getEncoded(compressed); + const hash = hash160(Q); + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(0x00, 0); // XXX TODO FIXME bitcoin only??? damn you BIP38 + hash.copy(payload, 1); + + return bs58check.encode(payload); +} + +function encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams) { + if (buffer.length !== 32) { + throw new Error("Invalid private key length"); + } + scryptParams = scryptParams || SCRYPT_PARAMS; + + const d = BigInteger.fromBuffer(buffer); + const address = getAddress(d, compressed); + const secret = Buffer.from(passphrase, "utf8"); + const salt = hash256(address).slice(0, 4); + + const N = scryptParams.N; + const r = scryptParams.r; + const p = scryptParams.p; + + const scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback); + const derivedHalf1 = scryptBuf.slice(0, 32); + const derivedHalf2 = scryptBuf.slice(32, 64); + + const xorBuf = xor(derivedHalf1, buffer); + const cipher = aes.createCipheriv("aes-256-ecb", derivedHalf2, NULL); + cipher.setAutoPadding(false); + cipher.end(xorBuf); + + const cipherText = cipher.read(); + + // 0x01 | 0x42 | flagByte | salt (4) | cipherText (32) + const result = Buffer.allocUnsafe(7 + 32); + result.writeUInt8(0x01, 0); + result.writeUInt8(0x42, 1); + result.writeUInt8(compressed ? 0xe0 : 0xc0, 2); + salt.copy(result, 3); + cipherText.copy(result, 7); + + return result; +} + +function encrypt(buffer, compressed, passphrase, progressCallback?, scryptParams?) { + return bs58check.encode(encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams)); +} + +// some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org +function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { + // 39 bytes: 2 bytes prefix, 37 bytes payload + if (buffer.length !== 39) { + throw new Error("Invalid BIP38 data length"); + } + if (buffer.readUInt8(0) !== 0x01) { + throw new Error("Invalid BIP38 prefix"); + } + scryptParams = scryptParams || SCRYPT_PARAMS; + + // check if BIP38 EC multiply + const type = buffer.readUInt8(1); + if (type === 0x43) { + return decryptECMult(buffer, passphrase, progressCallback, scryptParams); + } + if (type !== 0x42) { + throw new Error("Invalid BIP38 type"); + } + + passphrase = Buffer.from(passphrase, "utf8"); + + const flagByte = buffer.readUInt8(2); + const compressed = flagByte === 0xe0; + if (!compressed && flagByte !== 0xc0) { + throw new Error("Invalid BIP38 compression flag"); + } + + const N = scryptParams.N; + const r = scryptParams.r; + const p = scryptParams.p; + + const salt = buffer.slice(3, 7); + const scryptBuf = scrypt(passphrase, salt, N, r, p, 64, progressCallback); + const derivedHalf1 = scryptBuf.slice(0, 32); + const derivedHalf2 = scryptBuf.slice(32, 64); + + const privKeyBuf = buffer.slice(7, 7 + 32); + const decipher = aes.createDecipheriv("aes-256-ecb", derivedHalf2, NULL); + decipher.setAutoPadding(false); + decipher.end(privKeyBuf); + + const plainText = decipher.read(); + const privateKey = xor(derivedHalf1, plainText); + + // verify salt matches address + const d = BigInteger.fromBuffer(privateKey); + const address = getAddress(d, compressed); + const checksum = hash256(address).slice(0, 4); + assert.deepEqual(salt, checksum); + + return { + privateKey, + compressed, + }; +} + +function decrypt(string, passphrase, progressCallback?, scryptParams?) { + return decryptRaw(bs58check.decode(string), passphrase, progressCallback, scryptParams); +} + +function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { + passphrase = Buffer.from(passphrase, "utf8"); + buffer = buffer.slice(1); // FIXME: we can avoid this + scryptParams = scryptParams || SCRYPT_PARAMS; + + const flag = buffer.readUInt8(1); + + const compressed = (flag & 0x20) !== 0; + const hasLotSeq = (flag & 0x04) !== 0; + + assert.equal(flag & 0x24, flag, "Invalid private key."); + + const addressHash = buffer.slice(2, 6); + const ownerEntropy = buffer.slice(6, 14); + let ownerSalt; + + // 4 bytes ownerSalt if 4 bytes lot/sequence + if (hasLotSeq) { + ownerSalt = ownerEntropy.slice(0, 4); + + // else, 8 bytes ownerSalt + } else { + ownerSalt = ownerEntropy; + } + + const encryptedPart1 = buffer.slice(14, 22); // First 8 bytes + const encryptedPart2 = buffer.slice(22, 38); // 16 bytes + + const N = scryptParams.N; + const r = scryptParams.r; + const p = scryptParams.p; + const preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback); + + let passFactor; + if (hasLotSeq) { + const hashTarget = Buffer.concat([preFactor, ownerEntropy]); + passFactor = hash256(hashTarget); + } else { + passFactor = preFactor; + } + + const passInt = BigInteger.fromBuffer(passFactor); + const passPoint = curve.G.multiply(passInt).getEncoded(true); + + const seedBPass = scrypt(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64); + const derivedHalf1 = seedBPass.slice(0, 32); + const derivedHalf2 = seedBPass.slice(32, 64); + + const decipher = aes.createDecipheriv("aes-256-ecb", derivedHalf2, Buffer.alloc(0)); + decipher.setAutoPadding(false); + decipher.end(encryptedPart2); + + const decryptedPart2 = decipher.read(); + const tmp = xor(decryptedPart2, derivedHalf1.slice(16, 32)); + const seedBPart2 = tmp.slice(8, 16); + + const decipher2 = aes.createDecipheriv("aes-256-ecb", derivedHalf2, Buffer.alloc(0)); + decipher2.setAutoPadding(false); + decipher2.write(encryptedPart1); // first 8 bytes + decipher2.end(tmp.slice(0, 8)); // last 8 bytes + + const seedBPart1 = xor(decipher2.read(), derivedHalf1.slice(0, 16)); + const seedB = Buffer.concat([seedBPart1, seedBPart2], 24); + const factorB = BigInteger.fromBuffer(hash256(seedB)); + + // d = passFactor * factorB (mod n) + const d = passInt.multiply(factorB).mod(curve.n); + + return { + privateKey: d.toBuffer(32), + compressed, + }; +} + +function verify(string) { + const decoded = bs58check.decodeUnsafe(string); + if (!decoded) { + return false; + } + + if (decoded.length !== 39) { + return false; + } + if (decoded.readUInt8(0) !== 0x01) { + return false; + } + + const type = decoded.readUInt8(1); + const flag = decoded.readUInt8(2); + + // encrypted WIF + if (type === 0x42) { + if (flag !== 0xc0 && flag !== 0xe0) { + return false; + } + + // EC mult + } else if (type === 0x43) { + if (flag & ~0x24) { + return false; + } + } else { + return false; + } + + return true; +} + +export { decrypt, decryptECMult, decryptRaw, encrypt, encryptRaw, verify }; diff --git a/packages/crypto/src/crypto/index.ts b/packages/crypto/src/crypto/index.ts index 256319c47a..8c7b136d92 100644 --- a/packages/crypto/src/crypto/index.ts +++ b/packages/crypto/src/crypto/index.ts @@ -1,7 +1,8 @@ +import * as bip38 from "./bip38"; import { crypto } from "./crypto"; import { HashAlgorithms } from "./hash-algorithms"; import { HDWallet } from "./hdwallet"; import { Message } from "./message"; import { slots } from "./slots"; -export { crypto, HDWallet, Message, slots, HashAlgorithms }; +export { crypto, HDWallet, Message, slots, HashAlgorithms, bip38 }; diff --git a/packages/crypto/src/models/delegate.ts b/packages/crypto/src/models/delegate.ts index e60489bfcd..40307aed5d 100644 --- a/packages/crypto/src/models/delegate.ts +++ b/packages/crypto/src/models/delegate.ts @@ -1,8 +1,8 @@ -import bip38 from "bip38"; import { createHash } from "crypto"; import forge from "node-forge"; import { authenticator } from "otplib"; import wif from "wif"; +import * as bip38 from "../crypto/bip38"; import { Bignum } from "../utils"; import { crypto } from "../crypto/crypto"; From b2f508b6924dd8d8bbe4994cb86e88f615910866 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 21:06:10 +0100 Subject: [PATCH 02/12] refactor: use native node scrypt crypto --- packages/crypto/src/crypto/bip38.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index e38bb9d462..e4c3973dad 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -1,10 +1,9 @@ -// tslint:disable:no-var-requires no-bitwise variable-name +import crypto from "crypto"; const aes = require("browserify-aes"); const assert = require("assert"); const bs58check = require("bs58check"); const createHash = require("create-hash"); -const scrypt = require("scryptsy"); const xor = require("buffer-xor/inplace"); const ecurve = require("ecurve"); @@ -65,7 +64,7 @@ function encryptRaw(buffer, compressed, passphrase, progressCallback, scryptPara const r = scryptParams.r; const p = scryptParams.p; - const scryptBuf = scrypt(secret, salt, N, r, p, 64, progressCallback); + const scryptBuf = crypto.scryptSync(secret, salt, 64, SCRYPT_PARAMS); const derivedHalf1 = scryptBuf.slice(0, 32); const derivedHalf2 = scryptBuf.slice(32, 64); @@ -124,7 +123,7 @@ function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { const p = scryptParams.p; const salt = buffer.slice(3, 7); - const scryptBuf = scrypt(passphrase, salt, N, r, p, 64, progressCallback); + const scryptBuf = crypto.scryptSync(passphrase, salt, 64, SCRYPT_PARAMS); const derivedHalf1 = scryptBuf.slice(0, 32); const derivedHalf2 = scryptBuf.slice(32, 64); @@ -183,7 +182,8 @@ function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { const N = scryptParams.N; const r = scryptParams.r; const p = scryptParams.p; - const preFactor = scrypt(passphrase, ownerSalt, N, r, p, 32, progressCallback); + + const preFactor = crypto.scryptSync(passphrase, ownerSalt, 32, SCRYPT_PARAMS); let passFactor; if (hasLotSeq) { @@ -196,7 +196,11 @@ function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { const passInt = BigInteger.fromBuffer(passFactor); const passPoint = curve.G.multiply(passInt).getEncoded(true); - const seedBPass = scrypt(passPoint, Buffer.concat([addressHash, ownerEntropy]), 1024, 1, 1, 64); + const seedBPass = crypto.scryptSync(passPoint, Buffer.concat([addressHash, ownerEntropy]), 64, { + N: 1024, + r: 1, + p: 1, + }); const derivedHalf1 = seedBPass.slice(0, 32); const derivedHalf2 = seedBPass.slice(32, 64); From 4c6731cef735b5a5a4d8c2743517a5649ba7157c Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 21:10:48 +0100 Subject: [PATCH 03/12] refactor: cleanup --- packages/crypto/src/crypto/bip38.ts | 53 +++++++++++------------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index e4c3973dad..76f7101c00 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -1,16 +1,16 @@ +// tslint:disable:no-bitwise + +import assert from "assert"; +import BigInteger from "bigi"; +import aes from "browserify-aes"; +import bs58check from "bs58check"; +import xor from "buffer-xor/inplace"; +import createHash from "create-hash"; import crypto from "crypto"; +import ecurve from "ecurve"; -const aes = require("browserify-aes"); -const assert = require("assert"); -const bs58check = require("bs58check"); -const createHash = require("create-hash"); -const xor = require("buffer-xor/inplace"); - -const ecurve = require("ecurve"); const curve = ecurve.getCurveByName("secp256k1"); -const BigInteger = require("bigi"); - // constants const SCRYPT_PARAMS = { N: 16384, // specified by BIP38 @@ -49,21 +49,16 @@ function getAddress(d, compressed) { return bs58check.encode(payload); } -function encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams) { +function encryptRaw(buffer, compressed, passphrase) { if (buffer.length !== 32) { throw new Error("Invalid private key length"); } - scryptParams = scryptParams || SCRYPT_PARAMS; const d = BigInteger.fromBuffer(buffer); const address = getAddress(d, compressed); const secret = Buffer.from(passphrase, "utf8"); const salt = hash256(address).slice(0, 4); - const N = scryptParams.N; - const r = scryptParams.r; - const p = scryptParams.p; - const scryptBuf = crypto.scryptSync(secret, salt, 64, SCRYPT_PARAMS); const derivedHalf1 = scryptBuf.slice(0, 32); const derivedHalf2 = scryptBuf.slice(32, 64); @@ -86,12 +81,12 @@ function encryptRaw(buffer, compressed, passphrase, progressCallback, scryptPara return result; } -function encrypt(buffer, compressed, passphrase, progressCallback?, scryptParams?) { - return bs58check.encode(encryptRaw(buffer, compressed, passphrase, progressCallback, scryptParams)); +function encrypt(buffer, compressed, passphrase) { + return bs58check.encode(encryptRaw(buffer, compressed, passphrase)); } // some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org -function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { +function decryptRaw(buffer, passphrase) { // 39 bytes: 2 bytes prefix, 37 bytes payload if (buffer.length !== 39) { throw new Error("Invalid BIP38 data length"); @@ -99,12 +94,11 @@ function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { if (buffer.readUInt8(0) !== 0x01) { throw new Error("Invalid BIP38 prefix"); } - scryptParams = scryptParams || SCRYPT_PARAMS; // check if BIP38 EC multiply const type = buffer.readUInt8(1); if (type === 0x43) { - return decryptECMult(buffer, passphrase, progressCallback, scryptParams); + return decryptECMult(buffer, passphrase); } if (type !== 0x42) { throw new Error("Invalid BIP38 type"); @@ -118,10 +112,6 @@ function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { throw new Error("Invalid BIP38 compression flag"); } - const N = scryptParams.N; - const r = scryptParams.r; - const p = scryptParams.p; - const salt = buffer.slice(3, 7); const scryptBuf = crypto.scryptSync(passphrase, salt, 64, SCRYPT_PARAMS); const derivedHalf1 = scryptBuf.slice(0, 32); @@ -147,14 +137,13 @@ function decryptRaw(buffer, passphrase, progressCallback, scryptParams) { }; } -function decrypt(string, passphrase, progressCallback?, scryptParams?) { - return decryptRaw(bs58check.decode(string), passphrase, progressCallback, scryptParams); +function decrypt(address: string, passphrase) { + return decryptRaw(bs58check.decode(address), passphrase); } -function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { +function decryptECMult(buffer, passphrase) { passphrase = Buffer.from(passphrase, "utf8"); buffer = buffer.slice(1); // FIXME: we can avoid this - scryptParams = scryptParams || SCRYPT_PARAMS; const flag = buffer.readUInt8(1); @@ -179,10 +168,6 @@ function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { const encryptedPart1 = buffer.slice(14, 22); // First 8 bytes const encryptedPart2 = buffer.slice(22, 38); // 16 bytes - const N = scryptParams.N; - const r = scryptParams.r; - const p = scryptParams.p; - const preFactor = crypto.scryptSync(passphrase, ownerSalt, 32, SCRYPT_PARAMS); let passFactor; @@ -230,8 +215,8 @@ function decryptECMult(buffer, passphrase, progressCallback, scryptParams) { }; } -function verify(string) { - const decoded = bs58check.decodeUnsafe(string); +function verify(address: string) { + const decoded = bs58check.decodeUnsafe(address); if (!decoded) { return false; } From 1e90bda8c11d3c614bd9487390ffcd8afe2ccad1 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 21:21:58 +0100 Subject: [PATCH 04/12] chore: add link to upstream package --- packages/crypto/src/crypto/bip38.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index 76f7101c00..4770ce51b8 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -1,5 +1,9 @@ // tslint:disable:no-bitwise +/** + * Based on: https://github.com/bitcoinjs/bip38 @ 8e3a2cc6f7391782f3012129924a73bb632a3d4d + */ + import assert from "assert"; import BigInteger from "bigi"; import aes from "browserify-aes"; From f1611c8a38afb53ae4cbddb0431452fb5c8d0cf0 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 21:22:57 +0100 Subject: [PATCH 05/12] refactor: add type infos --- packages/crypto/src/crypto/bip38.ts | 106 ++++++++++++++-------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index 4770ce51b8..083fc04afb 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -23,7 +23,7 @@ const SCRYPT_PARAMS = { }; const NULL = Buffer.alloc(0); -function hash160(buffer) { +function hash160(buffer): Buffer { return createHash("rmd160") .update( createHash("sha256") @@ -33,7 +33,7 @@ function hash160(buffer) { .digest(); } -function hash256(buffer) { +function hash256(buffer): Buffer { return createHash("sha256") .update( createHash("sha256") @@ -43,7 +43,12 @@ function hash256(buffer) { .digest(); } -function getAddress(d, compressed) { +export interface DecryptResult { + privateKey: Buffer; + compressed: boolean; +} + +export function getAddress(d: Buffer, compressed: boolean): string { const Q = curve.G.multiply(d).getEncoded(compressed); const hash = hash160(Q); const payload = Buffer.allocUnsafe(21); @@ -53,7 +58,49 @@ function getAddress(d, compressed) { return bs58check.encode(payload); } -function encryptRaw(buffer, compressed, passphrase) { +export function encrypt(buffer: Buffer, compressed: boolean, passphrase: string): string { + return bs58check.encode(encryptRaw(buffer, compressed, passphrase)); +} + +export function decrypt(address: string, passphrase): DecryptResult { + return decryptRaw(bs58check.decode(address), passphrase); +} + +export function verify(address: string): boolean { + const decoded = bs58check.decodeUnsafe(address); + if (!decoded) { + return false; + } + + if (decoded.length !== 39) { + return false; + } + if (decoded.readUInt8(0) !== 0x01) { + return false; + } + + const type = decoded.readUInt8(1); + const flag = decoded.readUInt8(2); + + // encrypted WIF + if (type === 0x42) { + if (flag !== 0xc0 && flag !== 0xe0) { + return false; + } + + // EC mult + } else if (type === 0x43) { + if (flag & ~0x24) { + return false; + } + } else { + return false; + } + + return true; +} + +function encryptRaw(buffer: Buffer, compressed: boolean, passphrase: string): Buffer { if (buffer.length !== 32) { throw new Error("Invalid private key length"); } @@ -85,12 +132,8 @@ function encryptRaw(buffer, compressed, passphrase) { return result; } -function encrypt(buffer, compressed, passphrase) { - return bs58check.encode(encryptRaw(buffer, compressed, passphrase)); -} - // some of the techniques borrowed from: https://github.com/pointbiz/bitaddress.org -function decryptRaw(buffer, passphrase) { +function decryptRaw(buffer: Buffer, passphrase: string): DecryptResult { // 39 bytes: 2 bytes prefix, 37 bytes payload if (buffer.length !== 39) { throw new Error("Invalid BIP38 data length"); @@ -108,8 +151,6 @@ function decryptRaw(buffer, passphrase) { throw new Error("Invalid BIP38 type"); } - passphrase = Buffer.from(passphrase, "utf8"); - const flagByte = buffer.readUInt8(2); const compressed = flagByte === 0xe0; if (!compressed && flagByte !== 0xc0) { @@ -141,12 +182,7 @@ function decryptRaw(buffer, passphrase) { }; } -function decrypt(address: string, passphrase) { - return decryptRaw(bs58check.decode(address), passphrase); -} - -function decryptECMult(buffer, passphrase) { - passphrase = Buffer.from(passphrase, "utf8"); +function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { buffer = buffer.slice(1); // FIXME: we can avoid this const flag = buffer.readUInt8(1); @@ -218,39 +254,3 @@ function decryptECMult(buffer, passphrase) { compressed, }; } - -function verify(address: string) { - const decoded = bs58check.decodeUnsafe(address); - if (!decoded) { - return false; - } - - if (decoded.length !== 39) { - return false; - } - if (decoded.readUInt8(0) !== 0x01) { - return false; - } - - const type = decoded.readUInt8(1); - const flag = decoded.readUInt8(2); - - // encrypted WIF - if (type === 0x42) { - if (flag !== 0xc0 && flag !== 0xe0) { - return false; - } - - // EC mult - } else if (type === 0x43) { - if (flag & ~0x24) { - return false; - } - } else { - return false; - } - - return true; -} - -export { decrypt, decryptECMult, decryptRaw, encrypt, encryptRaw, verify }; From 8df4ecf9625a14ff6fe2c184767b2241363e95ef Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 22:16:33 +0100 Subject: [PATCH 06/12] refactor: replace with own crypto --- packages/crypto/src/crypto/bip38.ts | 69 +++++++++++------------------ 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index 083fc04afb..daf1bd4eca 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -9,9 +9,9 @@ import BigInteger from "bigi"; import aes from "browserify-aes"; import bs58check from "bs58check"; import xor from "buffer-xor/inplace"; -import createHash from "create-hash"; import crypto from "crypto"; import ecurve from "ecurve"; +import { crypto as arkCrypto, HashAlgorithms } from "../crypto"; const curve = ecurve.getCurveByName("secp256k1"); @@ -23,43 +23,13 @@ const SCRYPT_PARAMS = { }; const NULL = Buffer.alloc(0); -function hash160(buffer): Buffer { - return createHash("rmd160") - .update( - createHash("sha256") - .update(buffer) - .digest(), - ) - .digest(); -} - -function hash256(buffer): Buffer { - return createHash("sha256") - .update( - createHash("sha256") - .update(buffer) - .digest(), - ) - .digest(); -} - export interface DecryptResult { privateKey: Buffer; compressed: boolean; } -export function getAddress(d: Buffer, compressed: boolean): string { - const Q = curve.G.multiply(d).getEncoded(compressed); - const hash = hash160(Q); - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(0x00, 0); // XXX TODO FIXME bitcoin only??? damn you BIP38 - hash.copy(payload, 1); - - return bs58check.encode(payload); -} - -export function encrypt(buffer: Buffer, compressed: boolean, passphrase: string): string { - return bs58check.encode(encryptRaw(buffer, compressed, passphrase)); +export function encrypt(privateKey: Buffer, compressed: boolean, passphrase: string): string { + return bs58check.encode(encryptRaw(privateKey, compressed, passphrase)); } export function decrypt(address: string, passphrase): DecryptResult { @@ -100,21 +70,21 @@ export function verify(address: string): boolean { return true; } -function encryptRaw(buffer: Buffer, compressed: boolean, passphrase: string): Buffer { - if (buffer.length !== 32) { +function encryptRaw(privateKey: Buffer, compressed: boolean, passphrase: string): Buffer { + if (privateKey.length !== 32) { throw new Error("Invalid private key length"); } - const d = BigInteger.fromBuffer(buffer); - const address = getAddress(d, compressed); + const address = getAddressPrivate(privateKey, compressed); + const secret = Buffer.from(passphrase, "utf8"); - const salt = hash256(address).slice(0, 4); + const salt = HashAlgorithms.hash256(address).slice(0, 4); const scryptBuf = crypto.scryptSync(secret, salt, 64, SCRYPT_PARAMS); const derivedHalf1 = scryptBuf.slice(0, 32); const derivedHalf2 = scryptBuf.slice(32, 64); - const xorBuf = xor(derivedHalf1, buffer); + const xorBuf = xor(derivedHalf1, privateKey); const cipher = aes.createCipheriv("aes-256-ecb", derivedHalf2, NULL); cipher.setAutoPadding(false); cipher.end(xorBuf); @@ -171,9 +141,9 @@ function decryptRaw(buffer: Buffer, passphrase: string): DecryptResult { const privateKey = xor(derivedHalf1, plainText); // verify salt matches address - const d = BigInteger.fromBuffer(privateKey); - const address = getAddress(d, compressed); - const checksum = hash256(address).slice(0, 4); + const address = getAddressPrivate(privateKey, compressed); + + const checksum = HashAlgorithms.hash256(address).slice(0, 4); assert.deepEqual(salt, checksum); return { @@ -213,7 +183,7 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { let passFactor; if (hasLotSeq) { const hashTarget = Buffer.concat([preFactor, ownerEntropy]); - passFactor = hash256(hashTarget); + passFactor = HashAlgorithms.hash256(hashTarget); } else { passFactor = preFactor; } @@ -244,7 +214,7 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { const seedBPart1 = xor(decipher2.read(), derivedHalf1.slice(0, 16)); const seedB = Buffer.concat([seedBPart1, seedBPart2], 24); - const factorB = BigInteger.fromBuffer(hash256(seedB)); + const factorB = BigInteger.fromBuffer(HashAlgorithms.hash256(seedB)); // d = passFactor * factorB (mod n) const d = passInt.multiply(factorB).mod(curve.n); @@ -254,3 +224,14 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { compressed, }; } + +function getAddressPrivate(privateKey: Buffer, compressed: boolean): string { + const publicKey = arkCrypto.getKeysByPrivateKey(privateKey, compressed).publicKey; + const buffer = HashAlgorithms.hash160(Buffer.from(publicKey, "hex")); + const payload = Buffer.alloc(21); + + payload.writeUInt8(0x00, 0); + buffer.copy(payload, 1); + + return bs58check.encode(payload); +} From f662e3d4b583457011eefca0fc7f5cb48979b616 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 22:31:13 +0100 Subject: [PATCH 07/12] refactor: replace with own crypto --- packages/crypto/src/crypto/bip38.ts | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index daf1bd4eca..55791dd9a8 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -5,17 +5,13 @@ */ import assert from "assert"; -import BigInteger from "bigi"; import aes from "browserify-aes"; import bs58check from "bs58check"; import xor from "buffer-xor/inplace"; import crypto from "crypto"; -import ecurve from "ecurve"; +import secp256k1 from "secp256k1"; import { crypto as arkCrypto, HashAlgorithms } from "../crypto"; -const curve = ecurve.getCurveByName("secp256k1"); - -// constants const SCRYPT_PARAMS = { N: 16384, // specified by BIP38 r: 8, @@ -188,10 +184,9 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { passFactor = preFactor; } - const passInt = BigInteger.fromBuffer(passFactor); - const passPoint = curve.G.multiply(passInt).getEncoded(true); + const publicKey = calculatePublicKey(passFactor, true); - const seedBPass = crypto.scryptSync(passPoint, Buffer.concat([addressHash, ownerEntropy]), 64, { + const seedBPass = crypto.scryptSync(publicKey, Buffer.concat([addressHash, ownerEntropy]), 64, { N: 1024, r: 1, p: 1, @@ -214,20 +209,16 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { const seedBPart1 = xor(decipher2.read(), derivedHalf1.slice(0, 16)); const seedB = Buffer.concat([seedBPart1, seedBPart2], 24); - const factorB = BigInteger.fromBuffer(HashAlgorithms.hash256(seedB)); - - // d = passFactor * factorB (mod n) - const d = passInt.multiply(factorB).mod(curve.n); return { - privateKey: d.toBuffer(32), + privateKey: secp256k1.privateKeyTweakMul(passFactor, seedB), compressed, }; } function getAddressPrivate(privateKey: Buffer, compressed: boolean): string { - const publicKey = arkCrypto.getKeysByPrivateKey(privateKey, compressed).publicKey; - const buffer = HashAlgorithms.hash160(Buffer.from(publicKey, "hex")); + const publicKey = calculatePublicKey(privateKey, compressed); + const buffer = HashAlgorithms.hash160(publicKey); const payload = Buffer.alloc(21); payload.writeUInt8(0x00, 0); @@ -235,3 +226,7 @@ function getAddressPrivate(privateKey: Buffer, compressed: boolean): string { return bs58check.encode(payload); } + +function calculatePublicKey(buffer: Buffer, compressed: boolean): Buffer { + return Buffer.from(arkCrypto.getKeysByPrivateKey(buffer, compressed).publicKey, "hex"); +} From beb73d0bcbe2a521f22d14254c082230d347d063 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 23:16:37 +0100 Subject: [PATCH 08/12] fix: decryptECMult --- packages/crypto/src/crypto/bip38.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index 55791dd9a8..4b37e05b7e 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -185,7 +185,6 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { } const publicKey = calculatePublicKey(passFactor, true); - const seedBPass = crypto.scryptSync(publicKey, Buffer.concat([addressHash, ownerEntropy]), 64, { N: 1024, r: 1, @@ -209,9 +208,10 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { const seedBPart1 = xor(decipher2.read(), derivedHalf1.slice(0, 16)); const seedB = Buffer.concat([seedBPart1, seedBPart2], 24); + const privateKey = secp256k1.privateKeyTweakMul(HashAlgorithms.hash256(seedB), passFactor); return { - privateKey: secp256k1.privateKeyTweakMul(passFactor, seedB), + privateKey, compressed, }; } From c53b641ef7e50a47d7e35496b8c84c7d8348901b Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 23:32:31 +0100 Subject: [PATCH 09/12] test: add bip38 tests --- .../crypto/__tests__/crypto/bip38.test.ts | 56 ++++++++ .../__tests__/crypto/fixtures/bip38.json | 121 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 packages/crypto/__tests__/crypto/bip38.test.ts create mode 100644 packages/crypto/__tests__/crypto/fixtures/bip38.json diff --git a/packages/crypto/__tests__/crypto/bip38.test.ts b/packages/crypto/__tests__/crypto/bip38.test.ts new file mode 100644 index 0000000000..5dd7acc121 --- /dev/null +++ b/packages/crypto/__tests__/crypto/bip38.test.ts @@ -0,0 +1,56 @@ +import "jest-extended"; + +import bs58check from "bs58check"; +import wif from "wif"; +import { bip38 } from "../../src/crypto"; + +import fixtures from "./fixtures/bip38.json"; + +describe("BIP38", () => { + describe("decrypt", () => { + fixtures.valid.forEach(fixture => { + it(`should decrypt '${fixture.description}'`, () => { + const result = bip38.decrypt(fixture.bip38, fixture.passphrase); + expect(wif.encode(0x80, result.privateKey, result.compressed)).toEqual(fixture.wif); + }); + }); + + fixtures.invalid.verify.forEach(fixture => { + it(`should not decrypt '${fixture.description}'`, () => { + try { + bip38.decrypt(fixture.base58, "foobar"); + } catch (error) { + expect(error.message).toEqual(fixture.exception); + } + }); + }); + }); + + describe("encrypt", () => { + fixtures.valid.forEach(fixture => { + if (fixture.decryptOnly) { + return; + } + + it(`should encrypt '${fixture.description}'`, () => { + const buffer = bs58check.decode(fixture.wif); + const actual = bip38.encrypt(buffer.slice(1, 33), !!buffer[33], fixture.passphrase); + expect(actual).toEqual(fixture.bip38); + }); + }); + }); + + describe("verify", () => { + fixtures.valid.forEach(fixture => { + it(`should verify '${fixture.bip38}'`, () => { + expect(bip38.verify(fixture.bip38)).toBeTrue(); + }); + }); + + fixtures.invalid.verify.forEach(fixture => { + it(`should not verify '${fixture.description}'`, () => { + expect(bip38.verify(fixture.base58)).toBeFalse(); + }); + }); + }); +}); diff --git a/packages/crypto/__tests__/crypto/fixtures/bip38.json b/packages/crypto/__tests__/crypto/fixtures/bip38.json new file mode 100644 index 0000000000..1bef83f7fb --- /dev/null +++ b/packages/crypto/__tests__/crypto/fixtures/bip38.json @@ -0,0 +1,121 @@ +{ + "valid": [ + { + "passphrase": "TestingOneTwoThree", + "bip38": "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "wif": "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "address": "1Jq6MksXQVWzrznvZzxkV6oY57oWXD9TXB", + "description": "no EC multiply / no compression #1" + }, + { + "passphrase": "Satoshi", + "bip38": "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", + "wif": "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5", + "address": "1AvKt49sui9zfzGeo8EyL8ypvAhtR2KwbL", + "description": "no EC multiply / no compression #2" + }, + { + "passphrase": "TestingOneTwoThree", + "bip38": "6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo", + "wif": "L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP", + "address": "164MQi977u9GUteHr4EPH27VkkdxmfCvGW", + "description": "no EC multiply / compression #1" + }, + { + "passphrase": "Satoshi", + "bip38": "6PYLtMnXvfG3oJde97zRyLYFZCYizPU5T3LwgdYJz1fRhh16bU7u6PPmY7", + "wif": "KwYgW8gcxj1JWJXhPSu4Fqwzfhp5Yfi42mdYmMa4XqK7NJxXUSK7", + "address": "1HmPbwsvG5qJ3KJfxzsZRZWhbm1xBMuS8B", + "description": "no EC multiply / compression #2" + }, + { + "passphrase": "TestingOneTwoThree", + "bip38": "6PfQu77ygVyJLZjfvMLyhLMQbYnu5uguoJJ4kMCLqWwPEdfpwANVS76gTX", + "wif": "5K4caxezwjGCGfnoPTZ8tMcJBLB7Jvyjv4xxeacadhq8nLisLR2", + "address": "1PE6TQi6HTVNz5DLwB1LcpMBALubfuN2z2", + "description": "EC multiply / no compression, no lot sequence #1", + "decryptOnly": true, + "code": "passphrasepxFy57B9v8HtUsszJYKReoNDV6VHjUSGt8EVJmux9n1J3Ltf1gRxyDGXqnf9qm" + }, + { + "passphrase": "Satoshi", + "bip38": "6PfLGnQs6VZnrNpmVKfjotbnQuaJK4KZoPFrAjx1JMJUa1Ft8gnf5WxfKd", + "wif": "5KJ51SgxWaAYR13zd9ReMhJpwrcX47xTJh2D3fGPG9CM8vkv5sH", + "address": "1CqzrtZC6mXSAhoxtFwVjz8LtwLJjDYU3V", + "description": "EC multiply / no compression, no lot sequence #2", + "decryptOnly": true, + "code": "passphraseoRDGAXTWzbp72eVbtUDdn1rwpgPUGjNZEc6CGBo8i5EC1FPW8wcnLdq4ThKzAS" + }, + { + "passphrase": "MOLON LABE", + "bip38": "6PgNBNNzDkKdhkT6uJntUXwwzQV8Rr2tZcbkDcuC9DZRsS6AtHts4Ypo1j", + "wif": "5JLdxTtcTHcfYcmJsNVy1v2PMDx432JPoYcBTVVRHpPaxUrdtf8", + "address": "1Jscj8ALrYu2y9TD8NrpvDBugPedmbj4Yh", + "description": "EC multiply / no compression, lot sequence #1", + "decryptOnly": true, + "confirm": "cfrm38V8aXBn7JWA1ESmFMUn6erxeBGZGAxJPY4e36S9QWkzZKtaVqLNMgnifETYw7BPwWC9aPD", + "code": "passphraseaB8feaLQDENqCgr4gKZpmf4VoaT6qdjJNJiv7fsKvjqavcJxvuR1hy25aTu5sX", + "lot": 263183, + "seq": 1 + }, + { + "passphrase": "ΜΟΛΩΝ ΛΑΒΕ", + "bip38": "6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5mar2ngH", + "wif": "5KMKKuUmAkiNbA3DazMQiLfDq47qs8MAEThm4yL8R2PhV1ov33D", + "address": "1Lurmih3KruL4xDB5FmHof38yawNtP9oGf", + "description": "EC multiply / no compression, lot sequence #1", + "decryptOnly": true, + "confirm": "cfrm38V8G4qq2ywYEFfWLD5Cc6msj9UwsG2Mj4Z6QdGJAFQpdatZLavkgRd1i4iBMdRngDqDs51", + "code": "passphrased3z9rQJHSyBkNBwTRPkUGNVEVrUAcfAXDyRU1V28ie6hNFbqDwbFBvsTK7yWVK", + "lot": 806938, + "sequence": 1 + } + ], + "invalid": { + "decrypt": [], + "encrypt": [], + "verify": [ + { + "description": "Invalid base58", + "exception": "Invalid checksum", + "base58": "6PgGWtx25kUg8QWvwuJAgorN6k9FbE25rv5dMRwu5SKMnfpfVe5marXXXX" + }, + { + "description": "Length > 39", + "exception": "Invalid BIP38 data length", + "hex": "0142c000000000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "QmxDezFMDL7ExfYmsETsQXAtBbw5YE1CDyA8pm1AGpMpVVUpsVy1yXv4VTL" + }, + { + "description": "Length < 39", + "exception": "Invalid BIP38 data length", + "hex": "0142c00000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "2DnNxWcx4Prn8wmjbkvtYGDALsq8BMWxQ33KnXkeH8vrxE41psDLXRmK3" + }, + { + "description": "prefix !== 0x01", + "exception": "Invalid BIP38 prefix", + "hex": "0242c0000000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "AfE1YY4Wr2FLAENaH9PVaLRdyk714V4rhwiJMSGyQCGFB3rhGDCs2R7c4s" + }, + { + "description": "flag !== 0xc0 && flag !== 0xe0", + "exception": "Invalid BIP38 type", + "hex": "0101ff000000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "5JjnYkbFBmUnhGeDMVhR7aSitLToe1odEfXDBeg4RMK6JmAm9g7rkm7qY3" + }, + { + "description": "EC Mult: ~(flag & 0x24)", + "exception": "Invalid BIP38 type", + "hex": "0101db000000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "5JbtdQFKSemRTqMuWrJgSfzE8AX2jdz1KiZuMmuUcv9iXha1s6UarQTciW" + }, + { + "description": "EC Mult: ~(flag & 0x24)", + "exception": "Invalid BIP38 type", + "hex": "010135000000000000000000000000000000000000000000000000000000000000000000000000", + "base58": "5HyV7HSYdHUgLf7w36mxMHDPH9muTgUYHEj6cEogKMuV7ae8VRM3VEg56w" + } + ] + } +} From 5bb5f08bfcfca9e84ecfee92ecb30488f02beee2 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 23:47:44 +0100 Subject: [PATCH 10/12] chore: drop bip38 dependency --- packages/core-json-rpc/package.json | 2 -- .../core-json-rpc/src/server/methods/wallets/bip38/create.ts | 3 +-- packages/core-json-rpc/src/server/utils/decrypt-wif.ts | 3 +-- packages/core/package.json | 1 - packages/core/src/index.ts | 3 +-- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/core-json-rpc/package.json b/packages/core-json-rpc/package.json index 4b8dd979a6..b9490c8644 100644 --- a/packages/core-json-rpc/package.json +++ b/packages/core-json-rpc/package.json @@ -33,7 +33,6 @@ "@arkecosystem/core-http-utils": "^2.1.0", "@arkecosystem/crypto": "^2.1.0", "@keyv/sqlite": "^2.0.0", - "@types/bip38": "^2.0.0", "@types/bip39": "^2.4.1", "@types/boom": "^7.2.1", "@types/joi": "^14.0.0", @@ -42,7 +41,6 @@ "@types/uuid": "^3.4.4", "@types/wif": "^2.0.1", "axios": "^0.18.0", - "bip38": "^2.0.2", "bip39": "^2.5.0", "boom": "^7.3.0", "is-reachable": "^3.0.0", diff --git a/packages/core-json-rpc/src/server/methods/wallets/bip38/create.ts b/packages/core-json-rpc/src/server/methods/wallets/bip38/create.ts index 17cbc01cde..cb29c6c1f0 100644 --- a/packages/core-json-rpc/src/server/methods/wallets/bip38/create.ts +++ b/packages/core-json-rpc/src/server/methods/wallets/bip38/create.ts @@ -1,5 +1,4 @@ -import { crypto, HashAlgorithms } from "@arkecosystem/crypto"; -import bip38 from "bip38"; +import { bip38, crypto, HashAlgorithms } from "@arkecosystem/crypto"; import bip39 from "bip39"; import Joi from "joi"; import { database } from "../../../services/database"; diff --git a/packages/core-json-rpc/src/server/utils/decrypt-wif.ts b/packages/core-json-rpc/src/server/utils/decrypt-wif.ts index 46c291a4b8..a74344297d 100644 --- a/packages/core-json-rpc/src/server/utils/decrypt-wif.ts +++ b/packages/core-json-rpc/src/server/utils/decrypt-wif.ts @@ -1,5 +1,4 @@ -import { configManager, crypto } from "@arkecosystem/crypto"; -import bip38 from "bip38"; +import { bip38, configManager, crypto } from "@arkecosystem/crypto"; import wif from "wif"; export const decryptWIF = (encryptedWif, userId, bip38password) => { diff --git a/packages/core/package.json b/packages/core/package.json index 57ab04fa40..a1c428534c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -81,7 +81,6 @@ "@types/bip38": "^2.0.0", "@types/commander": "^2.12.2", "@types/wif": "^2.0.1", - "bip38": "^2.0.2", "commander": "^2.19.0", "wif": "^2.0.6" }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 56e7dff989..e195805830 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,6 @@ #!/usr/bin/env node -import { configManager, crypto } from "@arkecosystem/crypto"; -import bip38 from "bip38"; +import { bip38, configManager, crypto } from "@arkecosystem/crypto"; import app from "commander"; import fs from "fs"; import wif from "wif"; From 4427245518237255f5791d80d907ef719d160d76 Mon Sep 17 00:00:00 2001 From: supaiku Date: Thu, 3 Jan 2019 23:48:02 +0100 Subject: [PATCH 11/12] chore: yarn.lock --- yarn.lock | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/yarn.lock b/yarn.lock index 880c9907de..70ece095d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2947,11 +2947,6 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== -bigi@^1.1.0, bigi@^1.2.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" - integrity sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU= - bignumber.js@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.1.tgz#5d419191370fb558c64e3e5f70d68e5947138832" @@ -2990,19 +2985,6 @@ bip32@^1.0.2: typeforce "^1.11.5" wif "^2.0.6" -bip38@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/bip38/-/bip38-2.0.2.tgz#6f7762bc90b0bdf63b489ff95349354aecf9baee" - integrity sha512-22KDak0RDyghFbR0Si7wyq9IgY423YzGYzWLpGeofH3DaolOQqjD3mNN08eFoubKlbyclOQKFwtONMv2SD9V3A== - dependencies: - bigi "^1.2.0" - browserify-aes "^1.0.1" - bs58check "<3.0.0" - buffer-xor "^1.0.2" - create-hash "^1.1.1" - ecurve "^1.0.0" - scryptsy "^2.0.0" - bip39@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.5.0.tgz#51cbd5179460504a63ea3c000db3f787ca051235" @@ -3153,7 +3135,7 @@ browser-resolve@^1.11.3: dependencies: resolve "1.1.7" -browserify-aes@^1.0.0, browserify-aes@^1.0.1, browserify-aes@^1.0.4, browserify-aes@^1.0.6: +browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -3266,7 +3248,7 @@ buffer-writer@2.0.0: resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== -buffer-xor@^1.0.2, buffer-xor@^1.0.3: +buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= @@ -4115,7 +4097,7 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" -create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2, create-hash@^1.2.0: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -4650,14 +4632,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecurve@^1.0.0: - version "1.0.6" - resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797" - integrity sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w== - dependencies: - bigi "^1.1.0" - safe-buffer "^5.0.1" - editions@^1.1.1, editions@^1.3.3, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -11148,11 +11122,6 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -scryptsy@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.0.0.tgz#262c36f0231cfa7654e2363fa394cd2dec66f378" - integrity sha1-Jiw28CMc+nZU4jY/o5TNLexm83g= - secp256k1@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.5.2.tgz#f95f952057310722184fe9c914e6b71281f2f2ae" From 2ad8c658593b3b7d3d3c0b87b6f21c3fefeced8c Mon Sep 17 00:00:00 2001 From: supaiku Date: Fri, 4 Jan 2019 00:21:36 +0100 Subject: [PATCH 12/12] refactor: rename params --- packages/crypto/src/crypto/bip38.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/crypto/src/crypto/bip38.ts b/packages/crypto/src/crypto/bip38.ts index 4b37e05b7e..b8746f3052 100644 --- a/packages/crypto/src/crypto/bip38.ts +++ b/packages/crypto/src/crypto/bip38.ts @@ -28,12 +28,12 @@ export function encrypt(privateKey: Buffer, compressed: boolean, passphrase: str return bs58check.encode(encryptRaw(privateKey, compressed, passphrase)); } -export function decrypt(address: string, passphrase): DecryptResult { - return decryptRaw(bs58check.decode(address), passphrase); +export function decrypt(bip38: string, passphrase): DecryptResult { + return decryptRaw(bs58check.decode(bip38), passphrase); } -export function verify(address: string): boolean { - const decoded = bs58check.decodeUnsafe(address); +export function verify(bip38: string): boolean { + const decoded = bs58check.decodeUnsafe(bip38); if (!decoded) { return false; } @@ -66,12 +66,12 @@ export function verify(address: string): boolean { return true; } -function encryptRaw(privateKey: Buffer, compressed: boolean, passphrase: string): Buffer { - if (privateKey.length !== 32) { +function encryptRaw(buffer: Buffer, compressed: boolean, passphrase: string): Buffer { + if (buffer.length !== 32) { throw new Error("Invalid private key length"); } - const address = getAddressPrivate(privateKey, compressed); + const address = getAddressPrivate(buffer, compressed); const secret = Buffer.from(passphrase, "utf8"); const salt = HashAlgorithms.hash256(address).slice(0, 4); @@ -80,7 +80,7 @@ function encryptRaw(privateKey: Buffer, compressed: boolean, passphrase: string) const derivedHalf1 = scryptBuf.slice(0, 32); const derivedHalf2 = scryptBuf.slice(32, 64); - const xorBuf = xor(derivedHalf1, privateKey); + const xorBuf = xor(derivedHalf1, buffer); const cipher = aes.createCipheriv("aes-256-ecb", derivedHalf2, NULL); cipher.setAutoPadding(false); cipher.end(xorBuf); @@ -184,7 +184,7 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { passFactor = preFactor; } - const publicKey = calculatePublicKey(passFactor, true); + const publicKey = getPublicKey(passFactor, true); const seedBPass = crypto.scryptSync(publicKey, Buffer.concat([addressHash, ownerEntropy]), 64, { N: 1024, r: 1, @@ -217,7 +217,7 @@ function decryptECMult(buffer: Buffer, passphrase: string): DecryptResult { } function getAddressPrivate(privateKey: Buffer, compressed: boolean): string { - const publicKey = calculatePublicKey(privateKey, compressed); + const publicKey = getPublicKey(privateKey, compressed); const buffer = HashAlgorithms.hash160(publicKey); const payload = Buffer.alloc(21); @@ -227,6 +227,6 @@ function getAddressPrivate(privateKey: Buffer, compressed: boolean): string { return bs58check.encode(payload); } -function calculatePublicKey(buffer: Buffer, compressed: boolean): Buffer { +function getPublicKey(buffer: Buffer, compressed: boolean): Buffer { return Buffer.from(arkCrypto.getKeysByPrivateKey(buffer, compressed).publicKey, "hex"); }