Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

213 provide hd wallet implementation #363

Merged
merged 22 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
20a116d
#213 rough sketch of needed functionality as a discussion basis
Kammerlo Jan 16, 2024
52182b1
still work in progress
Kammerlo Jan 16, 2024
20e49d1
Merge branch 'master' into 213-provide-hd-wallet-implementation
Kammerlo Jan 16, 2024
1f7b4d5
merged master
Kammerlo Jan 17, 2024
bb48752
basic transaction working now. Still need to refactor a lot! But firs…
Kammerlo Jan 17, 2024
b14741a
#213 added WalletUtxoSupplier and removed getUtxo functionalities fro…
Kammerlo Jan 18, 2024
dcd184f
#213 added stakekey registrations and address caching to wallet
Kammerlo Jan 19, 2024
596488a
#213 added Tests, WalletUtxoSupplier interface and DefaultWalletUtxoS…
Kammerlo Jan 19, 2024
64df79e
#213 implemented signing with wallet. A UTXOSupplier will be passed.
Kammerlo Jan 22, 2024
9c0ec6e
Merge branch 'master' into 213-provide-hd-wallet-implementation
Kammerlo Jan 22, 2024
242f30c
Merge branch 'master' into 213-provide-hd-wallet-implementation
satran004 Jan 31, 2024
4e98c77
Merge with master
satran004 Nov 22, 2024
41c2615
Merge with master
satran004 Nov 22, 2024
588d3ae
Refactor Tx sender wallet handling and clean up AbstractTx.
satran004 Nov 24, 2024
3894bee
Refactor TxBuilderContext and QuickTxBuilder
satran004 Nov 24, 2024
bc3ee91
Refactor TxSigner to include TxBuilderContext
satran004 Nov 24, 2024
d444737
Normalize mnemonic phrase whitespaces
satran004 Nov 24, 2024
fdf1c4a
Update logging dependency to reload4j in build.gradle
satran004 Nov 24, 2024
a65b76d
Refactor: Change MnemonicUtil import path
satran004 Nov 24, 2024
187351f
Refactor Wallet signing and UTXO handling
satran004 Nov 24, 2024
5316e90
Refactor Wallet class and update related tests
satran004 Nov 25, 2024
0f10f3f
fix: changes to fix SonarQube errors
satran004 Nov 25, 2024
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
Expand Up @@ -2,16 +2,14 @@

import com.bloxbean.cardano.client.address.Address;
import com.bloxbean.cardano.client.address.AddressProvider;
import com.bloxbean.cardano.client.crypto.MnemonicUtil;
import com.bloxbean.cardano.client.address.Credential;
import com.bloxbean.cardano.client.common.model.Network;
import com.bloxbean.cardano.client.common.model.Networks;
import com.bloxbean.cardano.client.crypto.bip32.HdKeyPair;
import com.bloxbean.cardano.client.crypto.bip39.MnemonicCode;
import com.bloxbean.cardano.client.crypto.bip39.MnemonicException;
import com.bloxbean.cardano.client.crypto.bip39.Words;
import com.bloxbean.cardano.client.crypto.cip1852.CIP1852;
import com.bloxbean.cardano.client.crypto.cip1852.DerivationPath;
import com.bloxbean.cardano.client.exception.AddressRuntimeException;
import com.bloxbean.cardano.client.exception.CborDeserializationException;
import com.bloxbean.cardano.client.exception.CborSerializationException;
import com.bloxbean.cardano.client.governance.keys.CommitteeColdKey;
Expand All @@ -22,9 +20,6 @@
import com.bloxbean.cardano.client.util.HexUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.Arrays;
import java.util.stream.Collectors;

/**
* Create and manage secrets, and perform account-based work such as signing transactions.
*/
Expand Down Expand Up @@ -87,7 +82,8 @@ public Account(Network network, int index) {
public Account(Network network, DerivationPath derivationPath, Words noOfWords) {
this.network = network;
this.derivationPath = derivationPath;
generateNew(noOfWords);
this.mnemonic = MnemonicUtil.generateNew(noOfWords);
baseAddress();
}

/**
Expand Down Expand Up @@ -141,7 +137,7 @@ public Account(Network network, String mnemonic, DerivationPath derivationPath)
this.mnemonic = mnemonic;
this.accountKey = null;
this.derivationPath = derivationPath;
validateMnemonic();
MnemonicUtil.validateMnemonic(this.mnemonic);
baseAddress();
}

Expand Down Expand Up @@ -485,32 +481,6 @@ public Transaction signWithCommitteeHotKey(Transaction transaction) {
return TransactionSigner.INSTANCE.sign(transaction, getCommitteeHotKeyPair());
}

private void generateNew(Words noOfWords) {
String mnemonic = null;
try {
mnemonic = MnemonicCode.INSTANCE.createMnemonic(noOfWords).stream().collect(Collectors.joining(" "));
} catch (MnemonicException.MnemonicLengthException e) {
throw new RuntimeException("Mnemonic generation failed", e);
}
this.mnemonic = mnemonic;
baseAddress();
}

private void validateMnemonic() {
if (mnemonic == null) {
throw new AddressRuntimeException("Mnemonic cannot be null");
}

mnemonic = mnemonic.replaceAll("\\s+", " ");
String[] words = mnemonic.split("\\s+");

try {
MnemonicCode.INSTANCE.check(Arrays.asList(words));
} catch (MnemonicException e) {
throw new AddressRuntimeException("Invalid mnemonic phrase", e);
}
}

private HdKeyPair getHdKeyPair() {
HdKeyPair hdKeyPair;
if (mnemonic == null || mnemonic.trim().length() == 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.bloxbean.cardano.client.crypto;

import com.bloxbean.cardano.client.crypto.bip39.MnemonicCode;
import com.bloxbean.cardano.client.crypto.bip39.MnemonicException;
import com.bloxbean.cardano.client.crypto.bip39.Words;
import com.bloxbean.cardano.client.exception.AddressRuntimeException;

import java.util.Arrays;
import java.util.stream.Collectors;

public class MnemonicUtil {

private MnemonicUtil() {

}

public static void validateMnemonic(String mnemonic) {
if (mnemonic == null) {
throw new AddressRuntimeException("Mnemonic cannot be null");
}

mnemonic = mnemonic.replaceAll("\\s+", " ");
String[] words = mnemonic.split("\\s+");

try {
MnemonicCode.INSTANCE.check(Arrays.asList(words));
} catch (MnemonicException e) {
throw new AddressRuntimeException("Invalid mnemonic phrase", e);
}
}

public static String generateNew(Words noOfWords) {
String mnemonic = null;
try {
mnemonic = MnemonicCode.INSTANCE.createMnemonic(noOfWords).stream().collect(Collectors.joining(" "));
} catch (MnemonicException.MnemonicLengthException e) {
throw new AddressRuntimeException("Mnemonic generation failed", e);
}
return mnemonic;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ public static byte[] toSeed(List<String> words, String passphrase) {

public byte[] toEntropy(String mnemonicPhrase) throws MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException, MnemonicException.MnemonicChecksumException {
String[] wordsList;
wordsList = mnemonicPhrase.split(" ");

mnemonicPhrase = mnemonicPhrase.replaceAll("\\s+", " ");
wordsList = mnemonicPhrase.split("\\s+");

return toEntropy(Arrays.asList(wordsList));
}
Expand Down
1 change: 1 addition & 0 deletions function/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
api project(':core')
api project(':hd-wallet')
}

publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,7 @@ public static TxBuilderContext init(UtxoSupplier utxoSupplier, ProtocolParamsSup
* @throws com.bloxbean.cardano.client.function.exception.TxBuildException if exception during transaction build
*/
public Transaction build(TxBuilder txBuilder) {
Transaction transaction = new Transaction();
transaction.setEra(getSerializationEra());
txBuilder.apply(this, transaction);
Transaction transaction = buildTransaction(txBuilder);
clearTempStates();
return transaction;
}
Expand All @@ -282,8 +280,10 @@ public Transaction build(TxBuilder txBuilder) {
* @throws com.bloxbean.cardano.client.function.exception.TxBuildException if exception during transaction build
*/
public Transaction buildAndSign(TxBuilder txBuilder, TxSigner signer) {
Transaction transaction = build(txBuilder);
return signer.sign(transaction);
Transaction transaction = buildTransaction(txBuilder);
Transaction signedTransaction = signer.sign(this, transaction);
clearTempStates();
return signedTransaction;
}

/**
Expand All @@ -297,6 +297,13 @@ public void build(Transaction transaction, TxBuilder txBuilder) {
clearTempStates();
}

private Transaction buildTransaction(TxBuilder txBuilder) {
Transaction transaction = new Transaction();
transaction.setEra(getSerializationEra());
txBuilder.apply(this, transaction);
return transaction;
}

private void clearTempStates() {
clearMintMultiAssets();
clearUtxos();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ public interface TxSigner {
/**
* Apply this function to sign a transaction
*
* @param transaction
* @param context {@link TxBuilderContext}
* @param transaction {@link Transaction} to sign
* @return a signed transaction
*/
Transaction sign(Transaction transaction);
Transaction sign(TxBuilderContext context, Transaction transaction);

/**
* Returns a composed function that first applies this function to
Expand All @@ -29,6 +30,6 @@ public interface TxSigner {
*/
default TxSigner andThen(TxSigner after) {
Objects.requireNonNull(after);
return (transaction) -> after.sign(sign(transaction));
return (context, transaction) -> after.sign(context, sign(context, transaction));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import com.bloxbean.cardano.client.transaction.TransactionSigner;
import com.bloxbean.cardano.client.transaction.spec.Policy;
import com.bloxbean.cardano.client.transaction.spec.Transaction;
import com.bloxbean.cardano.hdwallet.Wallet;
import com.bloxbean.cardano.hdwallet.model.WalletUtxo;

import java.util.stream.Collectors;

/**
* Provides helper methods to get TxSigner function to sign a <code>{@link Transaction}</code> object
Expand All @@ -20,7 +24,7 @@ public class SignerProviders {
*/
public static TxSigner signerFrom(Account... signers) {

return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Account signer : signers) {
outputTxn = signer.sign(outputTxn);
Expand All @@ -30,14 +34,30 @@ public static TxSigner signerFrom(Account... signers) {
};
}

/**
* Function to sign a transaction with a wallet
*
* @param wallet wallet to sign the transaction
* @return <code>TxSigner</code> function which returns a <code>Transaction</code> object with witnesses when invoked
*/
public static TxSigner signerFrom(Wallet wallet) {
return (context, transaction) -> {
var utxos = context.getUtxos()
.stream().filter(utxo -> utxo instanceof WalletUtxo)
.map(utxo -> (WalletUtxo) utxo)
.collect(Collectors.toSet());
return wallet.sign(transaction, utxos);
};
}

/**
* Function to sign a transaction with one or more <code>SecretKey</code>
* @param secretKeys secret keys to sign the transaction
* @return <code>TxSigner</code> function which returns a <code>Transaction</code> object with witnesses when invoked
*/
public static TxSigner signerFrom(SecretKey... secretKeys) {

return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (SecretKey sk : secretKeys) {
outputTxn = TransactionSigner.INSTANCE.sign(outputTxn, sk);
Expand All @@ -54,7 +74,7 @@ public static TxSigner signerFrom(SecretKey... secretKeys) {
*/
public static TxSigner signerFrom(Policy... policies) {

return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Policy policy : policies) {
for (SecretKey sk : policy.getPolicyKeys()) {
Expand All @@ -73,7 +93,7 @@ public static TxSigner signerFrom(Policy... policies) {
*/
public static TxSigner signerFrom(HdKeyPair... hdKeyPairs) {

return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (HdKeyPair hdKeyPair : hdKeyPairs) {
outputTxn = TransactionSigner.INSTANCE.sign(outputTxn, hdKeyPair);
Expand All @@ -90,7 +110,7 @@ public static TxSigner signerFrom(HdKeyPair... hdKeyPairs) {
*/
public static TxSigner stakeKeySignerFrom(Account... signers) {

return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Account signer : signers) {
outputTxn = signer.signWithStakeKey(outputTxn);
Expand All @@ -106,7 +126,7 @@ public static TxSigner stakeKeySignerFrom(Account... signers) {
* @return <code>TxSigner</code> function which returns a <code>Transaction</code> object with witnesses when invoked
*/
public static TxSigner drepKeySignerFrom(Account... signers) {
return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Account signer : signers) {
outputTxn = signer.signWithDRepKey(outputTxn);
Expand All @@ -116,14 +136,23 @@ public static TxSigner drepKeySignerFrom(Account... signers) {
};
}

public static TxSigner stakeKeySignerFrom(Wallet... wallets) {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Wallet wallet : wallets)
outputTxn = wallet.signWithStakeKey(outputTxn);
return outputTxn;
};
}

//TODO -- Add Integration test
/**
* Function to sign a transaction with one or more Committee Cold key(s) of <code>Account</code>(s)
* @param signers - account(s) to sign the transaction
* @return <code>TxSigner</code> function which returns a <code>Transaction</code> object with witnesses when invoked
*/
public static TxSigner committeeColdKeySignerFrom(Account... signers) {
return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Account signer : signers) {
outputTxn = signer.signWithCommitteeColdKey(outputTxn);
Expand All @@ -140,7 +169,7 @@ public static TxSigner committeeColdKeySignerFrom(Account... signers) {
* @return <code>TxSigner</code> function which returns a <code>Transaction</code> object with witnesses when invoked
*/
public static TxSigner committeeHotKeySignerFrom(Account... signers) {
return transaction -> {
return (context, transaction) -> {
Transaction outputTxn = transaction;
for (Account signer : signers) {
outputTxn = signer.signWithCommitteeHotKey(outputTxn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void signerFromAccounts() throws Exception {
Account account2 = new Account(Networks.testnet());

Transaction signedTxn = SignerProviders.signerFrom(account1, account2)
.sign(buildTransaction());
.sign(null, buildTransaction());

assertThat(signedTxn.getWitnessSet().getVkeyWitnesses()).hasSize(2);
}
Expand All @@ -37,7 +37,7 @@ void signerFromSecretKey() throws Exception {
SecretKey sk3 = KeyGenUtil.generateKey().getSkey();

Transaction signedTxn = SignerProviders.signerFrom(sk1, sk2, sk3)
.sign(buildTransaction());
.sign(null, buildTransaction());

assertThat(signedTxn.getWitnessSet().getVkeyWitnesses()).hasSize(3);
}
Expand All @@ -48,7 +48,7 @@ void signerFromPolicies() throws Exception {
Policy policy2 = PolicyUtil.createMultiSigScriptAllPolicy("2", 4);

Transaction signedTxn = SignerProviders.signerFrom(policy1, policy2)
.sign(buildTransaction());
.sign(null, buildTransaction());

assertThat(signedTxn.getWitnessSet().getVkeyWitnesses()).hasSize(7);
}
Expand All @@ -60,7 +60,7 @@ void signerFromHdKeyPairs() throws Exception {

Transaction signedTxn = SignerProviders.signerFrom(account1.stakeHdKeyPair(), account2.stakeHdKeyPair())
.andThen(SignerProviders.signerFrom(account1))
.sign(buildTransaction());
.sign(null, buildTransaction());

assertThat(signedTxn.getWitnessSet().getVkeyWitnesses()).hasSize(3);
}
Expand All @@ -72,7 +72,7 @@ void signerFromAccountStakeKeys() throws Exception {

Transaction signedTxn = SignerProviders.stakeKeySignerFrom(account1, account2)
.andThen(SignerProviders.signerFrom(account1))
.sign(buildTransaction());
.sign(null, buildTransaction());

assertThat(signedTxn.getWitnessSet().getVkeyWitnesses()).hasSize(3);
}
Expand Down
29 changes: 29 additions & 0 deletions hd-wallet/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
dependencies {
api project(':core-api')
api project(':core')
api project(':common')
api project(':crypto')
api project(':backend')
implementation(libs.bouncycastle.bcprov)

integrationTestImplementation(libs.slf4j.reload4j)
integrationTestImplementation(libs.aiken.java.binding)
integrationTestImplementation project(':')
integrationTestImplementation project(':backend-modules:blockfrost')
integrationTestImplementation project(':backend-modules:koios')
integrationTestImplementation project(':backend-modules:ogmios')
integrationTestImplementation project(':backend-modules:ogmios')

integrationTestAnnotationProcessor project(':annotation-processor')
}

publishing {
publications {
mavenJava(MavenPublication) {
pom {
name = 'Cardano Client HD Wallet'
description = 'Cardano Client Lib - HD Wallet Integration'
}
}
}
}
Loading
Loading