hashcash 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +49 -0
  2. data/lib/hashcash.rb +143 -0
  3. data/test/test_hashcash.rb +50 -0
  4. metadata +69 -0
data/README ADDED
@@ -0,0 +1,49 @@
1
+ == Description
2
+ A library for creating and verifying so-called »hash cash stamps«, i.e.
3
+ proof of work as defined on hashcash.org.
4
+
5
+ == Prerequisites
6
+ This package requires Ruby 1.8 or later.
7
+
8
+ == Installation instructions
9
+ rake test (optional)
10
+ rake install (non-gem) or rake install_gem (gem)
11
+
12
+ == Synopsis
13
+ require 'hashcash'
14
+
15
+ # Create a new hash cash stamp
16
+ s = HashCash::Stamp.new(:resource => 'hashcash@alech.de')
17
+ puts s
18
+
19
+ # Verify a given stamp
20
+ s = HashCash::Stamp.new(:stamp => '1:20:060408:adam@cypherspace.org::1QTjaYd7niiQA/sc:ePa')
21
+ s.verify('adam@cypherspace.org)
22
+
23
+ == Known bugs
24
+ Stamp creation is an order of magnitude slower than using the
25
+ standalone hash cash program. This might not be fixed as I
26
+ mainly use the library for verification.
27
+
28
+ == Copyright
29
+ (c) 2010 Alexander Klink
30
+
31
+ == License
32
+ Licensed under the Apache License, Version 2.0 (the "License");
33
+ you may not use this file except in compliance with the License.
34
+ You may obtain a copy of the License at
35
+
36
+ http://www.apache.org/licenses/LICENSE-2.0
37
+
38
+ == Warranty
39
+ Unless required by applicable law or agreed to in writing, software
40
+ distributed under the License is distributed on an "AS IS" BASIS,
41
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42
+ See the License for the specific language governing permissions and
43
+ limitations under the License.
44
+
45
+ == Author
46
+ Alexander Klink
47
+ hashcash@alech.de
48
+ http://www.alech.de
49
+ @alech on Twitter
@@ -0,0 +1,143 @@
1
+ require 'time'
2
+ require 'digest/sha1'
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module HashCash
7
+ # The HashCash::Stamp class can be used to create and verify proof of work, so
8
+ # called hash cash, as defined on hashcash.org.
9
+ #
10
+ # Basically, it creates a 'stamp', which when hashed with SHA-1 has a
11
+ # certain amount of 0 bytes at the top.
12
+ #
13
+ # To create a new stamp, call the constructor with the :resource parameter
14
+ # to specify a resource for which this stamp will be valid (e.g. an email
15
+ # address).
16
+ #
17
+ # To verify a stamp, call it with a string representation of the stamp as
18
+ # the :stamp parameter and call verify with a resource on it.
19
+ class Stamp
20
+ attr_reader :version, :bits, :resource, :date, :stamp_string
21
+
22
+ STAMP_VERSION = 1
23
+
24
+ # To construct a new HashCash::Stamp object, pass the :resource parameter
25
+ # to it, e.g.
26
+ #
27
+ # s = HashCash::Stamp.new(:resource => 'hashcash@alech.de')
28
+ #
29
+ # This creates a 20 bit hash cash stamp, which can be retrieved using
30
+ # the stamp_string() attribute reader method.
31
+ #
32
+ # Optionally, the parameters :bits and :date can be passed to the
33
+ # method to change the number of bits the stamp is worth and the issuance
34
+ # date (which is checked on the server for an expiry with a default
35
+ # deviance of 2 days, pass a Time object).
36
+ #
37
+ # Alternatively, a stamp can be passed to the constructor by passing
38
+ # it as a string to the :stamp parameter, e.g.
39
+ #
40
+ # s = HashCash::Stamp.new(:stamp => '1:20:060408:adam@cypherspace.org::1QTjaYd7niiQA/sc:ePa')
41
+ def initialize(args)
42
+ if ! args || (! args[:stamp] && ! args[:resource]) then
43
+ raise ArgumentError, 'either stamp or stamp parameters needed'
44
+ end
45
+ # existing stamp in string format
46
+ if args[:stamp] then
47
+ @stamp_string = args[:stamp]
48
+ (@version, @bits, @date, @resource, ext, @rand, @counter) \
49
+ = args[:stamp].split(':')
50
+ @bits = @bits.to_i
51
+ if @version.to_i != STAMP_VERSION then
52
+ raise ArgumentError, "incorrect stamp version #{@version}"
53
+ end
54
+ @date = parse_date(@date)
55
+ # new stamp to be created
56
+ elsif args[:resource] then
57
+ @resource = args[:resource]
58
+ # optional parameters: bits and date
59
+ @bits = args[:bits] || 20
60
+ @bits = @bits.to_i
61
+ if args[:date] && ! args[:date].class == Time then
62
+ raise ArgumentError, 'date needs to be a Time object'
63
+ end
64
+ @date = args[:date] || Time.now
65
+
66
+ # create first part of stamp string
67
+ random_string = Base64.encode64(OpenSSL::Random.random_bytes(12)).chomp
68
+ first_part = "#{STAMP_VERSION}:#{@bits}:" + \
69
+ "#{date_to_str(@date)}:#{@resource}" + \
70
+ "::#{random_string}:"
71
+ ctr = 0
72
+ @stamp_string = nil
73
+ while ! @stamp_string do
74
+ test_stamp = first_part + ctr.to_s(36)
75
+ if Digest::SHA1.digest(test_stamp).unpack('B*')[0][0,@bits].to_i == 0
76
+ @stamp_string = test_stamp
77
+ end
78
+ ctr += 1
79
+ end
80
+ end
81
+ end
82
+
83
+ # Verify a stamp for a given resource or resources and a number of bits.
84
+ # The resources parameter can either be a string for a single resource
85
+ # or an array of strings for more than one possible resource (for example
86
+ # if you have different email addresses and want the stamp to verify against
87
+ # one of them).
88
+ #
89
+ # The method checks the resource, the time of issuance and the number of
90
+ # 0 bits when the stamp is SHA1-hashed. It returns true if all checks
91
+ # are successful and raises an exception otherwise.
92
+ def verify(resources, bits = 20)
93
+ # check for correct resource
94
+ if resources.class != String && resources.class != Array then
95
+ raise ArgumentError, "resource must be either String or Array"
96
+ end
97
+ if resources.class == String then
98
+ resources = [ resources ]
99
+ end
100
+ if ! resources.include? @resource then
101
+ raise "Stamp is not valid for the given resource(s)."
102
+ end
103
+ # check if difference is greater than 2 days
104
+ if (Time.now - @date).to_i.abs > 2*24*60*60 then
105
+ raise "Stamp is expired/not yet valid"
106
+ end
107
+ # check 0 bits in stamp
108
+ if (Digest::SHA1.hexdigest(@stamp_string).hex >> (160-bits) != 0) then
109
+ raise "Invalid stamp, not enough 0 bits"
110
+ end
111
+ true
112
+ end
113
+
114
+ # A string representation of the stamp
115
+ def to_s
116
+ @stamp_string
117
+ end
118
+
119
+ private
120
+ # Parse the date contained in the stamp string.
121
+ def parse_date(date)
122
+ year = date[0,2].to_i
123
+ month = date[2,2].to_i
124
+ day = date[4,2].to_i
125
+ # Those may not exist, but it is irrelevant as ''.to_i is 0
126
+ hour = date[6,2].to_i
127
+ min = date[8,2].to_i
128
+ sec = date[10,2].to_i
129
+ Time.utc(year, month, day, hour, min, sec)
130
+ end
131
+
132
+ # Convert a date to the string format used in the stamps
133
+ def date_to_str(date)
134
+ if (date.sec == 0) && (date.hour == 0) && (date.min == 0) then
135
+ date.strftime("%y%m%d")
136
+ elsif (date.sec == 0) then
137
+ date.strftime("%y%m%d%H%M")
138
+ else
139
+ date.strftime("%y%m%d%H%M%S")
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,50 @@
1
+ require 'test/unit'
2
+ require 'lib/hashcash'
3
+ require 'digest/sha1'
4
+
5
+ class TestHashCash < Test::Unit::TestCase
6
+ WIKIPEDIA_EXAMPLE = '1:20:060408:adam@cypherspace.org::1QTjaYd7niiQA/sc:ePa'
7
+
8
+ def test_instantiation
9
+ assert_raise( ArgumentError ) { HashCash::Stamp.new }
10
+ assert(HashCash::Stamp.new(:resource => 'testhashcash'))
11
+ assert(HashCash::Stamp.new(:resource => 'foobar', :bits => 15))
12
+ assert(HashCash::Stamp.new(:stamp => WIKIPEDIA_EXAMPLE))
13
+ end
14
+
15
+ def test_parsing
16
+ s = HashCash::Stamp.new(:stamp => WIKIPEDIA_EXAMPLE)
17
+ assert_equal(1, s.version.to_i)
18
+ assert_equal(20, s.bits)
19
+ assert_equal(2006, s.date.year)
20
+ assert_equal(4, s.date.month)
21
+ assert_equal(8, s.date.day)
22
+ end
23
+
24
+ def test_verify_errors
25
+ s = HashCash::Stamp.new(:stamp => WIKIPEDIA_EXAMPLE)
26
+ # wrong resource
27
+ assert_raise( RuntimeError ) { s.verify('foo@example.org') }
28
+ # expired
29
+ assert_raise( RuntimeError ) { s.verify('adam@cypherspace.org') }
30
+ # not enough 'cash'
31
+ stamp = "1:32:" + Time.now.strftime('%y%m%d') + ':testhashcash::foobar1234:1'
32
+ # make sure it is really not enough
33
+ while (Digest::SHA1.hexdigest(stamp).hex >> (160-32) == 0) do
34
+ stamp += '1'
35
+ end
36
+ s2 = HashCash::Stamp.new(:stamp => stamp)
37
+ assert_raise( RuntimeError ) { s.verify('testhashcash') }
38
+ end
39
+
40
+ def test_create_and_verify
41
+ s = HashCash::Stamp.new(:resource => 'testhashcash')
42
+ assert(s.verify('testhashcash'))
43
+
44
+ s2 = HashCash::Stamp.new(:resource => 'testhashcash', :bits => 10)
45
+ assert(s2.verify('testhashcash', 10))
46
+
47
+ s3 = HashCash::Stamp.new(:resource => 'testhashcash', :bits => 10, :date => Time.now)
48
+ assert(s3.verify('testhashcash', 10))
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hashcash
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Alexander Klink
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-24 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: "A library for creating and verifying so-called \xC2\xBBhash cash stamps\xC2\xAB, i.e.\n\
22
+ proof of work as defined on hashcash.org.\n"
23
+ email: hashcash@alech.de
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - lib/hashcash.rb
32
+ - test/test_hashcash.rb
33
+ - README
34
+ has_rdoc: true
35
+ homepage:
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ hash: 3
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.7
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A library to create hash cash stamps as defined on hashcash.org.
68
+ test_files:
69
+ - test/test_hashcash.rb