Bitcoin Forum
July 28, 2021, 07:36:25 PM *
News: Latest Bitcoin Core release: 0.21.1 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: openssl making private/public keys for ethereum  (Read 181 times)
sunsetblues
Member
**
Offline Offline

Activity: 154
Merit: 12

Live life to the fullest and focus on the positive


View Profile
March 22, 2018, 11:29:48 PM
Last edit: March 22, 2018, 11:41:33 PM by sunsetblues
 #1

I hope someone can help me with this… I'm asking in the bitcoin forum b/c question relates to secp256k1 & openssl.

I understand how to make an ethereum public/private key & public address using linux commands.
From the command prompt three simple steps:

1)                openssl ecparam -name secp256k1 -genkey -noout | openssl ec -text -noout > Key
2)                cat Key | grep pub -A 5 | tail -n +2 |tr -d '\n[:space:]:' | sed 's/^04//' > pub
3)                cat pub | keccak-256sum -x -l | tr -d ' -' | tail -c 41 >  address

Wala! All done! I’ve got a unique public/private key to use for my ethereum wallet.

But what I can’t figure out is how to input a set of numbers/letters into openssl and get the public/private keys like at the end of step 1.
Let’s say I like this ethereum private key but I need the public address to use it:

A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad

I know I can go to an online wallet and enter the private key and get the public address which is:
0xB1607e934B61Fc1128AF1E8aa668e33d883aB3b9 (please nobody use this address keys are now public)
but I want to do this from linux prompt and get the answer.

My question: How do I enter that private key into openssl to get the private/public key pair returned something like this result?

Private-Key: (256 bit)
priv:
     0b:f3:29:aa:bf:d0:bb:0b:0f:e1:4b:80:54:f2:1a:
     2e:38:7d:cd:b5:9b:07:5f:3c:38:99:40:bd:5e:e4:
     a4:0f
pub:
     04:2f:06:55:11:4a:b1:90:11:00:a4:48:cb:6a:91:
     76:f3:01:50:2d:9f:8b:03:ab:cb:8b:67:e7:e7:d0:
     3f:85:de:e9:2a:76:59:12:94:4a:4a:ff:94:cd:d7:
     97:a2:6b:9c:ae:da:4e:ec:b0:1d:30:c0:55:14:90:
     b6:51:dd:75:09
 ASN1 OID: secp256k1   (please don’t use this key pair it’s now public)

Thanks in advance for any help… Openssl has really got me all messed up trying to figure this out.

Have faith in humanity and never lose hope.
Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction. Advertise here.
1627500985
Hero Member
*
Offline Offline

Posts: 1627500985

View Profile Personal Message (Offline)

Ignore
1627500985
Reply with quote  #2

1627500985
Report to moderator
1627500985
Hero Member
*
Offline Offline

Posts: 1627500985

View Profile Personal Message (Offline)

Ignore
1627500985
Reply with quote  #2

1627500985
Report to moderator
starmyc
Full Member
***
Offline Offline

Activity: 198
Merit: 102

Some random software engineer


View Profile
March 23, 2018, 05:56:35 AM
Merited by sunsetblues (1)
 #2

A tricky way to do this is to make use of DER files, and to recreate one to import your key. This is a little hacky, but it works.

This is how I did it in a few minutes:

1/ First, create a private key (you won't use, you just need the file structure) in PEM format:

Code:
$ openssl ecparam -name secp256k1 -genkey -noout > private.key

We could directly create a DER file though, using a -outform DER, by the way.

1 (optional)/ What's cool about DER format is that you can dump in ASN.1 its structure. It is interesting and you should take a look at it:

Code:
$ cat private.key | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE         
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:BCDA55068C63324BB5CD05696264ED474467A3F1CC95CE83C00BC497F064F4B6
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]       
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]       
   50:d=2  hl=2 l=  66 prim: BIT STRING       

2/ From the private.key PEM file, convert it in DER and extract the header, the key, and the footer.

Code:
$ ll private.key
-rw-------. 1 mycroft mycroft 223 Mar 23 06:51 private.key
$ openssl ec -in private.key -outform DER 2>/dev/null|head -c 7 > header.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +8|head -c 32 > key.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +40 > footer.bin
$ ll *bin
-rw-rw-r--. 1 mycroft mycroft 79 Mar 23 06:53 footer.bin
-rw-rw-r--. 1 mycroft mycroft  7 Mar 23 06:53 header.bin
-rw-rw-r--. 1 mycroft mycroft 32 Mar 23 06:53 key.bin

3/ Replace the key I just created by yours

Code:
$ echo A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad | xxd -r -p > key.bin

4/ Rebuild & check the complete DER file

Code:
$ cat header.bin key.bin footer.bin > private_key.der
$ cat private_key.der | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE         
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A114DAD00000000000FACED00000000000BAD000000000DECAF00C02BAD02BAD
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]       
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]       
   50:d=2  hl=2 l=  66 prim: BIT STRING       

See ? Same structure, different key! Voodoo magic!

5/ recreate your "Key" file:

Code:
$ openssl ec -in private_key.der -inform DER -text -noout
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:d5:53:99:c9:4d:1f:1f:7f:f1:5b:c5:13:d1:96:
    34:2b:c8:22:dc:23:9d:f0:55:d6:72:3c:f0:b6:2c:
    c8:20:e8:dd:13:40:4b:3a:0d:e5:64:ce:6a:20:e6:
    23:ee:2a:17:18:23:d0:6f:0b:13:30:4a:d7:f5:2a:
    c2:28:2e:40:33
ASN1 OID: secp256k1

And we're done.


Hi, I'm just some random software engineer.
You can check my projects: Bitcoin & altcoin balances/addresses listing dumps: https://balances.crypto-nerdz.org/
sunsetblues
Member
**
Offline Offline

Activity: 154
Merit: 12

Live life to the fullest and focus on the positive


View Profile
March 23, 2018, 09:44:00 AM
 #3

A tricky way to do this is to make use of DER files, and to recreate one to import your key. This is a little hacky, but it works.

This is how I did it in a few minutes:

1/ First, create a private key (you won't use, you just need the file structure) in PEM format:

Code:
$ openssl ecparam -name secp256k1 -genkey -noout > private.key

We could directly create a DER file though, using a -outform DER, by the way.

1 (optional)/ What's cool about DER format is that you can dump in ASN.1 its structure. It is interesting and you should take a look at it:

Code:
$ cat private.key | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:BCDA55068C63324BB5CD05696264ED474467A3F1CC95CE83C00BC497F064F4B6
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
   50:d=2  hl=2 l=  66 prim: BIT STRING        

2/ From the private.key PEM file, convert it in DER and extract the header, the key, and the footer.

Code:
$ ll private.key
-rw-------. 1 mycroft mycroft 223 Mar 23 06:51 private.key
$ openssl ec -in private.key -outform DER 2>/dev/null|head -c 7 > header.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +8|head -c 32 > key.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +40 > footer.bin
$ ll *bin
-rw-rw-r--. 1 mycroft mycroft 79 Mar 23 06:53 footer.bin
-rw-rw-r--. 1 mycroft mycroft  7 Mar 23 06:53 header.bin
-rw-rw-r--. 1 mycroft mycroft 32 Mar 23 06:53 key.bin

3/ Replace the key I just created by yours

Code:
$ echo A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad | xxd -r -p > key.bin

4/ Rebuild & check the complete DER file

Code:
$ cat header.bin key.bin footer.bin > private_key.der
$ cat private_key.der | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A114DAD00000000000FACED00000000000BAD000000000DECAF00C02BAD02BAD
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
   50:d=2  hl=2 l=  66 prim: BIT STRING        

See ? Same structure, different key! Voodoo magic!

5/ recreate your "Key" file:

Code:
$ openssl ec -in private_key.der -inform DER -text -noout
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:d5:53:99:c9:4d:1f:1f:7f:f1:5b:c5:13:d1:96:
    34:2b:c8:22:dc:23:9d:f0:55:d6:72:3c:f0:b6:2c:
    c8:20:e8:dd:13:40:4b:3a:0d:e5:64:ce:6a:20:e6:
    23:ee:2a:17:18:23:d0:6f:0b:13:30:4a:d7:f5:2a:
    c2:28:2e:40:33
ASN1 OID: secp256k1

And we're done.



You're a genius starmyc! Thank you for taking the time to explain this it works like a charm... Sent you the only sendable merit I had. ;0)

Have faith in humanity and never lose hope.
sunsetblues
Member
**
Offline Offline

Activity: 154
Merit: 12

Live life to the fullest and focus on the positive


View Profile
March 23, 2018, 10:48:31 AM
 #4

A tricky way to do this is to make use of DER files, and to recreate one to import your key. This is a little hacky, but it works.

This is how I did it in a few minutes:

1/ First, create a private key (you won't use, you just need the file structure) in PEM format:

Code:
$ openssl ecparam -name secp256k1 -genkey -noout > private.key

We could directly create a DER file though, using a -outform DER, by the way.

1 (optional)/ What's cool about DER format is that you can dump in ASN.1 its structure. It is interesting and you should take a look at it:

Code:
$ cat private.key | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:BCDA55068C63324BB5CD05696264ED474467A3F1CC95CE83C00BC497F064F4B6
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
   50:d=2  hl=2 l=  66 prim: BIT STRING        

2/ From the private.key PEM file, convert it in DER and extract the header, the key, and the footer.

Code:
$ ll private.key
-rw-------. 1 mycroft mycroft 223 Mar 23 06:51 private.key
$ openssl ec -in private.key -outform DER 2>/dev/null|head -c 7 > header.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +8|head -c 32 > key.bin
$ openssl ec -in private.key -outform DER 2>/dev/null|tail -c +40 > footer.bin
$ ll *bin
-rw-rw-r--. 1 mycroft mycroft 79 Mar 23 06:53 footer.bin
-rw-rw-r--. 1 mycroft mycroft  7 Mar 23 06:53 header.bin
-rw-rw-r--. 1 mycroft mycroft 32 Mar 23 06:53 key.bin

3/ Replace the key I just created by yours

Code:
$ echo A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad | xxd -r -p > key.bin

4/ Rebuild & check the complete DER file

Code:
$ cat header.bin key.bin footer.bin > private_key.der
$ cat private_key.der | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l= 116 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A114DAD00000000000FACED00000000000BAD000000000DECAF00C02BAD02BAD
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
   48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
   50:d=2  hl=2 l=  66 prim: BIT STRING        

See ? Same structure, different key! Voodoo magic!

5/ recreate your "Key" file:

Code:
$ openssl ec -in private_key.der -inform DER -text -noout
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:d5:53:99:c9:4d:1f:1f:7f:f1:5b:c5:13:d1:96:
    34:2b:c8:22:dc:23:9d:f0:55:d6:72:3c:f0:b6:2c:
    c8:20:e8:dd:13:40:4b:3a:0d:e5:64:ce:6a:20:e6:
    23:ee:2a:17:18:23:d0:6f:0b:13:30:4a:d7:f5:2a:
    c2:28:2e:40:33
ASN1 OID: secp256k1

And we're done.



You're a genius starmyc! Thank you for taking the time to explain this it works like a charm... Sent you the only sendable merit I had. ;0)

Well heck this doesn't work as expected. Run the keccak hash on the public key and the address doesn't match the wallet address for the private key according to myetherwallet.com. 

             cat Key | grep pub -A 5 | tail -n +2 |tr -d '\n[:space:]:' | sed 's/^04//' > pub

             cat pub | keccak-256sum -x -l | tr -d ' -' | tail -c 41 > address

Seems the public key produced belongs to the original key file created when this command was ran:
openssl ecparam -name secp256k1 -genkey -noout > private.key

But I'm still going to call you a genius b/c that hack was pretty clever and taught me some things I didn't know. :0)

Have faith in humanity and never lose hope.
starmyc
Full Member
***
Offline Offline

Activity: 198
Merit: 102

Some random software engineer


View Profile
March 23, 2018, 11:34:29 AM
 #5

Well heck this doesn't work as expected. Run the keccak hash on the public key and the address doesn't match the wallet address for the private key according to myetherwallet.com. 

             cat Key | grep pub -A 5 | tail -n +2 |tr -d '\n[:space:]:' | sed 's/^04//' > pub

             cat pub | keccak-256sum -x -l | tr -d ' -' | tail -c 41 > address

Seems the public key produced belongs to the original key file created when this command was ran:
openssl ecparam -name secp256k1 -genkey -noout > private.key

But I'm still going to call you a genius b/c that hack was pretty clever and taught me some things I didn't know. :0)

Hmm. I need to take a deeper look, may be I missed a thing. Let me a few hours and I'll do some experiments. I'll let you know!

Hi, I'm just some random software engineer.
You can check my projects: Bitcoin & altcoin balances/addresses listing dumps: https://balances.crypto-nerdz.org/
starmyc
Full Member
***
Offline Offline

Activity: 198
Merit: 102

Some random software engineer


View Profile
March 23, 2018, 01:31:21 PM
 #6

Hmm. I need to take a deeper look, may be I missed a thing. Let me a few hours and I'll do some experiments. I'll let you know!

Ok, I guess it was incomplete.

Code:
$ openssl ec -check -inform DER -in private_key.der  -noout
read EC key
EC Key Invalid!
140074609043200:error:1010207B:elliptic curve routines:ec_key_simple_check_key:invalid private key:crypto/ec/ec_key.c:373:

That's because I imported the private key in the file, but the public key is still in there and openssl won't regenerate it (I thinked so, but well, it doesn't).

Let's make a valid file:

Code:
$ openssl ec -inform DER -in private_key.der -no_public 2>/dev/null | openssl ec -inform PEM -outform DER -out private_key_fixed.der 2>/dev/null
$ openssl ec -check -inform DER -in private_key_fixed.der -noout -text
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:9f:7d:cb:14:14:21:77:d7:b9:48:78:c4:59:b6:
    3a:16:f4:12:80:84:49:b7:8f:a1:7b:e6:4c:d3:7f:
    ed:57:a6:42:12:07:e6:ca:95:e0:c5:15:c3:5f:d5:
    8c:af:ac:a8:b0:e7:d6:07:a3:3a:2c:5c:b1:6a:de:
    28:af:83:15:f7
ASN1 OID: secp256k1
EC Key valid.

This said, my initial steps are just taking a long way. I could sum up the stuff, I guess.

Hi, I'm just some random software engineer.
You can check my projects: Bitcoin & altcoin balances/addresses listing dumps: https://balances.crypto-nerdz.org/
starmyc
Full Member
***
Offline Offline

Activity: 198
Merit: 102

Some random software engineer


View Profile
March 23, 2018, 01:39:55 PM
Last edit: March 24, 2018, 03:29:04 PM by starmyc
 #7

To sum up, the steps are (I've compressed the whole procedure to a smaller command list):

Code:
$  openssl ecparam -name secp256k1 -genkey -noout -outform DER | \
     openssl ec -inform DER -no_public -outform DER -out template.der
read EC key
writing EC key

$ head -c 7 template.der > header.bin
$ echo A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad | xxd -r -p > key.bin
$ tail -c +40 template.der > footer.bin
$ cat header.bin key.bin footer.bin > private_key.der

$ cat private_key.der | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l=  46 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A114DAD00000000000FACED00000000000BAD000000000DECAF00C02BAD02BAD
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1

$ openssl ec -check -inform DER -in private_key.der -noout -text
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:9f:7d:cb:14:14:21:77:d7:b9:48:78:c4:59:b6:
    3a:16:f4:12:80:84:49:b7:8f:a1:7b:e6:4c:d3:7f:
    ed:57:a6:42:12:07:e6:ca:95:e0:c5:15:c3:5f:d5:
    8c:af:ac:a8:b0:e7:d6:07:a3:3a:2c:5c:b1:6a:de:
    28:af:83:15:f7
ASN1 OID: secp256k1
EC Key valid.

$ rm *bin template.der private_key.der

You should be good this time. Could you check ?
Thanks you!

Hi, I'm just some random software engineer.
You can check my projects: Bitcoin & altcoin balances/addresses listing dumps: https://balances.crypto-nerdz.org/
sunsetblues
Member
**
Offline Offline

Activity: 154
Merit: 12

Live life to the fullest and focus on the positive


View Profile
March 23, 2018, 04:48:02 PM
 #8

To sum up, the steps are (I've compressed the whole procedure to a smaller command list):

Code:
$  openssl ecparam -name secp256k1 -genkey -noout -outform DER | \
     openssl ec -inform DER -no_public -outform DER -out template.der
read EC key
writing EC key

$ head -c 7 template.der > header.bin
$ echo A114dad00000000000faced00000000000bad000000000decaf00c02bad02bad | xxd -r -p > key.bin
$ tail -c +40 template.der > footer.bin
$ cat header.bin key.bin footer.bin > private_key.der

$ cat private_key.der | openssl asn1parse -in - -inform DER
    0:d=0  hl=2 l=  46 cons: SEQUENCE          
    2:d=1  hl=2 l=   1 prim: INTEGER           :01
    5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A114DAD00000000000FACED00000000000BAD000000000DECAF00C02BAD02BAD
   39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
   41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1

$ openssl ec -inform DER -in private_key.der -no_public 2>/dev/null | \
    openssl ec -inform PEM -outform DER -out private_key_fixed.der 2>/dev/null

$ openssl ec -check -inform DER -in private_key_fixed.der -noout -text
read EC key
Private-Key: (256 bit)
priv:
    a1:14:da:d0:00:00:00:00:00:fa:ce:d0:00:00:00:
    00:00:ba:d0:00:00:00:00:de:ca:f0:0c:02:ba:d0:
    2b:ad
pub:
    04:9f:7d:cb:14:14:21:77:d7:b9:48:78:c4:59:b6:
    3a:16:f4:12:80:84:49:b7:8f:a1:7b:e6:4c:d3:7f:
    ed:57:a6:42:12:07:e6:ca:95:e0:c5:15:c3:5f:d5:
    8c:af:ac:a8:b0:e7:d6:07:a3:3a:2c:5c:b1:6a:de:
    28:af:83:15:f7
ASN1 OID: secp256k1
EC Key valid.

$ rm *bin template.der private_key.der

You should be good this time. Could you check ?
Thanks you!

That works as expected thank you! I really appreciate you taking the time to send the updated code. If I ever get another spendable merit I'll send it your way. ;0)

Have faith in humanity and never lose hope.
starmyc
Full Member
***
Offline Offline

Activity: 198
Merit: 102

Some random software engineer


View Profile
March 23, 2018, 04:57:41 PM
 #9

That works as expected thank you! I really appreciate you taking the time to send the updated code. If I ever get another spendable merit I'll send it your way. ;0)

That's OK, I learnt stuff too Smiley
I used to know that it was possible to create a bitcoin key with openssl ecparam/ec and extract it from DER, but I didn't know that it was possible to rebuild a DER file from a key. I learnt that too today, and I'm pretty happy about it.

Have a nice day!

Hi, I'm just some random software engineer.
You can check my projects: Bitcoin & altcoin balances/addresses listing dumps: https://balances.crypto-nerdz.org/
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!