-
Notifications
You must be signed in to change notification settings - Fork 574
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ironfish): Create master key (#5375)
- Loading branch information
1 parent
57286b0
commit cf2941f
Showing
4 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { MasterKey } from './masterKey' | ||
|
||
describe('MasterKey', () => { | ||
it('can regenerate the master key from parts', async () => { | ||
const passphrase = 'foobar' | ||
const masterKey = MasterKey.generate(passphrase) | ||
const duplicate = new MasterKey({ nonce: masterKey.nonce, salt: masterKey.salt }) | ||
|
||
const key = await masterKey.unlock(passphrase) | ||
const reconstructed = await duplicate.unlock(passphrase) | ||
expect(key.key().equals(reconstructed.key())).toBe(true) | ||
}) | ||
|
||
it('can regenerate the child key from parts', async () => { | ||
const passphrase = 'foobar' | ||
const masterKey = MasterKey.generate(passphrase) | ||
await masterKey.unlock(passphrase) | ||
|
||
const childKey = masterKey.deriveNewKey() | ||
const duplicate = masterKey.deriveKey(childKey.salt(), childKey.nonce()) | ||
expect(childKey.key().equals(duplicate.key())).toBe(true) | ||
}) | ||
|
||
it('can save and remove the xchacha20poly1305 in memory', async () => { | ||
const passphrase = 'foobar' | ||
const masterKey = MasterKey.generate(passphrase) | ||
|
||
await masterKey.unlock(passphrase) | ||
expect(masterKey['masterKey']).not.toBeNull() | ||
|
||
await masterKey.lock() | ||
expect(masterKey['masterKey']).toBeNull() | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { xchacha20poly1305 } from '@ironfish/rust-nodejs' | ||
import { Assert } from '../assert' | ||
import { Mutex } from '../mutex' | ||
import { MasterKeyValue } from './walletdb/masterKeyValue' | ||
|
||
/** | ||
* A Master Key implementation for XChaCha20Poly1305. This class can be used | ||
* to derive child keys deterministically given the child key's salt and nonces. | ||
* | ||
* This master key does not automatically lock or unlock. You must call those | ||
* explicitly if you would like any default timeout behavior. | ||
*/ | ||
export class MasterKey { | ||
private mutex: Mutex | ||
private locked: boolean | ||
|
||
readonly salt: Buffer | ||
readonly nonce: Buffer | ||
|
||
private masterKey: xchacha20poly1305.XChaCha20Poly1305Key | null | ||
|
||
constructor(masterKeyValue: MasterKeyValue) { | ||
this.mutex = new Mutex() | ||
|
||
this.salt = masterKeyValue.salt | ||
this.nonce = masterKeyValue.nonce | ||
|
||
this.locked = true | ||
this.masterKey = null | ||
} | ||
|
||
static generate(passphrase: string): MasterKey { | ||
const key = new xchacha20poly1305.XChaCha20Poly1305Key(passphrase) | ||
return new MasterKey({ salt: key.salt(), nonce: key.nonce() }) | ||
} | ||
|
||
async lock(): Promise<void> { | ||
const unlock = await this.mutex.lock() | ||
|
||
try { | ||
if (this.masterKey) { | ||
this.masterKey.destroy() | ||
this.masterKey = null | ||
} | ||
|
||
this.locked = true | ||
} finally { | ||
unlock() | ||
} | ||
} | ||
|
||
async unlock(passphrase: string): Promise<xchacha20poly1305.XChaCha20Poly1305Key> { | ||
const unlock = await this.mutex.lock() | ||
|
||
try { | ||
this.masterKey = xchacha20poly1305.XChaCha20Poly1305Key.fromParts( | ||
passphrase, | ||
this.salt, | ||
this.nonce, | ||
) | ||
this.locked = false | ||
|
||
return this.masterKey | ||
} catch (e) { | ||
if (this.masterKey) { | ||
this.masterKey.destroy() | ||
this.masterKey = null | ||
} | ||
|
||
this.locked = true | ||
throw e | ||
} finally { | ||
unlock() | ||
} | ||
} | ||
|
||
deriveNewKey(): xchacha20poly1305.XChaCha20Poly1305Key { | ||
Assert.isFalse(this.locked) | ||
Assert.isNotNull(this.masterKey) | ||
|
||
return this.masterKey.deriveNewKey() | ||
} | ||
|
||
deriveKey(salt: Buffer, nonce: Buffer): xchacha20poly1305.XChaCha20Poly1305Key { | ||
Assert.isFalse(this.locked) | ||
Assert.isNotNull(this.masterKey) | ||
|
||
return this.masterKey.deriveKey(salt, nonce) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { xchacha20poly1305 } from '@ironfish/rust-nodejs' | ||
import { MasterKeyValue, NullableMasterKeyValueEncoding } from './masterKeyValue' | ||
|
||
describe('MasterKeyValueEncoding', () => { | ||
describe('with a defined value', () => { | ||
it('serializes the value into a buffer and deserializes to the original value', () => { | ||
const encoder = new NullableMasterKeyValueEncoding() | ||
|
||
const value: MasterKeyValue = { | ||
nonce: Buffer.alloc(xchacha20poly1305.XNONCE_LENGTH), | ||
salt: Buffer.alloc(xchacha20poly1305.XSALT_LENGTH), | ||
} | ||
const buffer = encoder.serialize(value) | ||
const deserializedValue = encoder.deserialize(buffer) | ||
expect(deserializedValue).toEqual(value) | ||
}) | ||
}) | ||
|
||
describe('with a null value', () => { | ||
it('serializes the value into a buffer and deserializes to the original value', () => { | ||
const encoder = new NullableMasterKeyValueEncoding() | ||
|
||
const value = null | ||
const buffer = encoder.serialize(value) | ||
const deserializedValue = encoder.deserialize(buffer) | ||
expect(deserializedValue).toEqual(value) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { xchacha20poly1305 } from '@ironfish/rust-nodejs' | ||
import bufio from 'bufio' | ||
import { IDatabaseEncoding } from '../../storage' | ||
|
||
export type MasterKeyValue = { | ||
nonce: Buffer | ||
salt: Buffer | ||
} | ||
|
||
export class NullableMasterKeyValueEncoding | ||
implements IDatabaseEncoding<MasterKeyValue | null> | ||
{ | ||
serialize(value: MasterKeyValue | null): Buffer { | ||
const bw = bufio.write(this.getSize(value)) | ||
|
||
if (value) { | ||
bw.writeBytes(value.nonce) | ||
bw.writeBytes(value.salt) | ||
} | ||
|
||
return bw.render() | ||
} | ||
|
||
deserialize(buffer: Buffer): MasterKeyValue | null { | ||
const reader = bufio.read(buffer, true) | ||
|
||
if (reader.left()) { | ||
const nonce = reader.readBytes(xchacha20poly1305.XNONCE_LENGTH) | ||
const salt = reader.readBytes(xchacha20poly1305.XSALT_LENGTH) | ||
return { nonce, salt } | ||
} | ||
|
||
return null | ||
} | ||
|
||
getSize(value: MasterKeyValue | null): number { | ||
if (!value) { | ||
return 0 | ||
} | ||
|
||
return xchacha20poly1305.XNONCE_LENGTH + xchacha20poly1305.XSALT_LENGTH | ||
} | ||
} |