I'm new to Bitcoin and have been making a serious attempt to learn it by programming bits & pieces of it in Python.
I recently learned about "split" keys and ran into something interesting that I thought I'd share here.
As I understand it, "split" keys emerge from the following identity (pseudocode):
Public(PrivateA) + Public(PrivateB) = Public(PrivateA + PrivateB)
This allows for surprising things like securely outsourcing vanity address generation or doing "multisig" with plain old P2PKH addresses.
I was adding a test-case to my code to check that the above relationship is always true and instead found that it was only true about 50% of the time. That is, if I generated pairs of private keys at random only about half of them would pass this test.
Now, obviously I was doing something wrong, but I couldn't figure out what. I was making sure to take the sum of the two private keys modulo the group order and I carefully checked my EC multiply logic and it passed all the tests I could dream up for it.
The confusing thing was that if I just stuck to ordinary key generation (Private -> Public -> Address) then my implementation agreed with everything I compared it to (Electrum, bitaddress.org) but as soon as I tried to "split" the keys things went wrong, and even then, only about half the time.
Anyway, I just fixed it and had a small epiphany at the same time!
I'm not a mathematician, so I may bungle the terminology but here goes:
For whatever reason, I had internalized the mathematics of address generation as essentially having two parts: A single finite field, and a group defined over that field.
To represent this in my code I have a "Scalar" class for elements of the finite field and a "Point" class for elements of the group.
Because I thought of private keys as belonging to the finite field I naturally made them "Scalar". What I failed to realize was that whenever I added them together and then applied the group order modulo operation I was actually applying a
second modulo operation (the first being applied under the hood by the "Scalar" class). This double modulo, one with P (the field order) and then one with N (the group order) was what was causing all the trouble.
This made me realize that there are actually
two finite fields, F
P and F
N and that they should not be mixed up. F
P should be used for point co-ordinates and F
N should be used for private keys.
So, now I model them in my code with a "Scalar" class (as before) a "Point" class (as before) and a new "Secret" class for elements of the other finite field.
In summary, don't do math on private keys in the wrong field