Absolute hinky bare-bones implementation of multiformats in Perl
1package
2 Multiformats::Multihash {
3 use strict;
4 use warnings;
5 use feature 'signatures';
6
7 use Exporter 'import';
8 our @EXPORT_OK = qw/multihash_encode multihash_decode multihash_wrap multihash_unwrap multihash_unwrap_stream/;
9
10 use Digest::SHA qw/sha1 sha256 sha384 sha512/; # SHA2
11 use Digest::SHA3 qw/sha3_224 sha3_384 sha3_256/;
12 use Multiformats::Varint qw/varint_decode_raw varint_encode varint_decode_stream/;
13
14 sub decode($self, $value) {
15 return multihash_decode($value);
16 }
17
18 sub encode($self, $as, $value) {
19 return multihash_encode($as, $value);
20 }
21
22 sub wrap($self, $as, $value) {
23 return multihash_wrap($as, $value);
24 }
25
26 sub unwrap($self, $value) {
27 return multihash_unwrap($value);
28 }
29
30 sub new($pkg) {
31 return bless({}, $pkg);
32 }
33
34 # this map holds the various encodings and decodings
35 use constant MULTIFORMAT_MAP => [
36 [ 'identity', 0x00, undef, sub { return shift } ],
37 [ 'sha1', 0x11, undef, sub { return sha1(shift) } ],
38 [ 'sha2-256', 0x12, undef, sub { return sha256(shift) } ],
39 [ 'sha2-512', 0x13, undef, sub { return sha512(shift) } ],
40 [ 'sha3-384', 0x15, undef, sub { return sha3_384(shift) } ],
41 [ 'sha3-256', 0x16, undef, sub { return sha3_256(shift) } ],
42 [ 'sha3-224', 0x17, undef, sub { return sha3_224(shift) } ],
43 [ 'sha2-384', 0x20, undef, sub { return sha_384(shift) } ],
44 ];
45
46 sub _map_by_tag($tag) {
47 foreach my $entry (@{__PACKAGE__->MULTIFORMAT_MAP}) {
48 return $entry if($entry->[1] == $tag);
49 }
50 return undef;
51 }
52
53 sub _map_by_name($name) {
54 foreach my $entry (@{__PACKAGE__->MULTIFORMAT_MAP}) {
55 return $entry if($entry->[0] eq $name);
56 }
57 return _map_by_tag($name);
58 }
59
60 sub multihash_decode($bytes) {
61 # make sure it's actual bytes
62 utf8::downgrade($bytes, 1);
63
64 my ($t, $bread_type) = varint_decode_raw($bytes);
65 if(my $e = _map_by_tag($t)) {
66 my ($l, $bread_len) = varint_decode_raw(substr($bytes, $bread_type));
67 return substr($bytes, $bread_type + $bread_len); # there isn't any decoding since hashes are a one-way street so we just return the actual value
68 } else {
69 die 'unknown format ' . $t . ', ';
70 }
71 }
72
73 sub multihash_unwrap_stream($stream) {
74 my ($t, $bread_type) = varint_decode_stream($stream);
75 if(my $e = _map_by_tag($t)) {
76 my ($l, $bread_len) = varint_decode_stream($stream);
77 my $buf;
78 $stream->read($buf, $l); # the raw digest
79 return wantarray
80 ? ([$e->[0], $e->[1] ], $buf) # allows us to get the whole kit and kaboodle in one sitting
81 : $buf
82 } else {
83 die 'unknown format ' . $t . ', ';
84 }
85 }
86
87 sub multihash_unwrap($bytes) {
88 utf8::downgrade($bytes, 1);
89
90 my ($t, $bread_type) = varint_decode_raw($bytes);
91 if(my $e = _map_by_tag($t)) {
92 my ($l, $bread_len) = varint_decode_raw(substr($bytes, $bread_type));
93 return wantarray
94 ? ([$e->[0], $e->[1] ], substr($bytes, $bread_type + $bread_len)) # allows us to get the whole kit and kaboodle in one sitting
95 : substr($bytes, $bread_type + $bread_len)
96 } else {
97 die 'unknown format ' . $t . ', ';
98 }
99 }
100
101 sub multihash_wrap($as, $bytes) {
102 utf8::downgrade($bytes, 1);
103 if(my $e = _map_by_name($as)) {
104 return varint_encode($e->[1]) . varint_encode(length($bytes)) . $bytes;
105 } else {
106 die 'unknown format ' . $as . ', ';
107 }
108 }
109
110 sub multihash_encode($as, $bytes) {
111 utf8::downgrade($bytes, 1);
112 if(my $e = _map_by_name($as)) {
113 my $hash = $e->[3]->($bytes);
114 return varint_encode($e->[1]) . varint_encode(length($hash)) . $hash;
115 } else {
116 die 'unknown format ' . $as . ', ';
117 }
118 }
119}
120
121=pod
122
123=head1 NAME
124
125Multiformats::Multihash - Multihash encoding/decoding/wrapping
126
127=head1 SYNOPSIS
128
129 use Multiformats::Multihash qw/multihash_encode multihash_decode multihash_unwrap multihash_wrap/;
130 my $data = '...';
131
132 my $encoded = multihash_encode('sha2-256', $data);
133
134 my $hash = Digest::SHA::sha256($data);
135
136 my $encoded_also = multihash_wrap('sha2-256', $hash);
137
138=head1 FUNCTIONS
139
140=head2 multihash_encode($hash_function, $data)
141
142Hashes the data with the given hash function, and encodes the result into a Multihash
143
144=head2 multihash_decode($data)
145
146Parses the used hash function and hash length for validity, and returns the original raw hash
147
148=head2 multihash_unwrap($data)
149
150Acts similar to C<multihash_decode> when called in scalar context, but when called in list context returns a list containing the encoding/decoding array and the raw hash. The decoding arrayref has the hash function name as first element, and the hash function tag value as the second value.
151
152 my ($encoding, $raw_hash) = multihash_unwrap($data)
153
154 $encoding->[0]; # e.g. 'sha2-256'
155 $encoding->[1]; # e.g. 0x12
156
157=head2 multihash_wrap($hash_function, $data)
158
159Acts similar to C<multihash_encode> but assumes the data passed is already a raw hash so does not digest it before encoding to a Multihash
160
161=head1 SUPPORTED HASHES
162
163=over
164
165=item * identity
166
167=item * sha1
168
169=item * sha2-256
170
171=item * sha2-512
172
173=item * sha3-384
174
175=item * sha3-256
176
177=item * sha3-224
178
179=item * sha2-384
180
181=back
182
183=cut
184
1851;