Bitcoin Integration: Error building transaction when using custom derivation path—no error generating address nor with default derivation path

Your derivation path should only be 1 level deep for your application.

Bitcoin wallets like to use [[44, 223, 0, 0, 0, 0]] (EDIT: that was wrong, they use [[44],[223],[0],[0],[0],[0]])because of BIP44 but the first four of those levels are so-called “hardened derivation” which the threshold key technology cannot provide for fundamental cryptographic reasons. So there is no point to try to mimic any of those standards when doing derivations inside a canister.

If you want to derive one address per principal then you should keep a table (mapping) internally and map the principals to consecutive Nats (0,1,2,…). Then for a given principal you look up the principals number in the map. For principal number n you use derivation path [n]. That is one level deep, just the value n on the first level. n can be at most 4 bytes long. You have to encode n in big endian to type [Nat8]. For example with the code from here:

func fromNat(len : Nat, n : Nat) : [Nat8] {
    let ith_byte = func(i : Nat) : Nat8 {
        assert(i < len);
        let shift : Nat = 8 * (len - 1 - i);
        Nat8.fromIntWrap(n / 2**shift)
    };
    Array.tabulate<Nat8>(len, ith_byte)
};

You have to call it with len=4. So your derivation path will look like this [[a,b,c,d]] for some Nat8s a,b,c,d where [a,b,c,d] is the big endian encoding of n.

You can make the encoding more efficient of course if you start with n of type Nat32 since you already know the length (4 bytes). So the code above is just an example.

A deep derivation path is expensive. The threshold ECDSA functionality probably has a limit on how deep it can be.

3 Likes