···11package Multiformats;
22use strict;
3344-# ABSTRACT: Implementation of multiformats as per https://multiformats.io/
44+# ABSTRACT: Implementation of several multiformats as per https://multiformats.io/ for use with ATprotocol
5566# VERSION
7788-# this package exists purely as a little placeholder for various abstracts and versions;
99-# the real meat is in the various sub-modules
88+# this package exists purely as a little placeholder for various abstracts and versions; as well as some
99+# of the documentation
101011111;
+70
lib/Multiformats/CID.pm
···11+package
22+ Multiformats::CID {
33+44+ use feature 'signatures';
55+ use Exporter 'import';
66+ our @EXPORT_OK = qw/cid/;
77+ use Multiformats::Varint qw/varint_decode_raw/;
88+ use Multiformats::Multicodec qw/multicodec_get_codec multicodec_wrap/;
99+ use Multiformats::Multibase qw/multibase_decode/;
1010+ use Multiformats::Multihash qw/multihash_unwrap/;
1111+1212+ sub cid($bytes) {
1313+ utf8::downgrade($bytes, 1);
1414+1515+ # so a v0 and v1 cid in binary should start with either 0x00 or 0x01 - if that isn't the case
1616+ # assume we have a string cid
1717+ if(substr($bytes, 0, 1) ne "\0" && substr($bytes, 0, 1) ne "\1") {
1818+ my $binary = multibase_decode($bytes);
1919+ return cid_from_binary($binary);
2020+ } else {
2121+ # binary
2222+ return cid_from_binary($bytes);
2323+ }
2424+ }
2525+2626+ sub cid_from_binary($bytes) {
2727+ utf8::downgrade($bytes, 1);
2828+ my ($version, $bread) = varint_decode_raw($bytes);
2929+ die 'Unsupported CID version ', $version, ', ' unless $version == 1;
3030+3131+ my ($mc_codec, $bread_codec) = varint_decode_raw(substr($bytes, $bread));
3232+3333+ my $mc = Multiformats::Multicodec::_get_by_tag($mc_codec);
3434+3535+ # not sure what that codec tag does in here because it doesn't appear to do
3636+ # anything short of encoding, well, nothing - the remaining data is the multihash
3737+ my ($mh, $hash) = multihash_unwrap(substr($bytes, $bread + $bread_codec));
3838+3939+ return Multiformats::CID::CIDv1->new(version => 1, codec => $mc->[0], hash_function => $mh->[0], hash => $hash);
4040+ }
4141+}
4242+4343+package
4444+ Multiformats::CID::CIDv1 {
4545+ use Mojo::Base -base, -signatures;
4646+ use Multiformats::Multicodec qw/multicodec_wrap multicodec_unwrap/;
4747+ use Multiformats::Multibase qw/multibase_encode/;
4848+ use Multiformats::Varint qw/varint_encode/;
4949+ use Multiformats::Multihash qw/multihash_wrap/;
5050+ use overload bool => sub {1}, '""' => sub { shift->to_str }, fallback => 1;
5151+5252+ # note that the codecs are the tag values, not the names, we need to take this into account
5353+ # in multibase_encode and multihash_encode
5454+ has [qw/version codec hash_function hash/] => undef;
5555+5656+ sub to_str($self, $codec = 'base32') {
5757+ return multibase_encode($codec, $self->to_bytes);
5858+ }
5959+6060+ sub to_bytes($self) {
6161+ my $hash = multihash_wrap($self->hash_function, $self->hash);
6262+ my $content = multicodec_wrap($self->codec, $hash);
6363+ my $version = varint_encode($self->version);
6464+ return $version . $content;
6565+ }
6666+}
6767+6868+1;
6969+7070+
+42-19
lib/Multiformats/Multibase.pm
···2222 return bless({}, $pkg);
2323 }
24242525- # these 2 maps map the actual encoding and decoding
2626- # to a subroutine that takes the to be decoded/encoded values as first argument
2727- # please note that only a few formats are implemented by default
2828- use constant MB_ENCODE_MAP => {
2929- 'none' => sub { return "\0" . shift },
3030- 'base32' => sub { return 'b' . encode_b32r(shift) },
3131- 'base36' => sub { return 'k' . encode_base36(shift) },
3232- 'base58btc' => sub { return 'z' . encode_b58b(shift) },
3333- };
2525+ # this map holds the various encodings and decodings
2626+ use constant MB_MAP => [
2727+ [ 'none', "\0", sub { return shift }, sub { return shift } ],
2828+ [ 'base32', 'b', sub { return lc(encode_b32r(shift)) }, sub { return decode_b32r(uc(shift)) } ],
2929+ [ 'base32upper','B', sub { return encode_b32r(shift) }, sub { return decode_b32r(shift) } ],
3030+ [ 'base36', 'k', sub { return lc(encode_base36(shift)) }, sub { return decode_base36(shift) } ],
3131+ [ 'base58btc', 'z', sub { return encode_b58b(shift) }, sub { return decode_b58b(shift) } ],
3232+ ];
3333+3434+ sub _map_by_tag($tag) {
3535+ foreach my $entry (@{__PACKAGE__->MB_MAP}) {
3636+ return $entry if($entry->[1] eq $tag);
3737+ }
3838+ return undef;
3939+ }
34403535- use constant MB_DECODE_MAP => {
3636- "\0" => sub { return shift },
3737- 'b' => sub { return decode_b32r(shift) },
3838- 'k' => sub { return decode_base36(shift) },
3939- 'z' => sub { return decode_b58b(shift) },
4040- };
4141+ sub _map_by_name($name) {
4242+ if(length($name) == 1) {
4343+ return _map_by_tag($name);
4444+ } else {
4545+ foreach my $entry (@{__PACKAGE__->MB_MAP}) {
4646+ return $entry if($entry->[0] eq $name);
4747+ }
4848+ return undef;
4949+ }
5050+ }
41514252 sub multibase_decode($bytes) {
5353+ # make sure it's actual bytes
5454+ utf8::downgrade($bytes, 1);
4355 my $t = substr($bytes, 0, 1);
4444- die 'unknown format ' . $t . ', ' unless exists MB_DECODE_MAP->{$t};
4545- return MB_DECODE_MAP->{$t}->(substr($bytes, 1));
5656+ if(my $e = _map_by_tag($t)) {
5757+ my $decoded = $e->[3]->(substr($bytes, 1));
5858+ return wantarray
5959+ ? ($t, $decoded)
6060+ : $decoded;
6161+ } else {
6262+ die 'unknown format ' . $t . ', ';
6363+ }
4664 }
47654866 sub multibase_encode($as, $bytes) {
4949- die 'unknown format ' . $as . ', ' unless exists MB_ENCODE_MAP->{$as};
5050- return MB_ENCODE_MAP->{$as}->($bytes);
6767+ utf8::downgrade($bytes, 1);
6868+ if(my $e = _map_by_name($as)) {
6969+ my $encoded = $e->[1] . $e->[2]->($bytes);
7070+ return $encoded;
7171+ } else {
7272+ die 'unknown format ' . $as . ', ';
7373+ }
5174 }
5275}
5376
+58
lib/Multiformats/Multicodec.pm
···11+package
22+ Multiformats::Multicodec {
33+ use strict;
44+ use warnings;
55+ use feature 'signatures';
66+ use Multiformats::Varint qw/varint_decode_raw varint_encode/;
77+88+ use Exporter 'import';
99+ our @EXPORT_OK = qw/multicodec_wrap multicodec_unwrap multicodec_get_codec/;
1010+1111+ use constant MULTICODEC_MAP => [
1212+ [ 'raw', 0x55 ],
1313+ [ 'dag-cbor', 0x71 ],
1414+ ];
1515+1616+ sub _get_by_name($as) {
1717+ foreach my $entry (@{__PACKAGE__->MULTICODEC_MAP}) {
1818+ return $entry if($entry->[0] eq $as);
1919+ }
2020+ return _get_by_tag($as);
2121+ }
2222+2323+ sub _get_by_tag($tag) {
2424+ foreach my $entry (@{__PACKAGE__->MULTICODEC_MAP}) {
2525+ return $entry if($entry->[1] == $tag);
2626+ }
2727+ return undef;
2828+ }
2929+3030+ sub multicodec_wrap($as, $value) {
3131+ utf8::downgrade($value, 1);
3232+ if(my $e = _get_by_name($as)) {
3333+ my $id = varint_encode($e->[1]);
3434+ return $id . $value;
3535+ } else {
3636+ die 'Unsupported multicodec type ', $as, ', ';
3737+ }
3838+ }
3939+4040+ sub multicodec_unwrap($value) {
4141+ utf8::downgrade($value, 1);
4242+ my ($id, $bytes) = varint_decode_raw($value);
4343+ return substr($value, $bytes);
4444+4545+ }
4646+4747+ sub multicodec_get_codec($value) {
4848+ utf8::downgrade($value, 1);
4949+ my ($id, $bytes) = varint_decode_raw($value);
5050+ if(my $e = _get_by_tag($id)) {
5151+ return $e;
5252+ } else {
5353+ die 'Unsupported multicodec type ', $id, ', ';
5454+ }
5555+ }
5656+}
5757+5858+1;
+103
lib/Multiformats/Multihash.pm
···11+package
22+ Multiformats::Multihash {
33+ use strict;
44+ use warnings;
55+ use feature 'signatures';
66+77+ use Exporter 'import';
88+ our @EXPORT_OK = qw/multihash_encode multihash_decode multihash_wrap multihash_unwrap/;
99+1010+ use Digest::SHA qw/sha1 sha256 sha384 sha512/; # SHA2
1111+ use Digest::SHA3 qw/sha3_224 sha3_384 sha3_256/;
1212+ use Multiformats::Varint qw/varint_decode_raw varint_encode/;
1313+1414+ sub decode($self, $value) {
1515+ return multihash_decode($value);
1616+ }
1717+1818+ sub encode($self, $as, $value) {
1919+ return multihash_encode($as, $value);
2020+ }
2121+2222+ sub new($pkg) {
2323+ return bless({}, $pkg);
2424+ }
2525+2626+ # this map holds the various encodings and decodings
2727+ use constant MULTIFORMAT_MAP => [
2828+ [ 'identity', 0x00, undef, sub { return shift } ],
2929+ [ 'sha1', 0x11, undef, sub { return sha1(shift) } ],
3030+ [ 'sha2-256', 0x12, undef, sub { return sha256(shift) } ],
3131+ [ 'sha2-512', 0x13, undef, sub { return sha512(shift) } ],
3232+ [ 'sha3-384', 0x15, undef, sub { return sha3_384(shift) } ],
3333+ [ 'sha3-256', 0x16, undef, sub { return sha3_256(shift) } ],
3434+ [ 'sha3-224', 0x17, undef, sub { return sha3_224(shift) } ],
3535+ [ 'sha2-384', 0x20, undef, sub { return sha_384(shift) } ],
3636+ ];
3737+3838+ sub codecs {
3939+ return __PACKAGE__->MULTIFORMAT_MAP;
4040+ }
4141+4242+ sub _map_by_tag($tag) {
4343+ foreach my $entry (@{__PACKAGE__->MULTIFORMAT_MAP}) {
4444+ return $entry if($entry->[1] == $tag);
4545+ }
4646+ return undef;
4747+ }
4848+4949+ sub _map_by_name($name) {
5050+ foreach my $entry (@{__PACKAGE__->MULTIFORMAT_MAP}) {
5151+ return $entry if($entry->[0] eq $name);
5252+ }
5353+ return _map_by_tag($name);
5454+ }
5555+5656+ sub multihash_decode($bytes) {
5757+ # make sure it's actual bytes
5858+ utf8::downgrade($bytes, 1);
5959+6060+ my ($t, $bread_type) = varint_decode_raw($bytes);
6161+ if(my $e = _map_by_tag($t)) {
6262+ my ($l, $bread_len) = varint_decode_raw(substr($bytes, $bread_type));
6363+ 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
6464+ } else {
6565+ die 'unknown format ' . $t . ', ';
6666+ }
6767+ }
6868+6969+ sub multihash_unwrap($bytes) {
7070+ utf8::downgrade($bytes, 1);
7171+7272+ my ($t, $bread_type) = varint_decode_raw($bytes);
7373+ if(my $e = _map_by_tag($t)) {
7474+ my ($l, $bread_len) = varint_decode_raw(substr($bytes, $bread_type));
7575+ return wantarray
7676+ ? ($e, substr($bytes, $bread_type + $bread_len)) # allows us to get the whole kit and kaboodle in one sitting
7777+ : substr($bytes, $bread_type + $bread_len)
7878+ } else {
7979+ die 'unknown format ' . $t . ', ';
8080+ }
8181+ }
8282+8383+ sub multihash_wrap($as, $bytes) {
8484+ utf8::downgrade($bytes, 1);
8585+ if(my $e = _map_by_name($as)) {
8686+ return varint_encode($e->[1]) . varint_encode(length($bytes)) . $bytes;
8787+ } else {
8888+ die 'unknown format ' . $as . ', ';
8989+ }
9090+ }
9191+9292+ sub multihash_encode($as, $bytes) {
9393+ utf8::downgrade($bytes, 1);
9494+ if(my $e = _map_by_name($as)) {
9595+ my $hash = $e->[3]->($bytes);
9696+ return varint_encode($e->[1]) . varint_encode(length($hash)) . $hash;
9797+ } else {
9898+ die 'unknown format ' . $as . ', ';
9999+ }
100100+ }
101101+}
102102+103103+1;
+4-4
lib/Multiformats/Varint.pm
···25252626 # varint_encode, varint_decode_raw and varint_decode lifted from python multiformats https://github.com/hashberg-io/multiformats
2727 sub varint_encode($value) {
2828- die 'PerlDS::Encoding::varint_encode: cannot encode negative values' unless $value >= 0;
2828+ die 'Multiformats::Varint::varint_encode: cannot encode negative values' unless $value >= 0;
2929 my @out = ();
3030 while(1) {
3131 my $next_byte = $value & 0b01111111;
···3737 last;
3838 }
3939 }
4040- die 'PerlDS::Encoding::varint_encode: encoded varint > 9 bytes' unless scalar(@out) <= 9;
4040+ die 'Multiformats::Varint::varint_encode: encoded varint > 9 bytes' unless scalar(@out) <= 9;
4141 return wantarray
4242 ? (pack('C*', @out), scalar(@out))
4343 : pack('C*', @out)
···46464747 sub varint_decode($value) {
4848 my ($x, $read) = varint_decode_raw($value);
4949- die 'PerlDS::Encoding::varint_decode: not all bytes used by encoding' if($read > length($value));
4949+ die 'Multiformats::Varint::varint_decode: not all bytes used by encoding' if($read > length($value));
5050 return $x;
5151 }
5252···5959 # via the num_bytes_read later
60606161 while($expect_next) {
6262- die 'PerlDS::Encoding::varint_decode: no next byte to read' if $num_bytes_read >= scalar(@buf);
6262+ die 'Multiformats::Varint::varint_decode_raw: no next byte to read' if $num_bytes_read >= scalar(@buf);
6363 my $next_byte = $buf[$num_bytes_read];
6464 $x += ($next_byte & 0b01111111) << (7 * $num_bytes_read);
6565 $expect_next = ($next_byte >> 7 == 0b1) ? 1 : undef;