Skip to content

Commit

Permalink
Merge branch 'main' into docs/vote-crypto-proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Leshiy authored Sep 23, 2024
2 parents 0cf3c2d + b2a1dde commit d0b6127
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// cspell: words wordlists WORDLIST
// ignore_for_file: implementation_imports

import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bip39/src/wordlists/english.dart';
import 'package:convert/convert.dart';

/// Represents a seed phrase consisting of a mnemonic and provides methods for
/// generating and deriving cryptographic data from the mnemonic.
///
/// The `SeedPhrase` class allows creation of a seed phrase either randomly,
/// from a given mnemonic, or from entropy data. It supports converting between
/// different formats, including Uint8List and hex strings.
class SeedPhrase {
/// The mnemonic phrase
final String mnemonic;

/// Generates a new seed phrase with a random mnemonic.
///
/// Throws an [ArgumentError] if the word count is invalid.
///
/// [wordCount]: The number of words in the mnemonic.
/// The default word count is 12, but can specify 12, 15, 18, 21, or 24 words.
/// with a higher word count providing greater entropy and security.
SeedPhrase({int wordCount = 12})
: this.fromMnemonic(
bip39.generateMnemonic(
strength: (wordCount * 32) ~/ 3,
),
);

/// Creates a SeedPhrase from an existing [Uint8List] entropy.
///
/// [encodedData]: The entropy data as a Uint8List.
SeedPhrase.fromUint8ListEntropy(Uint8List encodedData)
: this.fromHexEntropy(hex.encode(encodedData));

/// Creates a SeedPhrase from an existing hex-encoded entropy.
///
/// [encodedData]: The entropy data as a hex string.
SeedPhrase.fromHexEntropy(String encodedData)
: this.fromMnemonic(bip39.entropyToMnemonic(encodedData));

/// Creates a SeedPhrase from an existing [mnemonic].
///
/// Throws an [ArgumentError] if the mnemonic is invalid.
///
/// [mnemonic]: The mnemonic to derive the seed from.
SeedPhrase.fromMnemonic(this.mnemonic)
: assert(bip39.validateMnemonic(mnemonic), 'Invalid mnemonic phrase');

/// The seed derived from the mnemonic as a Uint8List.
Uint8List get uint8ListSeed => bip39.mnemonicToSeed(mnemonic);

/// The seed derived from the mnemonic as a hex-encoded string.
String get hexSeed => bip39.mnemonicToSeedHex(mnemonic);

/// The entropy derived from the mnemonic as a Uint8List.
Uint8List get uint8ListEntropy => Uint8List.fromList(hex.decode(hexEntropy));

/// The entropy derived from the mnemonic as a hex-encoded string.
String get hexEntropy => bip39.mnemonicToEntropy(mnemonic);

/// The mnemonic phrase as a list of individual words.
List<String> get mnemonicWords => mnemonic.split(' ');

/// The full list of BIP-39 mnemonic words in English.
static List<String> get wordList => WORDLIST;
}
2 changes: 2 additions & 0 deletions catalyst_voices/packages/catalyst_voices_models/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ environment:
sdk: ">=3.5.0 <4.0.0"

dependencies:
bip39: ^1.0.6
catalyst_cardano_serialization: ^0.4.0
convert: ^3.1.1
equatable: ^2.0.5
flutter_quill: ^10.5.13
meta: ^1.10.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:catalyst_voices_models/src/seed_phrase.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group(SeedPhrase, () {
test('should generate a new SeedPhrase with random mnemonic', () {
final seedPhrase = SeedPhrase();
expect(seedPhrase.mnemonic, isNotEmpty);
expect(seedPhrase.uint8ListSeed, isNotEmpty);
expect(seedPhrase.hexSeed, isNotEmpty);
expect(seedPhrase.mnemonicWords.length, 12);
});

test('should generate a seed phrase with 12, 15, 18, 21, and 24 words', () {
for (final wordCount in [12, 15, 18, 21, 24]) {
final seedPhrase = SeedPhrase(wordCount: wordCount);
expect(seedPhrase.mnemonicWords.length, wordCount);
expect(bip39.validateMnemonic(seedPhrase.mnemonic), isTrue);
}
});

test('should throw an error for an invalid word count', () {
expect(() => SeedPhrase(wordCount: 9), throwsA(isA<ArgumentError>()));
expect(() => SeedPhrase(wordCount: 13), throwsA(isA<AssertionError>()));
expect(() => SeedPhrase(wordCount: 27), throwsA(isA<ArgumentError>()));
});

test('should create SeedPhrase from a valid mnemonic', () {
final validMnemonic = bip39.generateMnemonic();
final seedPhrase = SeedPhrase.fromMnemonic(validMnemonic);
expect(seedPhrase.mnemonic, validMnemonic);
expect(seedPhrase.hexSeed, bip39.mnemonicToSeedHex(validMnemonic));
});

test('should create SeedPhrase from hex-encoded entropy', () {
final entropy = bip39.mnemonicToEntropy(bip39.generateMnemonic());
final seedPhrase = SeedPhrase.fromHexEntropy(entropy);

expect(seedPhrase.mnemonic, isNotEmpty);
expect(seedPhrase.hexEntropy, entropy);
});

test('should throw an error for invalid mnemonic', () {
const invalidMnemonic = 'invalid mnemonic phrase';
expect(
() => SeedPhrase.fromMnemonic(invalidMnemonic),
throwsA(
isA<AssertionError>().having(
(e) => e.message,
'message',
contains('Invalid mnemonic phrase'),
),
),
);
});

test('should contain consistent mnemonic and seed in generated SeedPhrase',
() {
final seedPhrase = SeedPhrase();
final mnemonic = seedPhrase.mnemonic;
final seed = seedPhrase.hexSeed;

expect(bip39.mnemonicToSeedHex(mnemonic), seed);
});

test('should split mnemonic into a list of words', () {
final mnemonic = bip39.generateMnemonic();
final seedPhrase = SeedPhrase.fromMnemonic(mnemonic);
final expectedWords = mnemonic.split(' ');
expect(seedPhrase.mnemonicWords, expectedWords);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ Byron era addresses are not supported.
| 5 = reward withdrawals | ❌️ |
| 6 = protocol parameter update | ❌️ |
| 7 = auxiliary_data_hash | ✔️ |
| 8 = validity interval start | ️ |
| 9 = mint | ️ |
| 11 = script_data_hash | ️ |
| 13 = collateral inputs | ️ |
| 8 = validity interval start | ️ |
| 9 = mint | ️ |
| 11 = script_data_hash | ️ |
| 13 = collateral inputs | ️ |
| 14 = required signers | ✔️ |
| 15 = network_id | ✔️ |
| 16 = collateral return | ️ |
| 17 = total collateral | ️ |
| 18 = reference inputs | ️ |
| 16 = collateral return | ️ |
| 17 = total collateral | ️ |
| 18 = reference inputs | ️ |

## Reference documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,34 @@ final class TransactionBuilder extends Equatable {
/// The transaction metadata as a list of key-value pairs (a map).
final AuxiliaryData? auxiliaryData;

/// The list of public key hashes of addresses
/// that are required to sign the transaction.
/// Validity interval start as integer.
final SlotBigNum? validityStart;

/// Mint as a non-zero uint64 multiasset.
final MultiAsset? mint;

/// The transaction metadata as a list of key-value pairs (a map).
final ScriptData? scriptData;

/// Collateral inputs as nonempty set.
final Set<TransactionInput>? collateralInputs;

/// The list of public key hashes of addresses that are required to sign the
/// transaction. A nonempty set of addr key hashes.
final Set<Ed25519PublicKeyHash>? requiredSigners;

/// Specifies on which network the code will run.
final NetworkId? networkId;

/// Collateral return's transaction output.
final ShelleyMultiAssetTransactionOutput? collateralReturn;

/// Total collateral as coin (uint64).
final Coin? totalCollateral;

/// Reference inputs as nonempty set of transaction inputs.
final Set<TransactionInput>? referenceInputs;

/// The builder that builds the witness set of the transaction.
///
/// The caller must know in advance how many witnesses there will be to
Expand All @@ -60,8 +81,15 @@ final class TransactionBuilder extends Equatable {
this.fee,
this.ttl,
this.auxiliaryData,
this.validityStart,
this.mint,
this.scriptData,
this.collateralInputs,
this.requiredSigners,
this.networkId,
this.collateralReturn,
this.totalCollateral,
this.referenceInputs,
this.witnessBuilder = const TransactionWitnessSetBuilder(
vkeys: {},
vkeysCount: 1,
Expand Down Expand Up @@ -232,8 +260,15 @@ final class TransactionBuilder extends Equatable {
fee,
ttl,
auxiliaryData,
validityStart,
mint,
scriptData,
collateralInputs,
requiredSigners,
networkId,
collateralReturn,
totalCollateral,
referenceInputs,
witnessBuilder,
];

Expand Down Expand Up @@ -476,8 +511,17 @@ final class TransactionBuilder extends Equatable {
auxiliaryDataHash: auxiliaryData != null
? AuxiliaryDataHash.fromAuxiliaryData(auxiliaryData!)
: null,
validityStart: validityStart,
mint: mint,
scriptDataHash: scriptData != null
? ScriptDataHash.fromScriptData(scriptData!)
: null,
collateralInputs: collateralInputs,
requiredSigners: requiredSigners,
networkId: networkId,
collateralReturn: collateralReturn,
totalCollateral: totalCollateral,
referenceInputs: referenceInputs,
);
}

Expand All @@ -493,8 +537,15 @@ final class TransactionBuilder extends Equatable {
fee: fee ?? this.fee,
ttl: ttl,
auxiliaryData: auxiliaryData,
validityStart: validityStart,
mint: mint,
scriptData: scriptData,
collateralInputs: collateralInputs,
requiredSigners: requiredSigners,
networkId: networkId,
collateralReturn: collateralReturn,
totalCollateral: totalCollateral,
referenceInputs: referenceInputs,
witnessBuilder: witnessBuilder ?? this.witnessBuilder,
);
}
Expand Down
1 change: 1 addition & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ command:
flutter: ">=3.24.1"
dependencies:
asn1lib: ^1.5.3
bip39: ^1.0.6
bloc_concurrency: ^0.2.2
collection: ^1.18.0
cryptography: ^2.7.0
Expand Down

0 comments on commit d0b6127

Please sign in to comment.