Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 34 additions & 15 deletions lib/src/bip/bip39/mnemonic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ class Mnemonic extends Equatable {
final List<String> mnemonicList;

// Validation of a mnemonic phrase requires to check its length, checksum and words.
// To do this before the object is created - in the factory constructor, all public methods contained in this class must be static,
// Such a solution would be less readable and less intuitive. For that reason, the validation and exception throwing are done in the constructor.
// To do this before a Mnemonic object is created, you can use the isValidMnemonic static method.
// Exceptions are left in the constructor mainly in case we want to tell a user the reason why a mnemonic is invalid.
Mnemonic(this.mnemonicList) {
if (mnemonicList.length % 3 != 0 || mnemonicList.isEmpty) {
throw const MnemonicException(MnemonicExceptionType.invalidLength);
} else if (_mnemonicListIndexes.contains(_mnemonicWordNotFoundInDictionary)) {
} else if (_parseWordsToWordIndexes(mnemonicList).contains(_mnemonicWordNotFoundInDictionary)) {
throw const MnemonicException(MnemonicExceptionType.invalidWord);
}

String extractedChecksum = _extractChecksum();
String extractedChecksum = _extractChecksum(mnemonicList);
String calculatedChecksum = _calculateChecksum(entropy);

if (extractedChecksum != calculatedChecksum) {
Expand Down Expand Up @@ -57,9 +57,30 @@ class Mnemonic extends Equatable {
return Mnemonic(mnemonicList);
}

/// Validates a mnemonic phrase without creating an object of the Mnemonic class.
static bool isValidMnemonic(List<String> mnemonicList) {
Uint8List entropy = _calcEntropy(mnemonicList);

String extractedChecksum = _extractChecksum(mnemonicList);
String calculatedChecksum = _calculateChecksum(entropy);

if (extractedChecksum != calculatedChecksum || extractedChecksum == '0') {
return false;
}

return true;
}

@override
String toString() {
return mnemonicList.join(' ');
}

Uint8List get entropy => _calcEntropy(mnemonicList);

/// Returns the entropy of the mnemonic phrase.
Uint8List get entropy {
String mnemonicBits = _mnemonicBits;
static Uint8List _calcEntropy(List<String> mnemonicList) {
String mnemonicBits = _parseWordsToBits(mnemonicList);

int entropyStartIndex = 0;
int entropyEndIndex = (mnemonicBits.length / 33).floor() * 32;
Expand All @@ -81,8 +102,8 @@ class Mnemonic extends Equatable {
}

/// Returns binary checksum included in the last word of the mnemonic phrase.
String _extractChecksum() {
String mnemonicBits = _mnemonicBits;
static String _extractChecksum(List<String> mnemonicList) {
String mnemonicBits = _parseWordsToBits(mnemonicList);

int checksumStartIndex = (mnemonicBits.length / 33).floor() * 32;
int checksumEndIndex = mnemonicBits.length;
Expand All @@ -93,20 +114,18 @@ class Mnemonic extends Equatable {
}

/// Returns the combined mnemonic words in binary format.
String get _mnemonicBits {
List<String> mnemonicListBinaries = _mnemonicListIndexes.map((int index) => BinaryUtils.intToBinary(index, padding: 11)).toList();
static String _parseWordsToBits(List<String> mnemonicList) {
List<int> mnemonicListIndexes = mnemonicList.map(MnemonicDictionary.english.indexOf).toList();
List<String> mnemonicListBinaries = mnemonicListIndexes.map((int index) => BinaryUtils.intToBinary(index, padding: 11)).toList();
String mnemonicBits = mnemonicListBinaries.join('');

return mnemonicBits;
}

/// Returns dictionary indexes of the mnemonic words.
/// If a word is not found in the dictionary, it will be represented as -1.
List<int> get _mnemonicListIndexes => mnemonicList.map(MnemonicDictionary.english.indexOf).toList();

@override
String toString() {
return mnemonicList.join(' ');
static List<int> _parseWordsToWordIndexes(List<String> mnemonicList) {
return mnemonicList.map(MnemonicDictionary.english.indexOf).toList();
}

@override
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: cryptography_utils
description: "Dart package containing utility methods for common cryptographic and blockchain-specific operations"
publish_to: none
version: 0.0.21
version: 0.0.22

environment:
sdk: ">=3.2.6"
Expand Down
47 changes: 47 additions & 0 deletions test/bip/bip39/mnemonic_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ void main() {
group('Tests of Mnemonic() constructor', () {
test('Should [return Mnemonic] from given mnemonic phrase', () {
// Arrange
// @formatter:off
List<String> actualMnemonicList = <String>['catalog', 'letter', 'frown', 'ramp', 'chest', 'van', 'pole', 'unfold', 'sound', 'unable', 'cool', 'endorse'];
// @formatter:on

// Act
Mnemonic actualMnemonic = Mnemonic(actualMnemonicList);
Expand Down Expand Up @@ -224,6 +226,51 @@ void main() {
});
});

group('Tests of Mnemonic.isValidMnemonic()', () {
test('Should [return TRUE] for a valid mnemonic', () {
// Arrange
// @formatter:off
List<String> mnemonicList = <String>['catalog', 'letter', 'frown', 'ramp', 'chest', 'van', 'pole', 'unfold', 'sound', 'unable', 'cool', 'endorse'];
// @formatter:on

// Act
bool actualValidBool = Mnemonic.isValidMnemonic(mnemonicList);

// Assert
bool expectedValidBool = true;

expect(actualValidBool, expectedValidBool);
});

test('Should [return FALSE] if mnemonic phrase has invalid length', () {
// Arrange
List<String> mnemonicList = <String>['catalog', 'letter', 'frown'];

// Act
bool actualValidBool = Mnemonic.isValidMnemonic(mnemonicList);

// Assert
bool expectedValidBool = false;

expect(actualValidBool, expectedValidBool);
});

test('Should [return FALSE] if mnemonic phrase has invalid checksum', () {
// Arrange
// @formatter:off
List<String> mnemonicList = <String>['attend', 'piano', 'mail', 'clap', 'argue', 'square', 'effort', 'cause', 'cook', 'onion', 'mouse', 'delay' ];
// @formatter:on

// Act
bool actualValidBool = Mnemonic.isValidMnemonic(mnemonicList);

// Assert
bool expectedValidBool = false;

expect(actualValidBool, expectedValidBool);
});
});

group('Tests of Mnemonic.entropy getter', () {
test('Should [return entropy] from [12-word Mnemonic]', () {
// Arrange
Expand Down
Loading