Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
555d36d
add an aes256 lib, required for scrypt later
Jun 2, 2021
3f85f54
remove duplicated test
Jun 2, 2021
d74cf26
minor jsdoc update, add ip ref
Jun 2, 2021
a7da375
remove direct native crypto pkg usage, use browserify libs
Jun 8, 2021
0e18502
allow custom iv for aes enc
Jun 8, 2021
e3fe183
remove invalid npm dev dep
Jun 8, 2021
8df8329
change iv arg from buffer to () => buffer
Jun 8, 2021
3c12bc1
few requested changes
Jun 9, 2021
b7f64a7
update comment
Jun 9, 2021
54f9625
add an aes256 lib, required for scrypt later
Jun 2, 2021
bed1564
remove duplicated test
Jun 2, 2021
6646c61
minor jsdoc update, add ip ref
Jun 2, 2021
96bc0fb
add a lib to do store passphrase protected data using scrypt
Jun 2, 2021
1a7a652
some refactoring, and scrypt interface has to be provided by caller, …
Jun 2, 2021
6c67cb3
reverted unwated changed
Jun 3, 2021
abff71a
move dep to sub package
Jun 3, 2021
ebb5bea
rename var
Jun 3, 2021
bf11d28
rename package
Jun 3, 2021
071167b
rename package
Jun 3, 2021
b00fece
some refactor, implement EncryptedMnemoniceNode
Jun 3, 2021
a685419
add secp to jlf-wal-encryted
Jun 4, 2021
d3e52b8
minor rename, some jsdoc
Jun 4, 2021
7f1cf13
done with all wallet-encrypted implementation
Jun 4, 2021
c589408
minor syntax clean up
Jun 4, 2021
a9f0293
allow custom scrypt param for scryptsy implementation, reduce test di…
Jun 4, 2021
18aba36
sort doc alphabetically (did not do so after rename package)
Jun 4, 2021
78eb2ed
add tests
Jun 4, 2021
f2800d8
update some description in md
Jun 4, 2021
3778f61
add dev dep
Jun 4, 2021
69f69b5
add missing dep
Jun 4, 2021
822112a
fix tiny secp version
Jun 4, 2021
58bc8dd
make ScryptStorage require 2 stores to instantiate, store hash for pa…
Jun 4, 2021
55bdbc0
minor comment update
Jun 4, 2021
379246b
put back seedHash in provider
Jun 4, 2021
4595330
add more desc, + minor fix
Jun 4, 2021
7dc7c6b
fix test
Jun 4, 2021
f103863
remove direct native crypto pkg usage, use browserify libs
Jun 8, 2021
5466474
allow custom iv for aes enc
Jun 8, 2021
af7f094
remove invalid npm dev dep
Jun 8, 2021
20cd86e
fix import (dep renamed in jlf crypto)
Jun 8, 2021
0e12e38
change iv arg from buffer to () => buffer
Jun 8, 2021
a6465fb
allow custom iv randomizer logic to for ScryptStorage aes enc
Jun 8, 2021
825e829
update some comments in demo
Jun 8, 2021
e4235bb
reverting unwanted package-lock changes
Jun 8, 2021
bfd2709
few requested changes
Jun 9, 2021
5f49e06
update comment
Jun 9, 2021
7a96183
quick save
Jun 10, 2021
a0b15b4
fix build script command
Jun 10, 2021
5479041
remove import from dist dir
Jun 10, 2021
38399bb
add missing jest cli config
Jun 10, 2021
a515433
enhance test
Jun 11, 2021
bb143e7
start over using updated main
Jun 11, 2021
ddc3f1c
import SIGHASH from jlf-tx
Jun 11, 2021
63a6345
add config for gh actions
Jun 11, 2021
67d8936
add module mapper and build config
Jun 11, 2021
a398c4e
add doc, add new tests to jlf-tx-sig
Jun 11, 2021
f69f435
replace transactionsigner from jlf-tx-sig
Jun 11, 2021
5fed58c
replace all signer logic import from simple jlf-tx
Jun 11, 2021
5d626b9
fix jest config module mapper order
Jun 11, 2021
f63f539
repeat test
Jun 11, 2021
2bef85e
Merge branch 'feature/mnemonic-wallet/passphrase-encrypt-seed' into f…
Jun 11, 2021
0e1969a
remove PR irrelevant code
Jun 11, 2021
bab5aba
Merge branch 'handpick-jellyfish-tx-signature' into feature/mnemonic-…
Jun 11, 2021
b31582d
npm i
Jun 11, 2021
9d8a227
trim down and split PR, to ease review process
Jun 11, 2021
ef75507
Merge branch 'main' into mnemonic-seed-encryption
Jun 15, 2021
f5c66ea
Merge branch 'main' into mnemonic-seed-encryption
Jun 15, 2021
5bd91e9
fix test, add desc
Jun 15, 2021
fcb8393
Merge branch 'main' into mnemonic-seed-encryption
Jul 16, 2021
0a53e3c
simplify construct private readonly syntax
Jul 16, 2021
a5f8c7e
Merge branch 'mnemonic-seed-encryption' into feature/mnemonic-wallet/…
Jul 16, 2021
b729a36
passphrase only need when get pub/priv key, sign/verify
Jul 16, 2021
a156024
remove cast type
Jul 16, 2021
e1cfef8
Refactoring and clean up, added passphrase input interface
Jul 16, 2021
66ef446
remove unnecessary setter call in consturctor
Jul 16, 2021
01278e5
fix invalidtype cast
Jul 19, 2021
9207a6f
remove husky gitigonore (no longer needed)
Jul 19, 2021
60e138d
address some change request
Jul 19, 2021
28a1f89
remove outdate husky file, updated package-lock
Jul 19, 2021
03667d4
Merge branch 'mnemonic-seed-encryption' into feature/mnemonic-wallet/…
Jul 19, 2021
90e1195
Merge branch 'main' into feature/mnemonic-wallet/scrypt-storage
Jul 19, 2021
0744d4b
rename file, fix import
Jul 19, 2021
c7d51bd
add back missed out changes
Jul 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import BigNumber from 'bignumber.js'
import { OP_CODES, Transaction, Vout } from '@defichain/jellyfish-transaction'
import { OnDemandMnemonicHdNode, EncryptedMnemonicProvider, ScryptStorage, SimpleScryptsy, Storage } from '../src'
import { HASH160 } from '@defichain/jellyfish-crypto'

// Mock storage
class MockStore implements Storage {
inMemory: string | undefined
async getter (): Promise<string|undefined> {
return this.inMemory
}

async setter (data: string|undefined): Promise<void> {
this.inMemory = data
}
}

const sampleMnemonicSeed = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262'
const scryptPovider = new SimpleScryptsy({
N: 16384,
r: 8,
p: 1 // to speed up test, p = 8 take ~4s for each encrypt or decrypt
})

describe('EncryptedMnemonicProvider', () => {
let provider: EncryptedMnemonicProvider
const seedStore = new MockStore()
const seedHashStore = new MockStore()
let node: OnDemandMnemonicHdNode

beforeEach(async () => {
const seed = Buffer.from(sampleMnemonicSeed, 'hex')
const collectPassphrase = async (): Promise<string> => 'password'

provider = await EncryptedMnemonicProvider.create({
scryptStorage: new ScryptStorage(
scryptPovider, // client platform supported encryption implementation
seedStore, // client provided interface to get/set encrypted seed
seedHashStore // client provided interface to get/set encrypted seedHash, for passphrase validation
),
seed, // new wallet, pass in seed
collectPassphrase, // interface, wait for user input
options: { // bip32 options
bip32: {
public: 0x00000000,
private: 0x00000000
},
wif: 0x00
}
})

node = await provider.derive("44'/1129'/0'/0/0")

const pubKey = await node.publicKey()
const privKey = await node.privateKey()
expect(pubKey.length).toStrictEqual(33)
expect(privKey.length).toStrictEqual(32)
})

it('EncryptedMnemonicProvider.load() - should be able to load/restore a provider instance', async () => {
const loaded = await EncryptedMnemonicProvider.load({
scryptStorage: new ScryptStorage(
scryptPovider, // must have same Scrypt logic
seedStore, // already holding the encrypted seed
seedHashStore // already holding the seed hash
),
collectPassphrase: async () => 'password',
options: {
bip32: {
public: 0x00000000,
private: 0x00000000
},
wif: 0x00
}
})

const pubKey = await node.publicKey()
const privKey = await node.privateKey()

const loadedNode = await loaded.derive("44'/1129'/0'/0/0")
const loadedPubKey = await loadedNode.publicKey()
const loadedPrivKey = await loadedNode.privateKey()

expect(pubKey.toString('hex')).toStrictEqual(loadedPubKey.toString('hex'))
expect(privKey.toString('hex')).toStrictEqual(loadedPrivKey.toString('hex'))
})

it('Should be able to derive multiple node/elliptic pair and unlockable with same passphrase', async () => {
const node1 = await provider.derive("44'/1129'/1'/0/0")
const pubKey1 = await node1.publicKey()
const privKey1 = await node1.privateKey()

const node2 = await provider.derive("44'/1129'/2'/0/0")
const pubKey2 = await node2.publicKey()
const privKey2 = await node2.privateKey()
expect(pubKey2.length).toStrictEqual(33)
expect(privKey2.length).toStrictEqual(32)

expect(pubKey1.toString('hex')).not.toStrictEqual(pubKey2.toString('hex'))
expect(privKey1.toString('hex')).not.toStrictEqual(privKey2.toString('hex'))
})

it('Should be able to sign and verify', async () => {
const hash = Buffer.from('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a', 'hex')
const signature = await node.sign(hash)

expect(signature.length).toBeLessThanOrEqual(70)
expect(signature.length).toBeGreaterThanOrEqual(67) // 0.00001 probability of being this length

expect(await node.verify(hash, signature)).toBeTruthy()

// meant to fail, differnt pubkey
const anotherNode = await provider.derive("44'/1129'/1'/0/0")
expect(await anotherNode.verify(hash, signature)).toBeFalsy()
})

it('signTx()', async () => {
const transaction: Transaction = {
version: 0x00000004,
lockTime: 0x00000000,
vin: [{
index: 0,
script: { stack: [] },
sequence: 4294967278,
txid: '9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff'
}],
vout: [{
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a1', 'hex'), 'little')
]
},
value: new BigNumber('5.98'),
tokenId: 0x00
}]
}

const prevout: Vout = {
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a1', 'hex'), 'little')
]
},
value: new BigNumber('6'),
tokenId: 0x00
}

const signed = await node.signTx(transaction, [{
...prevout,
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(HASH160(await node.publicKey()), 'little')
]
}
}])

expect(signed.witness.length).toStrictEqual(1)
expect(signed.witness[0].scripts.length).toStrictEqual(2)

expect(signed.witness[0].scripts[0].hex.length).toBeGreaterThanOrEqual(140)
expect(signed.witness[0].scripts[0].hex.length).toBeLessThanOrEqual(142)
expect(signed.witness[0].scripts[1].hex.length).toStrictEqual(66)
})

it('should failed to unlock seed using invalid password thus unable to access any data of hdnode', async () => {
let password = 'valid-password'
const seed = Buffer.from(sampleMnemonicSeed, 'hex')
const collectPassphrase = async (): Promise<string> => password

const provider = await EncryptedMnemonicProvider.create({
scryptStorage: new ScryptStorage(
scryptPovider, // client platform supported encryption implementation
seedStore, // client provided interface to get/set encrypted seed
seedHashStore // client provided interface to get/set encrypted seedHash, for passphrase validation
),
seed, // new wallet, pass in seed
collectPassphrase, // interface, wait for user input
options: { // bip32 options
bip32: {
public: 0x00000000,
private: 0x00000000
},
wif: 0x00
}
})

const encryptedHdNode = provider.derive("44'/1129'/0'/0/0")
const publicKey = await encryptedHdNode.publicKey() // no err thrown

password = 'invalid'

await expect(encryptedHdNode.publicKey()).rejects.toThrow('InvalidPassphrase')
await expect(encryptedHdNode.privateKey()).rejects.toThrow('InvalidPassphrase')
await expect(encryptedHdNode.sign(Buffer.from('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a', 'hex'))).rejects.toThrow('InvalidPassphrase')
await expect(encryptedHdNode.verify(Buffer.alloc(1), Buffer.alloc(1))).rejects.toThrow('InvalidPassphrase')

const transaction: Transaction = {
version: 0x00000004,
lockTime: 0x00000000,
vin: [{
index: 0,
script: { stack: [] },
sequence: 4294967278,
txid: '9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff'
}],
vout: [{
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a1', 'hex'), 'little')
]
},
value: new BigNumber('5.98'),
tokenId: 0x00
}]
}

const prevout: Vout = {
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(Buffer.from('1d0f172a0ecb48aee1be1f2687d2963ae33f71a1', 'hex'), 'little')
]
},
value: new BigNumber('6'),
tokenId: 0x00
}

await expect(encryptedHdNode.signTx(transaction, [{
...prevout,
script: {
stack: [
OP_CODES.OP_0,
OP_CODES.OP_PUSHDATA(HASH160(publicKey), 'little')
]
}
}])).rejects.toThrow('InvalidPassphrase')
})
})
26 changes: 26 additions & 0 deletions packages/jellyfish-wallet-encrypted/src/bip32-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Bip32Options } from '@defichain/jellyfish-wallet-mnemonic'
import { ScryptStorage } from './scrypt_storage'
import * as bip32 from 'bip32'
import { Bip32Provider } from './encrypted_mnemonic_provider'

export class EncryptedBip32Provider implements Bip32Provider {
/**
* @param {ScryptStorage} scryptStorage to store encrypted mnemonic seed
* @param {Bip32Options} prefixOptions to reconstruct Bip32Interface when hdnode unlocked with passphrase
* @param {Buffer} seedHash to verify new node derivation is using a valid seed
*/
constructor (
private readonly collectPassphrase: () => Promise<string>,
private readonly scryptStorage: ScryptStorage,
private readonly options: Bip32Options
) {}

async get (): Promise<bip32.BIP32Interface> {
const passphrase = await this.collectPassphrase()
const seed = await this.scryptStorage.decrypt(passphrase)
if (seed === null) {
throw new Error('No encrypted seed found in storage')
}
return bip32.fromSeed(seed, this.options)
}
}
Loading