Author

Topic: deriving chaincode from root privkey (Read 1314 times)

inf
newbie
Activity: 26
Merit: 0
September 19, 2014, 11:34:21 AM
#5
haha, i just spent 2 hours of debugging and came here to post that i can reproduce armorys chaincode when using SHA256_DIGEST_LENGTH instead of SHA256_BLOCK_LENGTH for the buffers in my HMAC implementation, just to read the same observation was made already! damnit Cheesy

well, i will take that flaw into account for now Smiley

thanks to both of you for your quick replies!
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
September 19, 2014, 11:19:54 AM
#4
Wow!  You're absolutely right.  I have no idea how that got by me.   I just tested it against a test vectors and found that the default hash sizes are half what they are supposed to be.   I even remember looking up test vectors at the time... but I guess I didn't apply them?  I guess the other factor is that that code has never had to interoperate with anything else expecting the same HMACs.  Otherwise we would've noticed.

Well, we'll have to mark those functions as "special" (and keep using them for the old wallets), then update the parameters properly.  The new wallets actually use HMAC from the Crypto++, and already pass all the test vectors.  If I had tried doing CKD function with the python HMAC, I would have noticed.

From RFC4231:

Code:
4.2.  Test Case 1

   Key =          0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
                  0b0b0b0b                          (20 bytes)
   Data =         4869205468657265                  ("Hi There")

   HMAC-SHA-224 = 896fb1128abbdf196832107cd49df33f
                  47b4b1169912ba4f53684b22
   HMAC-SHA-256 = b0344c61d8db38535ca8afceaf0bf12b
                  881dc200c9833da726e9376c2e32cff7
   HMAC-SHA-384 = afd03944d84895626b0825f4ab46907f
                  15f9dadbe4101ec682aa034c7cebc59c
                  faea9ea9076ede7f4af152e8b2fa9cb6
   HMAC-SHA-512 = 87aa7cdea5ef619d4ff0b4241a1d6cb0
                  2379f4e2ce4ec2787ad0b30545e17cde
                  daa833b7d6b8a702038b274eaea3f4e4
                  be9d914eeb61f1702e696c203a126854

4.3.  Test Case 2

   Test with a key shorter than the length of the HMAC output.

   Key =          4a656665                          ("Jefe")
   Data =         7768617420646f2079612077616e7420  ("what do ya want ")
                  666f72206e6f7468696e673f          ("for nothing?")

   HMAC-SHA-224 = a30e01098bc6dbbf45690f3a7e9e6d0f
                  8bbea2a39e6148008fd05e44
   HMAC-SHA-256 = 5bdcc146bf60754e6a042426089575c7
                  5a003f089d2739839dec58b964ec3843
   HMAC-SHA-384 = af45d2e376484031617f78d2b58a6b1b
                  9c7ef464f5a01b47e42ec3736322445e
                  8e2240ca5e69e2c78b3239ecfab21649
   HMAC-SHA-512 = 164b7a7bfcf819e2e395fbe73b56e0a3
                  87bd64222e831fd610270cd7ea250554
                  9758bf75c05a994a6d034f65f8f0e6fd
                  caeab1a34d4a6b4b636e070a38bce737





hero member
Activity: 672
Merit: 504
a.k.a. gurnec on GitHub
September 19, 2014, 10:50:19 AM
#3
inf,

I think you've uncovered a bug in Armory's HMAC implementation. I very well could be (and in fact I hope I'm) wrong though....

In particular, this doesn't produce what I'd expect:

Code:
import hmac, hashlib
from armoryengine.ArmoryUtils import HMAC256

# Test vector case 1 from RFC 4231
key      = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b".decode("hex")
data     = "Hi There"
expected = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7".decode("hex")

print "RFC 4231 HMAC-SHA256: ", expected                                    .encode("hex")
print "  Python HMAC-SHA256: ", hmac.new(key, data, hashlib.sha256).digest().encode("hex")
print "  Armory HMAC-SHA256: ", HMAC256(key, data)                          .encode("hex")

Here's the definition of HMAC256 in ArmoryUtils.py:

Code:
def sha256(bits):
   return hashlib.new('sha256', bits).digest()

def HMAC(key, msg, hashfunc, hashsz=None):
   """ This is intended to be simple, not fast.  For speed, use HDWalletCrypto() """
   hashsz = len(hashfunc('')) if hashsz==None else hashsz
   key = (hashfunc(key) if len(key)>hashsz else key)
   key = key.ljust(hashsz, '\x00')
   okey = ''.join([chr(ord('\x5c')^ord(c)) for c in key])
   ikey = ''.join([chr(ord('\x36')^ord(c)) for c in key])
   return hashfunc( okey + hashfunc(ikey + msg) )

HMAC256 = lambda key,msg: HMAC(key, msg, sha256, 32)

I believe the problem is that this HMAC function takes and uses the hash size as a parameter (256 bits for SHA256), but it should be taking and using the block size instead (512 bits for SHA256).

Could someone verify this, or hopefully correct me?
legendary
Activity: 1428
Merit: 1093
Core Armory Developer
September 18, 2014, 03:56:37 PM
#2
I just ran the same calculation using armoryengine and got the chaincode you were expecting:

Code:
>>> from armoryengine.ALL import *
>>> k = hex_to_binary('C263A3ED1351B0E07D57B788688C3BD8EA0DE54788739093880AAEC2DFF21BCF')
>>> binary_to_hex(hash256(k))
'a7941f148f0537ddb2794c923a9ef15a9eada511f7ee5aad851e86ecb86d3b0a'
>>> binary_to_hex(HMAC256( hash256(k), "Derive Chaincode from Root Key"))
'90ced6c81a642182660c91a0418cdaf1165ae311378a3862723aed81d48a767e'

Usually these kinds of issues are endianness related.  For instance, the first line k=hex_to_binary(...) interprets the strings as little-endian.  You have to explicitly pass in a second arg with BIGENDIAN if you want to interpret it that way.  When I first started learning Bitcoin, a significant amount of time was spent guessing and checking endianness switches for calculations with known outputs.  That might be what you have to do here.
inf
newbie
Activity: 26
Merit: 0
September 18, 2014, 03:37:03 PM
#1
hi,

i have a problem with deriving the chaincode from the root key in my own code, i tried different private keys differently in c, php and python and i always get the same digest, but that digest does not fit the provided chaincode in the wallet generated by armory which i am checking against.

as far as i understand, i simply get it like that:

Code:
hmac_sha256( key=sha256(sha256(ROOTKEY)) , ascii_message="Derive Chaincode from Root Key")

basically, i can treat the message the same as hex

Code:
0x44657269766520436861696E636F64652066726F6D20526F6F74204B6579

right? however, i tried it both as char * and uint8_t *.

let my example be:
Code:
ROOTKEY = 0xC263A3ED1351B0E07D57B788688C3BD8EA0DE54788739093880AAEC2DFF21BCF
hash256(ROOTKEY) = 0xa7941f148f0537ddb2794c923a9ef15a9eada511f7ee5aad851e86ecb86d3b0a (yes, its double-hashed)

all my sample programs, including the following python-script (sry, i really suck at python), deliver me

Code:
chaincode = 0x52c4ec3cd49b4af3c944f498e8d08fce2f3c432687408577ff6ae307d9ef1e6f

while the wallet file says the actual chaincode is

Code:
chaincode_wallet = 0x90CED6C81A642182660C91A0418CDAF1165AE311378A3862723AED81D48A767E

can someone please elaborate what i am getting wrong?

thanks Smiley
inf


python code:
Code:
#!/usr/local/bin/python2.7
    
import hmac
import hashlib

k = bytearray([0xa7,0x94,0x1f,0x14,0x8f,0x05,0x37,0xdd,0xb2,0x79,0x4c,0x92,0x3a,0x9e,0xf1,0x5a,0x9e,0xad,0xa5,0x11,0xf7,0xee,0x5a,0xad,0x85,0x1e,0x86,0xec,0xb8,0x6d,0x3b,0x0a]);
x = hmac.new(k, "Derive Chaincode from Root Key", hashlib.sha256);
print x.hexdigest()

edit: i am using armory 0.92.1, generating wallets version 1.35.
Jump to: