Class: HashCash::Stamp

Inherits:
Object
  • Object
show all
Defined in:
lib/hashcash.rb

Overview

The HashCash::Stamp class can be used to create and verify proof of work, so called hash cash, as defined on hashcash.org.

Basically, it creates a ‘stamp’, which when hashed with SHA-1 has a certain amount of 0 bytes at the top.

To create a new stamp, call the constructor with the :resource parameter to specify a resource for which this stamp will be valid (e.g. an email address).

To verify a stamp, call it with a string representation of the stamp as the :stamp parameter and call verify with a resource on it.

Constant Summary collapse

STAMP_VERSION =
1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Stamp

To construct a new HashCash::Stamp object, pass the :resource parameter to it, e.g.

s = HashCash::Stamp.new(:resource => ‘[email protected]’)

This creates a 20 bit hash cash stamp, which can be retrieved using the stamp_string() attribute reader method.

Optionally, the parameters :bits and :date can be passed to the method to change the number of bits the stamp is worth and the issuance date (which is checked on the server for an expiry with a default deviance of 2 days, pass a Time object).

Alternatively, a stamp can be passed to the constructor by passing it as a string to the :stamp parameter, e.g.

s = HashCash::Stamp.new(:stamp => ‘1:20:060408:[email protected]::1QTjaYd7niiQA/sc:ePa’)



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/hashcash.rb', line 41

def initialize(args)
	if ! args || (! args[:stamp] && ! args[:resource]) then
		raise ArgumentError, 'either stamp or stamp parameters needed'
	end
	# existing stamp in string format
	if args[:stamp] then
		@stamp_string = args[:stamp]
		(@version, @bits, @date, @resource, ext, @rand, @counter) \
			= args[:stamp].split(':')
		@bits = @bits.to_i
		if @version.to_i != STAMP_VERSION then
			raise ArgumentError, "incorrect stamp version #{@version}"
		end
		@date = parse_date(@date)
	# new stamp to be created
	elsif args[:resource] then
		@resource = args[:resource]
		# optional parameters: bits and date
		@bits = args[:bits] || 20
		@bits = @bits.to_i
		if args[:date] && ! args[:date].class == Time then
			raise ArgumentError, 'date needs to be a Time object'
		end
		@date = args[:date] || Time.now

		# create first part of stamp string
		random_string = Base64.encode64(OpenSSL::Random.random_bytes(12)).chomp
		first_part = "#{STAMP_VERSION}:#{@bits}:" + \
					 "#{date_to_str(@date)}:#{@resource}" + \
					 "::#{random_string}:"
		ctr = 0
		@stamp_string = nil
		while ! @stamp_string do
			test_stamp = first_part + ctr.to_s(36)
			if Digest::SHA1.digest(test_stamp).unpack('B*')[0][0,@bits].to_i == 0
				@stamp_string = test_stamp
			end
			ctr += 1
		end
	end
end

Instance Attribute Details

#bitsObject (readonly)

Returns the value of attribute bits.



20
21
22
# File 'lib/hashcash.rb', line 20

def bits
  @bits
end

#dateObject (readonly)

Returns the value of attribute date.



20
21
22
# File 'lib/hashcash.rb', line 20

def date
  @date
end

#resourceObject (readonly)

Returns the value of attribute resource.



20
21
22
# File 'lib/hashcash.rb', line 20

def resource
  @resource
end

#stamp_stringObject (readonly)

Returns the value of attribute stamp_string.



20
21
22
# File 'lib/hashcash.rb', line 20

def stamp_string
  @stamp_string
end

#versionObject (readonly)

Returns the value of attribute version.



20
21
22
# File 'lib/hashcash.rb', line 20

def version
  @version
end

Instance Method Details

#to_sObject

A string representation of the stamp



115
116
117
# File 'lib/hashcash.rb', line 115

def to_s
	@stamp_string
end

#verify(resources, bits = 20) ⇒ Object

Verify a stamp for a given resource or resources and a number of bits. The resources parameter can either be a string for a single resource or an array of strings for more than one possible resource (for example if you have different email addresses and want the stamp to verify against one of them).

The method checks the resource, the time of issuance and the number of 0 bits when the stamp is SHA1-hashed. It returns true if all checks are successful and raises an exception otherwise.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/hashcash.rb', line 92

def verify(resources, bits = 20)
	# check for correct resource
	if resources.class != String && resources.class != Array then
		raise ArgumentError, "resource must be either String or Array"
	end
	if resources.class == String then
		resources = [ resources ]
	end
	if ! resources.include? @resource then
		raise "Stamp is not valid for the given resource(s)."
	end
	# check if difference is greater than 2 days
	if (Time.now - @date).to_i.abs > 2*24*60*60 then
		raise "Stamp is expired/not yet valid"
	end
	# check 0 bits in stamp
	if (Digest::SHA1.hexdigest(@stamp_string).hex >> (160-bits) != 0) then
		raise "Invalid stamp, not enough 0 bits"
	end
	true
end