cardano-client-lib
cardano-client-lib copied to clipboard
Add impl to generate child public key from parent public key when not hardened
Hello, I am wondering if this is something that is currently being worked?
I see that your implementation of getChildKeyPair in the file https://github.com/bloxbean/cardano-client-lib/blob/master/src/main/java/com/bloxbean/cardano/client/crypto/bip32/HdKeyGenerator.java contains most of the implementation that would be needed for this feature. Looking at the BIP32 documentation, I believe the only thing that needs to be added is the, "The returned child key Ki is point(parse256(IL)) + Kpar" if I am not mistaken? Of course the getChildKeyPair would need to be adjusted to remove the private key logic, but I am wondering if this is the right track?
Sorry, I am new to the key derivation piece. The Ki definition above may be just the identification of what Ki is. The Hmac.hmac512(data, xChain) may be the child public key, which would mean that the implementation is complete in the getChildKeyPair and that the private logic just needs to be separated out?
I am looking at the Public Key -> Public child key documentation for BIP32 located at https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#public-parent-key--public-child-key in regards to the Ki is point(parse256(IL)) + Kpar definition
I have implemented some of the logic to start working on this, and I am getting the derivation for the chain code to come out correct. But, I am not having luck with getting the public key to come out correct. I am sure I am doing something wrong with the math somewhere. `
public HdPublicKey getPublicChildKey(HdPublicKey parent, long child) {
HdPublicKey publicKey = new HdPublicKey();
byte[] childNumber = BytesUtil.ser32LE(child);
publicKey.setVersion(parent.getVersion());
publicKey.setDepth(parent.getDepth() + 1);
publicKey.setChildNumber(childNumber);
//If derivation V2 Shelley
byte[] AP = parent.getKeyData();
byte[] cP = parent.getChainCode();
byte[] I;
// addes a header byte of value 3 || SERp(Kpar) || SER32(i)
byte[] data = BytesUtil.merge(new byte[]{3}, AP, childNumber);
I = Hmac.hmac512(data, cP);
byte[] IL = Arrays.copyOfRange(I, 0, 32);
byte[] IR = Arrays.copyOfRange(I, 32, 64);
publicKey.setChainCode(IR); // verified to be correct chain code
GroupElement gp1 = new GroupElement(ED25519SPEC.getCurve(), AP);
gp1 = gp1.toCached();
byte[] Ki = ED25519SPEC.getB().scalarMultiply(IL).add(gp1).toByteArray();
publicKey.setKeyData(Ki);
return publicKey;
}
`
@satran004 do you know what I am doing wrong with the math here? I pretty much have just adjusted what is being used in the getChildKeyPair
@zack-scott Thanks for trying this out.
I had faced some issue while trying to implement this earlier. As I am traveling now, I have limited access to my laptop for next few days. I will try to verify your impl with the spec next week.
@satran004 looking at the EC Math library and the implementation you used along with the BIP32 docs. I believe to derive the public key from a public key you will need to do the scalarMultiply on IL and then add the Parent key data to the Group Element then convert back to a byte array. I have adjusted the code above to reflect that, but I am still running into an issue of it not matching the expected value. Thank you for taking the time to respond. I will continue adjusting the bits until hopefully it aligns 👍. I think my GroupElement I am creating may be wrong, which is throwing off the add.
@zack-scott Finally I am able to verify it.
Please find the modified code below with a test. Need some more tests.
Please feel free to create a PR after your verification.
I have done few changes.
- For child public key, only 28 bytes taken for left side (ZL)
- For hmac512(), data should be with prefix 0x02 for child public key and with prefix 0x03 for child chain code calculation
- Some minor changes (variable renaming etc.)
Here's the link to the bip32-ed25519 spec. I am referring to section "D. Public child key". https://raw.githubusercontent.com/LedgerHQ/orakolo/master/papers/Ed25519_BIP%20Final.pdf
Impl
public HdPublicKey getChildPublicKey(HdPublicKey parent, long child) {
HdPublicKey publicKey = new HdPublicKey();
byte[] AP = parent.getKeyData();
byte[] pChain = parent.getChainCode();
byte[] childNumber = BytesUtil.ser32(child);
//prefix 0x02 for child public key
byte[] ApLE = serializeUnsignedLE256(parseUnsignedLE(AP));
byte[] data = BytesUtil.merge(new byte[]{2}, ApLE, BytesUtil.ser32LE(child));
byte[] Z = Hmac.hmac512(data, pChain);
//prefix 0x03 for child chain code
data[0] = 3;
byte[] c = Hmac.hmac512(data, parent.getChainCode());
//truncate to right 32 bytes for child chain code
c = Arrays.copyOfRange(c, 32, 64);
// split into left (28 bytes) /right (for child public key)
byte[] ZL = Arrays.copyOfRange(Z, 0, 28);
// byte[] ZR = Arrays.copyOfRange(Z, 32, 64);
//Ai ← AP + [8ZL]B,
BigInteger kLiBI = parseUnsignedLE(ZL)
.multiply(BigInteger.valueOf(8));
byte[] kLi = serializeUnsignedLE256(kLiBI);
GroupElement gp1 = new GroupElement(ED25519SPEC.getCurve(), AP);
gp1 = gp1.toCached();
GroupElement groupElement = ED25519SPEC.getB().scalarMultiply(kLi).add(gp1);
//TODO -- If Ai is the identity point (0, 1), discard the child
byte[] Ai = groupElement.toByteArray(); //child public key
publicKey.setVersion(parent.getVersion());
publicKey.setDepth(parent.getDepth() + 1);
publicKey.setChildNumber(childNumber);
publicKey.setChainCode(c);
publicKey.setKeyData(Ai);
return publicKey;
}
Test
@Test
void testPubKeyFromParentPubKey() {
String mnemonicPhrase = "indicate traffic belt syrup chief accident put upset present short drink bus glide warm roof";
Account account = new Account(mnemonicPhrase,2);
HdKeyPair hdKeyPair = account.hdKeyPair();
HdKeyGenerator hdKeyGenerator = new HdKeyGenerator();
HdKeyPair childHdKeyPair = hdKeyGenerator.getChildKeyPair(hdKeyPair, 1, false);
HdPublicKey publicKey = hdKeyGenerator.getChildPublicKey(hdKeyPair.getPublicKey(), 1);
assertThat(publicKey.getKeyData()).isEqualTo(childHdKeyPair.getPublicKey().getKeyData());
}
@satran004 thank you for the response. I was looking at the wrong BIP32 implementation. Sorry, I am new to the space, and I appreciate the pdf you provided. I will review this and make a PR.
@zack-scott fyi, I have created a separate issue to track identity point verification
https://github.com/bloxbean/cardano-client-lib/issues/129