Skip to content

Commit

Permalink
migrate ecdsa code to use byte arrays instead of hex strings
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsuo committed Dec 18, 2014
1 parent 0bdfb25 commit 851d2d1
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 55 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ First, we're going to implement a thin-client wallet.
- Clean up tests to make more consistent
- Different nets (e.g., https://github.com/haskoin/haskoin/blob/master/prodnet/Network/Haskoin/Constants.hs)

- DER: https://bitcointa.lk/threads/quick-question-on-der-encoding-of-signature-pair-r-s.329293/#post-7058009
- ECDSA signing: https://bitcointa.lk/threads/quick-question-on-der-encoding-of-signature-pair-r-s.329293/#post-7023595
- Why der: http://bitcoin.stackexchange.com/questions/14415/why-use-der-encoding-for-signatures

### Public key distribution
- See [ref](https://github.com/danielsuo/Crypto.jl)

Expand Down
3 changes: 1 addition & 2 deletions src/Coin.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Coin
export
# keys.jl
generate_keys,
get_public_key,
get_pub_key,

# wif.jl
private2wif,
Expand All @@ -25,7 +25,6 @@ export
# utils.jl
reverse_endian,
get_checksum,
hex_string_to_array,
to_varint,
bytearray

Expand Down
37 changes: 22 additions & 15 deletions src/keys.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,53 @@
##
##############################################################################

function generate_keys(network_id = "00", version = "1")
secret_key = join([hex(x) for x in Crypto.random(256)])
public_key = get_public_key(secret_key, network_id = network_id, version = version)
function generate_keys(network_id = 0x00, version = "1")
priv_key = Crypto.random(256)
pub_key = get_pub_key(priv_key, network_id = network_id, version = version)

return (secret_key, public_key)
return (Crypto.hex_array_to_string(priv_key), pub_key)
end

function get_public_key(secret_key; network_id = "00", version = "1")
function get_pub_key(priv_key::String; network_id = 0x00, version = "1")
get_pub_key(Crypto.hex_string_to_array(priv_key),
network_id = network_id,
version = version)
end

function get_pub_key(priv_key::Array{Uint8}; network_id = 0x00, version = "1")

# Generate corresponding public key generated with against ECDSA secp256k1
# (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate, 32 bytes
# corresponding to Y coordinate)
public_key = Crypto.ec_public_key_create(secret_key)
pub_key = Crypto.ec_pub_key(priv_key)

# Perform SHA-256 hashing on the public key
public_key = Crypto.digest("SHA256", public_key, is_hex=true)
pub_key = Crypto.digest("SHA256", pub_key)

# Perform RIPEMD-160 hashing on the result of SHA-256
public_key = Crypto.digest("RIPEMD160", public_key, is_hex=true)
pub_key = Crypto.digest("RIPEMD160", pub_key)

# Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)
# Reference: https://bitcoin.org/en/developer-reference#address-conversion
public_key = string(network_id, public_key)
pub_key = [bytearray(network_id), pub_key]

# Get checksum by performing SHA256 hash twice and taking first 4 bytes
checksum = get_checksum(public_key, is_hex=true)
checksum = get_checksum(pub_key)

# Add the 4 checksum bytes from stage 7 at the end of extended RIPEMD-160
# hash from stage 4. This is the 25-byte binary Bitcoin Address.
public_key = string(public_key, checksum)
append!(pub_key, checksum)

# Convert the result from a byte string into a base58 string using
# Base58Check encoding. This is the most commonly used Bitcoin Address format
# Reference: https://en.bitcoin.it/wiki/Base58Check_encoding
public_key = parseint(BigInt, public_key, 16)
public_key = encode58(public_key)
# TODO: array to string to BigInt is really round-about
pub_key = parseint(BigInt, Crypto.hex_array_to_string(pub_key), 16)
pub_key = encode58(pub_key)

# Append address version byte in hex
# Reference: https://en.bitcoin.it/wiki/List_of_address_prefixes
public_key = string(version, public_key)
pub_key = string(version, pub_key)

return public_key
return pub_key
end
9 changes: 4 additions & 5 deletions src/messages.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Base.convert
import Base.print

##############################################################################
##
Expand Down Expand Up @@ -82,7 +81,7 @@ type Message
checksum = uint32(parseint(get_checksum(payload, is_hex=true)[1:8], 16))

# Turn payload hex string into array of bytes
payload = hex_string_to_array(payload)
payload = Crypto.hex_string_to_array(payload)

new(magic, command.data, payload)
end
Expand All @@ -93,7 +92,7 @@ type OutPoint
index::Uint32 # Index of specific output in tx. 1st output is 0

function OutPoint(hash::String, index::Integer)
OutPoint(hex_string_to_array(hash), uint32(index))
OutPoint(Crypto.hex_string_to_array(hash), uint32(index))
end

function OutPoint(hash::Array{Uint8}, index::Integer)
Expand Down Expand Up @@ -124,7 +123,7 @@ type Tx_Input
sequence::Uint32 # Tx version as defined by the sender

function Tx_Input(previous_output::OutPoint, scriptSig::String; sequence = 0xffffffff)
scriptSig = hex_string_to_array(scriptSig)
scriptSig = Crypto.hex_string_to_array(scriptSig)
Tx_Input(previous_output, scriptSig, sequence = sequence)
end

Expand All @@ -150,7 +149,7 @@ type Tx_Output
# value: transaction value in Satoshi
# scriptPubKey: script as hex string
function Tx_Output(value, scriptPubKey::String)
scriptPubKey = hex_string_to_array(scriptPubKey)
scriptPubKey = Crypto.hex_string_to_array(scriptPubKey)
Tx_Output(value, scriptPubKey)
end

Expand Down
30 changes: 5 additions & 25 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,33 +52,13 @@ function reverse_endian(hex_data::Integer)
return result
end

# TODO: String manipulation is really not the best way
function convert(::Type{Array{Uint8}}, x::Integer)
padding = 0
if typeof(x) != BigInt
padding = sizeof(x) * 2
end
hex_string = hex(x, padding)
return hex_string_to_array(hex_string)
end

function get_checksum(message::String; is_hex = false)
create_digest(x) = Crypto.digest("SHA256", x, is_hex = is_hex)
return create_digest(create_digest(message))[1:8]
return Crypto.hex_array_to_string(create_digest(create_digest(message))[1:4])
end

function hex_string_to_array(hex_string::String)
hex_length = length(hex_string)

# Left pad with 0 to make hex_string even length
if hex_length % 2 != 0
hex_string = string("0", hex_string)
hex_length += 1
end

hex_length = div(hex_length, 2)

return [uint8(parseint(hex_string[2i-1:2i], 16)) for i in 1:hex_length]
function get_checksum(message::Array{Uint8})
return Crypto.digest("SHA256", Crypto.digest("SHA256", message))[1:4]
end

# Convenience function for converting to Array{Uint8}
Expand All @@ -92,11 +72,11 @@ function string(array::Array{Uint8})
end

print(x::Array{Uint8}) = print(string(x))
println(x::Array{Uint8}) = print(x); print()
println(x::Array{Uint8}) = print(x); print("\n")

function string(x::Unsigned)
return string("0x", hex(x, div(sizeof(x), 2)))
end

print(x::Unsigned) = print(string(x))
println(x::Unsigned) = print(x); print()
println(x::Unsigned) = print(x); print("\n")
3 changes: 2 additions & 1 deletion src/wif.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

# Convert private key to WIF.
# TODO: turn keys into objects to hold metadata
# TODO: Update to use byte arrays
# - network_id: which network to use; 0x80 for mainnet, 0xef for testnet
# - compression: 01 if private key corresponds to compressed public key
function private2wif(private_key; network_id = "80", compression = "")
Expand All @@ -22,7 +23,7 @@ function private2wif(private_key; network_id = "80", compression = "")
hashed = Crypto.digest("SHA256", private_key, is_hex=true)
hashed = Crypto.digest("SHA256", hashed, is_hex=true)

checksum = hashed[1:8]
checksum = Crypto.hex_array_to_string(hashed[1:4])

private_key = string(private_key, checksum)

Expand Down
8 changes: 1 addition & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ base58data = parseint(BigInt, "800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471
##############################################################################

secret_key = "18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725"
public_key = get_public_key(secret_key)
public_key = get_pub_key(secret_key)
@test public_key == "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"

##############################################################################
Expand Down Expand Up @@ -104,12 +104,6 @@ tx = Tx([tx_in], [tx_out])
@test reverse_endian(23) == 1657324662872342528
@test reverse_endian(-23) == -1585267068834414593

# hex_string_to_array: empty string
@test hex_string_to_array("") == []
# hex_string_to_array: odd- and even-length strings
@test hex_string_to_array("adfcef981") == [0x0a, 0xdf, 0xce, 0xf9, 0x81]
@test hex_string_to_array("aadfcef981") == [0xaa, 0xdf, 0xce, 0xf9, 0x81]

# Test VarInt conversion
@test to_varint(1) == [0x01]
@test to_varint(252) == [0xfc]
Expand Down

0 comments on commit 851d2d1

Please sign in to comment.