# frozen_string_literal: true class Multibase # Multicodec uses a variable-length unsigned integer to tag # data. We precompute here the types we are interested about. # Registered types are available at https://github.com/multiformats/multicodec/blob/master/table.csv MULTICODEC_PREFIXES = { 'rsa-pub': 0x1205, 'ed25519-pub': 0xED, 'mldsa-44-pub': 0x1210, }.transform_values do |code| bytes = [] while code > 127 bytes << ((code & 0x7F) | 128) code >>= 7 end bytes << code bytes.pack('C*') end.freeze ED25519_PUB_DER_HEADER = ['302a300506032b6570032100'].pack('H*').freeze ML_DSA_44_PUB_DER_HEADER = ['30820532300b06096086480165030403110382052100'].pack('H*').freeze class Error < StandardError; end def self.decode(string) raise Error, 'Multibase string is nil' if string.nil? case string[0] when 'u' Base64.urlsafe_decode64(string[1...]) when 'z' Base58.base58_to_binary(string[1...], :bitcoin) else raise Error, 'Invalid Multibase base' end end def self.decode_multicodec(string) binary = decode(string) MULTICODEC_PREFIXES.each do |tag, prefix| return [tag, binary[prefix.length..]] if binary.starts_with?(prefix) end raise Error, 'Unknown Multicodec tag' end def self.decode_key_to_pem(string) tag, binary = decode_multicodec(string) case tag when :'rsa-pub' [:rsa, OpenSSL::PKey::RSA.new(binary).to_pem] when :'ed25519-pub' raise Error, 'Invalid data size for ed25519-pub' unless binary.size == 32 der = ED25519_PUB_DER_HEADER + binary [:ed25519, "-----BEGIN PUBLIC KEY-----\n#{Base64.strict_encode64(der)}\n-----END PUBLIC KEY-----\n"] when :'mldsa-44-pub' raise Error, 'Invalid data size for mldsa-44-pub' unless binary.size == 1312 der = ML_DSA_44_PUB_DER_HEADER + binary [:'ml-dsa-44', "-----BEGIN PUBLIC KEY-----\n#{Base64.strict_encode64(der).scan(/.{,64}/).join("\n")}-----END PUBLIC KEY-----\n"] else raise Error, 'Unsupported key type' end rescue OpenSSL::PKey::PKeyError => e raise Error, e end end