Skip to content

Commit

Permalink
feat(address): #256 Add helper to generate stake address from account…
Browse files Browse the repository at this point in the history
… public key
  • Loading branch information
satran004 committed Jun 7, 2023
1 parent 2f7a767 commit 2283c11
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.bloxbean.cardano.client.address;

import com.bloxbean.cardano.client.common.model.Network;
import com.bloxbean.cardano.client.crypto.Bech32;
import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey;
import com.bloxbean.cardano.client.crypto.bip32.util.BytesUtil;
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.CborSerializationException;
import com.bloxbean.cardano.client.spec.NetworkId;
Expand All @@ -11,10 +14,7 @@
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;

import static com.bloxbean.cardano.client.address.util.AddressEncoderDecoderUtil.*;
import static com.bloxbean.cardano.client.crypto.Blake2bUtil.blake2bHash224;
Expand Down Expand Up @@ -490,6 +490,45 @@ public static Address getStakeAddress(@NonNull Address address) {
return new Address(rewardAddressBytes);
}

/**
* Get stake address from CIP-1852's extended account public key (Bech32 encoded) with prefix "acct_xvk" or "xpub"
*
* @param accountPubKey extended account public key (Bech32 encoded) with prefix "acct_xvk" or "xpub"
* @param network network
* @return stake address
*/
public static Address getStakeAddressFromAccountPublicKey(String accountPubKey, Network network) {
Objects.requireNonNull(accountPubKey, "accountPubKey cannot be null");
Objects.requireNonNull(network, "network cannot be null");

if (!accountPubKey.startsWith("acct_xvk") && !accountPubKey.startsWith("xpub")) {
throw new IllegalArgumentException("Invalid account public key. Must start with 'acct_xvk' or 'xpub'");
}

byte[] accountPubKeyBytes = Bech32.decode(accountPubKey).data;

return getStakeAddressFromAccountPublicKey(accountPubKeyBytes, network);
}

/**
* Get stake address from CIP-1852's extended account public key. Ed25519 public key with chain code.
*
* @param accountPubKeyBytes extended account public key. Ed25519 public key with chain code.
* @param network network
* @return stake address
*/
public static Address getStakeAddressFromAccountPublicKey(byte[] accountPubKeyBytes, Network network) {
Objects.requireNonNull(accountPubKeyBytes, "accountPubKeyBytes cannot be null");
Objects.requireNonNull(network, "network cannot be null");

if (accountPubKeyBytes.length != 64) {
throw new IllegalArgumentException("Invalid account public key");
}

HdPublicKey stakeHdPubKey = new CIP1852().getPublicKeyFromAccountPubKey(accountPubKeyBytes, DerivationPath.createStakeAddressDerivationPath());
return getRewardAddress(stakeHdPubKey, network);
}

/**
* Get StakeKeyHash or ScriptHash from delegation part of a Shelley {@link Address}
* @param address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1266,4 +1266,52 @@ void getRewardAddress_fromPaymentScriptCred() {

}

@Nested
class StakeAddressFromAccountPubKey {

@Test
void getStakeAddressFromAccountPubKey_acct_xvk() {
Address address = AddressProvider.getStakeAddressFromAccountPublicKey("acct_xvk136qnzfm6c34lddfxll60uqxwe3csymp8sdqvg3zcs79azchhqdn4qhs75qtwvuzjkd5h436fujcrgqgq2mnlmr5yse5zvewdj6flkgg5catgs",
Networks.testnet());
assertThat(address.toBech32()).isEqualTo("stake_test1uq28tn7xfckpaxxlfnhjz07r9nlwgcagzzza0r5la7hagycfa458m");
}

@Test
void getStakeAddressFromAccountPubKey_acct_xvk_2() {
Address address = AddressProvider.getStakeAddressFromAccountPublicKey("acct_xvk1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhepfxyysq4nydjqsjxj2dcsfc6ns6ljm2gqs6jh5vj58auceyfadsydvkn7",
Networks.testnet());
assertThat(address.toBech32()).isEqualTo("stake_test1uqu22dnnwvfrw0xwa3x3jux9p9z63ts5l0kmmn3jvttvg6gljr6y9");
}

@Test
void getStakeAddressFromAccountPubKey_xpub() {
Address address = AddressProvider.getStakeAddressFromAccountPublicKey("xpub136qnzfm6c34lddfxll60uqxwe3csymp8sdqvg3zcs79azchhqdn4qhs75qtwvuzjkd5h436fujcrgqgq2mnlmr5yse5zvewdj6flkgg88stt0",
Networks.testnet());
assertThat(address.toBech32()).isEqualTo("stake_test1uq28tn7xfckpaxxlfnhjz07r9nlwgcagzzza0r5la7hagycfa458m");
}

@Test
void getStakeAddressFromAccountPubKey_xpub_2() {
Address address = AddressProvider.getStakeAddressFromAccountPublicKey("xpub1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhepfxyysq4nydjqsjxj2dcsfc6ns6ljm2gqs6jh5vj58auceyfadshjpksp",
Networks.testnet());
assertThat(address.toBech32()).isEqualTo("stake_test1uqu22dnnwvfrw0xwa3x3jux9p9z63ts5l0kmmn3jvttvg6gljr6y9");
}

@Test
void getStakeAddressFromAccountPubKey_whenLessThan64Bytes_throwError() {
assertThrows(IllegalArgumentException.class, () -> {
AddressProvider.getStakeAddressFromAccountPublicKey("acct_vk1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhsk5ajve",
Networks.testnet());
});
}

@Test
void getStakeAddressFromAccountPubKey_invalidPrefix_throwError() {
assertThrows(IllegalArgumentException.class, () -> {
AddressProvider.getStakeAddressFromAccountPublicKey("xyz1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhepfxyysq4nydjqsjxj2dcsfc6ns6ljm2gqs6jh5vj58auceyfadshjpksp",
Networks.testnet());
});
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@
import com.bloxbean.cardano.client.crypto.CryptoException;
import com.bloxbean.cardano.client.crypto.bip32.HdKeyGenerator;
import com.bloxbean.cardano.client.crypto.bip32.HdKeyPair;
import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey;
import com.bloxbean.cardano.client.crypto.bip39.MnemonicCode;

/**
* CIP1852 helper class
*/
public class CIP1852 {

/**
* Get HdKeyPair from mnemonic phrase
* @param mnemonicPhrase mnemonic phrase
* @param derivationPath derivation path
* @return HdKeyPair
*/
public HdKeyPair getKeyPairFromMnemonic(String mnemonicPhrase, DerivationPath derivationPath) {
try {
byte[] entropy = MnemonicCode.INSTANCE.toEntropy(mnemonicPhrase);
Expand All @@ -17,6 +27,12 @@ public HdKeyPair getKeyPairFromMnemonic(String mnemonicPhrase, DerivationPath de
}
}

/**
* Get HdKeyPair from entropy
* @param entropy entropy
* @param derivationPath derivation path
* @return HdKeyPair
*/
public HdKeyPair getKeyPairFromEntropy(byte[] entropy, DerivationPath derivationPath) {
HdKeyGenerator hdKeyGenerator = new HdKeyGenerator();
HdKeyPair rootKeyPair = hdKeyGenerator.getRootKeyPairFromEntropy(entropy);
Expand All @@ -30,6 +46,12 @@ public HdKeyPair getKeyPairFromEntropy(byte[] entropy, DerivationPath derivation
return indexKey;
}

/**
* Get HdKeyPair from account key
* @param accountKey account key
* @param derivationPath derivation path
* @return HdKeyPair
*/
public HdKeyPair getKeyPairFromAccountKey(byte[] accountKey, DerivationPath derivationPath) {
HdKeyGenerator hdKeyGenerator = new HdKeyGenerator();

Expand All @@ -40,4 +62,20 @@ public HdKeyPair getKeyPairFromAccountKey(byte[] accountKey, DerivationPath deri
return indexKey;
}

/**
* Get HdPublicKey from account public key
* @param accountPubKey account public key
* @param derivationPath derivation path
* @return HdPublicKey
*/
public HdPublicKey getPublicKeyFromAccountPubKey(byte[] accountPubKey, DerivationPath derivationPath) {
HdPublicKey accountHdPubKey = HdPublicKey.fromBytes(accountPubKey);

HdKeyGenerator hdKeyGenerator = new HdKeyGenerator();
HdPublicKey roleHdPubKey = hdKeyGenerator.getChildPublicKey(accountHdPubKey, derivationPath.getRole().getValue());
HdPublicKey indexHdPubKey = hdKeyGenerator.getChildPublicKey(roleHdPubKey, derivationPath.getIndex().getValue());

return indexHdPubKey;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.bloxbean.cardano.client.crypto.Bech32;
import com.bloxbean.cardano.client.crypto.bip32.HdKeyPair;
import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey;
import com.bloxbean.cardano.client.util.HexUtil;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -48,4 +50,44 @@ void getStakeVerificationKey() {
assertThat(publicAdd).isEqualTo("stake_xvk143rnqx89nnmlt8w5kerl03hvl2reuv02l450wjs2vd74cezqx2mja08euhtd7gejfylpfe8j3vgejh25nu9nwqgfx0qy8d40llf9h6qeg2t4z");

}

@Test
void getPublicKeyFromAccountPubKey_acct_xvk() {
String accountPubKey = "acct_xvk136qnzfm6c34lddfxll60uqxwe3csymp8sdqvg3zcs79azchhqdn4qhs75qtwvuzjkd5h436fujcrgqgq2mnlmr5yse5zvewdj6flkgg5catgs";
Bech32.Bech32Data bech32Data = Bech32.decode(accountPubKey);
String expectedStakePubKey = "03ab2b878c8f8ec759cee22c321f86270bea486796fb5b881241cfa9254a60f32ee31c3e3529c64f8c801fa5f630b52dd11491ded35d180325cd94a1f80f7f2f";
HdPublicKey hdPublicKey = new CIP1852().getPublicKeyFromAccountPubKey(bech32Data.data, DerivationPath.createStakeAddressDerivationPath());

assertThat(HexUtil.encodeHexString(hdPublicKey.getBytes())).isEqualTo(expectedStakePubKey);
}

@Test
void getPublicKeyFromAccountPubKey_xpub() {
String accountPubKey = "xpub136qnzfm6c34lddfxll60uqxwe3csymp8sdqvg3zcs79azchhqdn4qhs75qtwvuzjkd5h436fujcrgqgq2mnlmr5yse5zvewdj6flkgg88stt0";
Bech32.Bech32Data bech32Data = Bech32.decode(accountPubKey);
String expectedStakePubKey = "03ab2b878c8f8ec759cee22c321f86270bea486796fb5b881241cfa9254a60f32ee31c3e3529c64f8c801fa5f630b52dd11491ded35d180325cd94a1f80f7f2f";
HdPublicKey hdPublicKey = new CIP1852().getPublicKeyFromAccountPubKey(bech32Data.data, DerivationPath.createStakeAddressDerivationPath());

assertThat(HexUtil.encodeHexString(hdPublicKey.getBytes())).isEqualTo(expectedStakePubKey);
}

@Test
void getPublicKeyFromAccountPubKey_acct_xvk_2() {
String accountPubKey = "acct_xvk1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhepfxyysq4nydjqsjxj2dcsfc6ns6ljm2gqs6jh5vj58auceyfadsydvkn7";
Bech32.Bech32Data bech32Data = Bech32.decode(accountPubKey);
String expectedStakePubKey = "23cebbe2b5707bb3f4255ca44398556925f3f6dc2b5f8c6f2f1b27f252dbbc12de7ea31330158d2b2f163c456416160ef8c8739a2336030f770314277fb1c9f8";
HdPublicKey hdPublicKey = new CIP1852().getPublicKeyFromAccountPubKey(bech32Data.data, DerivationPath.createStakeAddressDerivationPath());

assertThat(HexUtil.encodeHexString(hdPublicKey.getBytes())).isEqualTo(expectedStakePubKey);
}

@Test
void getPublicKeyFromAccountPubKey_xpub_2() {
String accountPubKey = "xpub1zxnrf4j4xzvxwwkmsjsrvtgv6g5q4l9yyskp807d62w5y6zvnmhepfxyysq4nydjqsjxj2dcsfc6ns6ljm2gqs6jh5vj58auceyfadshjpksp";
Bech32.Bech32Data bech32Data = Bech32.decode(accountPubKey);
String expectedStakePubKey = "23cebbe2b5707bb3f4255ca44398556925f3f6dc2b5f8c6f2f1b27f252dbbc12de7ea31330158d2b2f163c456416160ef8c8739a2336030f770314277fb1c9f8";
HdPublicKey hdPublicKey = new CIP1852().getPublicKeyFromAccountPubKey(bech32Data.data, DerivationPath.createStakeAddressDerivationPath());

assertThat(HexUtil.encodeHexString(hdPublicKey.getBytes())).isEqualTo(expectedStakePubKey);
}
}

0 comments on commit 2283c11

Please sign in to comment.