I'm trying to understand in more detail how it works the Armory's derivation scheme.
Let
oiae hiij eauw ekhd aeuu wdau iirh tksu astr
wjjj dfuw hfej nwsn naoi rwgs jeja unni kkku
be the Root Key printed on a paper backup (obviously I use it only to understand the mechanism ...)
'astr' and 'kkku' should be only to check errors at the end of each line, then I remove them and convert from the easy16 to hex format:
EASY16CHARS = 'asdf ghjk wert uion'.replace(' ','')
plainRootKeyEasy : oiaehiijeauwekhdaeuuwdauiirhtksuwjjjdfuwhfejnwsnnaoirwgsjejaunni
NORMALCHARS = '0123 4567 89ab cdef'.replace(' ','')
plainRootKey : ed095dd690c8975209cc820cdda5b71c866623c85396f81ff0eda8416960cffd (32 bytes)
I derive the chaincode from the Root Key:
bin_root_key = plainRootKey.to_bytes(32, byteorder='big')
hash256(bin_root_key) = ddbed0169f589ff48ec32cb448dd16f5288a0719bb2454a15036b8575903ffda
chaincode = HMAC256(hash256(bin_root_key), 'Derive Chaincode from Root Key')
chaincode : 84444f1c8c83c9523f120fbae08fa47cb602342ce26b03a31d01dcf343e2e13e (32 bytes) (it is constant for the entire chain)
To pass from a private key (index N) to the next one (index N+1) I have to do a multiplication a * b mod n, where:
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
a = chaincode xor hash256(pubkey)
b = privkey (index N)
nextprivkey = a*b % n
nextpubkey = (a*b % n)*G where G is the generator of the secp256k1 curve
nextpubkey = a*(bG) = a*P where P is the public key (index N)
The parameter 'a' (together the related address) is stored in the multipliers.txt file in .armory directory.
'a' is like a sort of private key of the address N+1 computed respect to the previous public key N instead of the generator G.
In this way the wallet offline can generate these particular private keys (multipliers) only from the first (root) public key and the chaincode. We get all the public keys (as multiples of the root public key) and then all the addresses without using the real private keys (because to get an address we need only a public key, the private key is not necessary).
public keys chain: root public key P1 -> a1*P1 = P2 -> a2*P2 = P3 -> a3*P3 = P4 -> ...
private keys chain: root private key a -> a*a1 % n -> a*a1*a2 % n -> a*a1*a2*a3 % n --> ...
If only one private key N and the chaincode are revelead, then
Private key N with the chaincode will let you compute all private keys beyond N. To compute private keys prior to N, you need all public keys preceding N as well. Both chaincode and public keys are considered known data (as they lay on online computers), hence the generalization that revealing a private key within the chain is as good as revealing all private keys in the wallet.
Returning to the computations, in my particular case:
chaincode : 84444f1c8c83c9523f120fbae08fa47cb602342ce26b03a31d01dcf343e2e13e
pubkey : 0445563be187fb75440607375558bcc0676e84b30040a2068747f94f82e78dfeb911299531610d0ec3a5602350a643dadd9af51b21f2c33ad446ea9c9f099c61e1
hash256(pubkey) : 12f1ed57097159dbb709229e48aafb2e080f6f0dfe8213b7fa836942e4aba08
a = 856b51c9fc14dccf84629d9304050bce5682c2dc3d83229862a9ea676da85b36
b = ed095dd690c8975209cc820cdda5b71c866623c85396f81ff0eda8416960cffd
nextprivkey = 27dcda057ab1f2c114da68f3fb5d1c42456ccf76b606064fdef4ca38167fb508
Then the first private key (and address) of this wallet is:
privkey : 27dcda057ab1f2c114da68f3fb5d1c42456ccf76b606064fdef4ca38167fb508
address : 1AyEzG2REn9szCTimJZjZx9X2TJ25DfAqW (base58)
address : 6d5c1a1398b2a869535afda83c3ca62e6537fb6f --> first 5 bytes: \x6d \x5c \x1a \x13 \x98
Id wallet = binary_to_base58(ADDRBYTE + firstAddr.getAddr160()[:5])[::-1]
Id wallet = binary_to_base58(b'\x98\x13\x1a\x5c\x6d\x00')
Id wallet : 2JjGQxFtK
If I apply again the same scheme, I get the other addresses:
#1 private key : 27dcda057ab1f2c114da68f3fb5d1c42456ccf76b606064fdef4ca38167fb508 address: 1AyEzG2REn9szCTimJZjZx9X2TJ25DfAqW
#2 private key : 94094ed331ca8fba87e9f223e71df6945aac5bde1b8523584d828b596f3bd167 address: 1BU1WGcrkNTRSzLVQnEYeoWHXecGYK9YBv
#3 private key : 80aced7c27ac69b5f359572773c45be903d90012f96b8ae7da398242c2019c47 address: 12JSLGgYVY8EdeF1dfZWpSspLVE2A4Sms8
#4 private key : db5099b13a7a0309e705e2d6166349ff049cfb95203ddc66b652ae61bd5aac84 address: 1CZqeKPka88ARov1CGXKugU7WEmgENs3My
#5 private key : f79d421ee19f9278cb802db35bf4df9ec14b57d4aef3ea70d8a659e4e6e8bd3e address: 1K85SqJVxJio5awb3kaCe1VN6vpsfXEXFV
...
#101 private key : f6c25c16b2bce422fd43c95cfafebc517def32ddbc71ec53259fe3670e863dac address: 18gK7fuU6ymxEdH1ngieiehK4GyzLXBAzy
I noted that Armory at the start generates a pool of 100 addresses. Each time I press the button "Receive Bitcoins", it generates another address (then there are always at least 100 empty addresses in the Address Pool).
2 questions:
1) how does Armory generate the change addresses?
2) when you restore a wallet from its Root Key, how many addresses are checked to retrieve the total balance?
Maybe it tries until it finds at least 100 consecutive addresses without tx, or more? I did a test, it stops after 1000 consecutive addresses.