cardano-client-lib icon indicating copy to clipboard operation
cardano-client-lib copied to clipboard

Add impl to generate child public key from parent public key when not hardened

Open satran004 opened this issue 3 years ago • 7 comments

satran004 avatar Dec 17 '21 14:12 satran004

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

zack-scott avatar Jun 14 '22 16:06 zack-scott

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 avatar Jun 15 '22 21:06 zack-scott

@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 avatar Jun 16 '22 01:06 satran004

@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 avatar Jun 16 '22 13:06 zack-scott

@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 avatar Jun 23 '22 07:06 satran004

@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 avatar Jun 23 '22 13:06 zack-scott

@zack-scott fyi, I have created a separate issue to track identity point verification

https://github.com/bloxbean/cardano-client-lib/issues/129

satran004 avatar Jun 24 '22 07:06 satran004