Commit 60c2d948 authored by David Vorick's avatar David Vorick

add checksums to addresses

parent ef0a5fb3
package api
import (
"errors"
"fmt"
"math/big"
"net/http"
......@@ -36,14 +35,7 @@ func scanAmount(amountStr string) (amount types.Currency, err error) {
// scanAddres scans a types.UnlockHash.
func scanAddress(addrStr string) (addr types.UnlockHash, err error) {
var addrBytes []byte
_, err = fmt.Sscanf(addrStr, "%x", &addrBytes)
if err != nil {
return
}
if copy(addr[:], addrBytes) != len(addr) {
err = errors.New("Malformed coin address")
}
err = addr.LoadString(addrStr)
return
}
......
......@@ -195,7 +195,7 @@ func siag(*cobra.Command, []string) {
return
}
fmt.Printf("Keys created for address: %x\n", unlockConditions.UnlockHash())
fmt.Printf("Keys created for address: %s\n", unlockConditions.UnlockHash().String())
fmt.Printf("%v file(s) created. KEEP THESE FILES. To spend money from this address, you will need at least %v of the files.\n", config.Siag.TotalKeys, config.Siag.RequiredKeys)
}
......@@ -215,7 +215,7 @@ func printKeyInfo(filename string) error {
}
fmt.Printf("Found a key for a %v of %v address.\n", kp.UnlockConditions.SignaturesRequired, len(kp.UnlockConditions.PublicKeys))
fmt.Printf("The address is: %x\n", kp.UnlockConditions.UnlockHash())
fmt.Printf("The address is: %s\n", kp.UnlockConditions.UnlockHash().String())
return nil
}
......
......@@ -7,6 +7,7 @@ package types
// called 'UnlockConditions'.
import (
"bytes"
"encoding/json"
"errors"
"fmt"
......@@ -26,6 +27,7 @@ var (
ErrEntropyKey = errors.New("transaction tries to sign an entproy public key")
ErrFrivilousSignature = errors.New("transaction contains a frivilous siganture")
ErrInvalidPubKeyIndex = errors.New("transaction contains a signature that points to a nonexistent public key")
ErrInvalidUnlockHashChecksum = errors.New("provided unlock hash has an invalid checksum")
ErrMissingSignatures = errors.New("transaction has inputs with missing signatures")
ErrPrematureSignature = errors.New("timelock on signature has not expired")
ErrPublicKeyOveruse = errors.New("public key was used multiple times while signing transaction")
......@@ -34,6 +36,13 @@ var (
ErrWholeTransactionViolation = errors.New("covered fields violation")
ZeroUnlockHash = UnlockHash{0}
// UnlockHashChecksumSize is the size of the checksum used to verify
// human-readable addresses. It is not a crypytographically secure
// checksum, it's merely intended to prevent typos. 6 is chosen because it
// brings the total size of the address to 38 bytes, leaving 2 bytes for
// potential version additions in the future.
UnlockHashChecksumSize = 6
)
type (
......@@ -399,7 +408,8 @@ func (t *Transaction) validSignatures(currentHeight BlockHeight) error {
// MarshalJSON is implemented on the unlock hash to always produce a hex string
// upon marshalling.
func (uh UnlockHash) MarshalJSON() ([]byte, error) {
str := fmt.Sprintf("%x", uh)
uhChecksum := crypto.HashObject(uh)
str := fmt.Sprintf("%x%x", uh[:], uhChecksum[:UnlockHashChecksumSize])
return json.Marshal(str)
}
......@@ -407,17 +417,72 @@ func (uh UnlockHash) MarshalJSON() ([]byte, error) {
// that has been encoded to a hex string.
func (uh *UnlockHash) UnmarshalJSON(b []byte) error {
// Check the length of b.
if len(b) != crypto.HashSize*2+2 {
if len(b) != crypto.HashSize*2+UnlockHashChecksumSize*2+2 {
return ErrUnlockHashWrongLen
}
// Decode the unlock hash.
var byteUnlockHash []byte
var checksum []byte
_, err := fmt.Sscanf(string(b[1:1+crypto.HashSize*2]), "%x", &byteUnlockHash)
if err != nil {
return err
}
// Decode the checksum.
_, err = fmt.Sscanf(string(b[1+crypto.HashSize*2:1+crypto.HashSize*2+UnlockHashChecksumSize*2]), "%x", &checksum)
if err != nil {
return err
}
// Verify the checksum - leave uh as-is unless the checksum is valid.
var unlockHash UnlockHash
copy(unlockHash[:], byteUnlockHash)
expectedChecksum := crypto.HashObject(unlockHash)
if bytes.Compare(expectedChecksum[:UnlockHashChecksumSize], checksum) != 0 {
return ErrInvalidUnlockHashChecksum
}
copy(uh[:], unlockHash[:])
return nil
}
// String returns the hex representation of the unlock hash as a string - this
// includes a checksum.
func (uh UnlockHash) String() string {
uhChecksum := crypto.HashObject(uh)
return fmt.Sprintf("%x%x", uh[:], uhChecksum[:UnlockHashChecksumSize])
}
// LoadString loads a hex representation (including checksum) of an unlock hash
// into an unlock hash object. An error is returned if the string is invalid or
// fails the checksum.
func (uh *UnlockHash) LoadString(strUH string) error {
// Check the length of strUH.
if len(strUH) != crypto.HashSize*2+UnlockHashChecksumSize*2 {
return ErrUnlockHashWrongLen
}
// Decode the input .
// Decode the unlock hash.
var byteUnlockHash []byte
_, err := fmt.Sscanf(string(b[1:crypto.HashSize*2+1]), "%x", &byteUnlockHash)
var checksum []byte
_, err := fmt.Sscanf(strUH[:crypto.HashSize*2], "%x", &byteUnlockHash)
if err != nil {
return err
}
// Decode the checksum.
_, err = fmt.Sscanf(strUH[crypto.HashSize*2:crypto.HashSize*2+UnlockHashChecksumSize*2], "%x", &checksum)
if err != nil {
return err
}
copy(uh[:], byteUnlockHash)
// Verify the checksum - leave uh as-is unless the checksum is valid.
var unlockHash UnlockHash
copy(unlockHash[:], byteUnlockHash)
expectedChecksum := crypto.HashObject(unlockHash)
if bytes.Compare(expectedChecksum[:UnlockHashChecksumSize], checksum) != 0 {
return ErrInvalidUnlockHashChecksum
}
copy(uh[:], unlockHash[:])
return nil
}
......@@ -367,24 +367,31 @@ func TestUnlockHashJSONMarshalling(t *testing.T) {
t.Fatal(err)
}
// Unmarshal the unlock hash.
// Unmarshal the unlock hash and compare to the original.
var umarUH UnlockHash
err = json.Unmarshal(marUH, &umarUH)
if err != nil {
t.Fatal(err)
}
// Compare the original to the unmarshalled.
if umarUH != uh {
t.Error("Marshalled and unmarshalled unlock hash are not equivalent")
}
// Corrupt the checksum.
marUH[36]++
err = umarUH.UnmarshalJSON(marUH)
if err != ErrInvalidUnlockHashChecksum {
t.Error("expecting an invalid checksum:", err)
}
marUH[36]--
// Try an input that's not correct hex.
marUH[7] = 255
marUH[7] += 100
err = umarUH.UnmarshalJSON(marUH)
if err == nil {
t.Error("Expecting error after corrupting input")
}
marUH[7] -= 100
// Try an input of the wrong length.
err = (&umarUH).UnmarshalJSON(marUH[2:])
......@@ -392,3 +399,51 @@ func TestUnlockHashJSONMarshalling(t *testing.T) {
t.Error("Got wrong error:", err)
}
}
// TestUnlockHashStringMarshalling checks that when an unlock hash is
// marshalled and unmarshalled using String and LoadString, the result is what
// is expected.
func TestUnlockHashStringMarshalling(t *testing.T) {
// Create an unlock hash.
uc := UnlockConditions{
Timelock: 2,
SignaturesRequired: 7,
}
uh := uc.UnlockHash()
// Marshal the unlock hash.
marUH := uh.String()
// Unmarshal the unlock hash and compare to the original.
var umarUH UnlockHash
err := umarUH.LoadString(marUH)
if err != nil {
t.Fatal(err)
}
if umarUH != uh {
t.Error("Marshalled and unmarshalled unlock hash are not equivalent")
}
// Corrupt the checksum.
byteMarUH := []byte(marUH)
byteMarUH[36]++
err = umarUH.LoadString(string(byteMarUH))
if err != ErrInvalidUnlockHashChecksum {
t.Error("expecting an invalid checksum:", err)
}
byteMarUH[36]--
// Try an input that's not correct hex.
byteMarUH[7] += 100
err = umarUH.LoadString(string(byteMarUH))
if err == nil {
t.Error("Expecting error after corrupting input")
}
byteMarUH[7] -= 100
// Try an input of the wrong length.
err = umarUH.LoadString(string(byteMarUH[2:]))
if err != ErrUnlockHashWrongLen {
t.Error("Got wrong error:", err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment