ffi-libsodium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +191 -0
- data/README.md +21 -0
- data/lib/crypto/aead/chacha20_poly1305.rb +75 -0
- data/lib/crypto/auth.rb +54 -0
- data/lib/crypto/box.rb +150 -0
- data/lib/crypto/generic_hash.rb +123 -0
- data/lib/crypto/one_time_auth.rb +87 -0
- data/lib/crypto/pw_hash/scrypt_salsa208_sha256.rb +101 -0
- data/lib/crypto/scalar_mult.rb +55 -0
- data/lib/crypto/secret_box.rb +101 -0
- data/lib/crypto/short_hash.rb +41 -0
- data/lib/crypto/sign.rb +102 -0
- data/lib/libsodium.rb +21 -0
- data/lib/random_bytes.rb +21 -0
- data/lib/sodium.rb +69 -0
- data/lib/sodium/buffer.rb +11 -0
- data/lib/sodium/secret_buffer.rb +58 -0
- data/lib/sodium/utils.rb +71 -0
- data/lib/sodium/version.rb +3 -0
- metadata +90 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../sodium'
|
2
|
+
require_relative '../sodium/utils'
|
3
|
+
require_relative '../sodium/buffer'
|
4
|
+
require_relative '../sodium/secret_buffer'
|
5
|
+
|
6
|
+
module Crypto
|
7
|
+
module ShortHash
|
8
|
+
extend FFI::Library
|
9
|
+
extend Sodium::Utils
|
10
|
+
|
11
|
+
ffi_lib :libsodium
|
12
|
+
|
13
|
+
attach_function :primitive, :crypto_shorthash_primitive, [], :string
|
14
|
+
attach_function :bytes, :crypto_shorthash_bytes, [], :size_t
|
15
|
+
attach_function :keybytes, :crypto_shorthash_keybytes, [], :size_t
|
16
|
+
|
17
|
+
PRIMITIVE = primitive.freeze
|
18
|
+
BYTES = bytes.freeze
|
19
|
+
KEYBYTES = keybytes.freeze
|
20
|
+
|
21
|
+
attach_function :crypto_shorthash, [:buffer_out, :buffer_in, :ulong_long, :buffer_in], :int
|
22
|
+
|
23
|
+
module_function
|
24
|
+
|
25
|
+
def shorthash(short_data, key)
|
26
|
+
short_data_len = get_size(short_data)
|
27
|
+
check_length(key, KEYBYTES, :SecretKey)
|
28
|
+
|
29
|
+
siphash = Sodium::Buffer.new(:uchar, BYTES)
|
30
|
+
key.readonly if key.is_a?(Sodium::SecretBuffer)
|
31
|
+
crypto_shorthash(siphash, short_data, short_data_len, key)
|
32
|
+
key.noaccess if key.is_a?(Sodium::SecretBuffer)
|
33
|
+
|
34
|
+
siphash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.shorthash(*args)
|
39
|
+
ShortHash.shorthash(*args)
|
40
|
+
end
|
41
|
+
end
|
data/lib/crypto/sign.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative '../sodium'
|
2
|
+
require_relative '../sodium/utils'
|
3
|
+
require_relative '../sodium/buffer'
|
4
|
+
require_relative '../sodium/secret_buffer'
|
5
|
+
require_relative '../random_bytes'
|
6
|
+
|
7
|
+
module Crypto
|
8
|
+
module Sign
|
9
|
+
extend FFI::Library
|
10
|
+
extend Sodium::Utils
|
11
|
+
|
12
|
+
ffi_lib :libsodium
|
13
|
+
|
14
|
+
attach_function :primitive, :crypto_sign_primitive, [], :string
|
15
|
+
attach_function :bytes, :crypto_sign_bytes, [], :size_t
|
16
|
+
attach_function :seedbytes, :crypto_sign_seedbytes, [], :size_t
|
17
|
+
attach_function :publickeybytes, :crypto_sign_publickeybytes, [], :size_t
|
18
|
+
attach_function :secretkeybytes, :crypto_sign_secretkeybytes, [], :size_t
|
19
|
+
|
20
|
+
PRIMITIVE = primitive.freeze
|
21
|
+
BYTES = bytes.freeze
|
22
|
+
SEEDBYTES = seedbytes.freeze
|
23
|
+
PUBLICKEYBYTES = publickeybytes.freeze
|
24
|
+
SECRETKEYBYTES = secretkeybytes.freeze
|
25
|
+
|
26
|
+
attach_function :crypto_sign_keypair, [:buffer_out, :buffer_out], :int
|
27
|
+
attach_function :crypto_sign_seed_keypair, [:buffer_out, :buffer_out, :buffer_in], :int
|
28
|
+
|
29
|
+
attach_function :crypto_sign, [:buffer_out, :buffer_out, :buffer_in, :ulong_long, :buffer_in], :int
|
30
|
+
attach_function :crypto_sign_open, [:buffer_out, :buffer_out, :buffer_in, :ulong_long, :buffer_in], :int
|
31
|
+
|
32
|
+
module_function
|
33
|
+
|
34
|
+
def keypair
|
35
|
+
public_key = Sodium::Buffer.new(:uchar, PUBLICKEYBYTES)
|
36
|
+
secret_key = Sodium::Buffer.new(:uchar, SECRETKEYBYTES)
|
37
|
+
|
38
|
+
crypto_sign_keypair(public_key, secret_key)
|
39
|
+
|
40
|
+
[public_key, secret_key]
|
41
|
+
end
|
42
|
+
|
43
|
+
def seed_keypair(seed)
|
44
|
+
check_length(seed, SEEDBYTES, :Seed)
|
45
|
+
|
46
|
+
public_key = Sodium::Buffer.new(:uchar, PUBLICKEYBYTES)
|
47
|
+
secret_key = Sodium::Buffer.new(:uchar, SECRETKEYBYTES)
|
48
|
+
|
49
|
+
crypto_sign_seed_keypair(public_key, secret_key, seed)
|
50
|
+
|
51
|
+
[public_key, secret_key]
|
52
|
+
end
|
53
|
+
|
54
|
+
def memory_locked_keypair
|
55
|
+
public_key = Sodium::Buffer.new(:uchar, PUBLICKEYBYTES)
|
56
|
+
secret_key = Sodium::SecretBuffer.new(SECRETKEYBYTES)
|
57
|
+
crypto_sign_keypair(public_key, secret_key)
|
58
|
+
secret_key.noaccess
|
59
|
+
|
60
|
+
[public_key, secret_key]
|
61
|
+
end
|
62
|
+
|
63
|
+
def memory_locked_seed_keypair(seed)
|
64
|
+
check_length(seed, SEEDBYTES, :Seed)
|
65
|
+
|
66
|
+
public_key = Sodium::Buffer.new(:uchar, PUBLICKEYBYTES)
|
67
|
+
secret_key = Sodium::SecretBuffer.new(:uchar, SECRETKEYBYTES)
|
68
|
+
crypto_sign_seed_keypair(public_key, secret_key, seed)
|
69
|
+
secret_key.noaccess
|
70
|
+
|
71
|
+
[public_key, secret_key]
|
72
|
+
end
|
73
|
+
|
74
|
+
def sign(message, secret_key)
|
75
|
+
message_len = get_size(message)
|
76
|
+
check_length(secret_key, SECRETKEYBYTES, :SecretKey)
|
77
|
+
|
78
|
+
sealed_message = FFI::MemoryPointer.new(:uchar, BYTES + message_len)
|
79
|
+
sealed_message_len = FFI::MemoryPointer.new(:ulong_long)
|
80
|
+
secret_key.readonly if secret_key.is_a?(Sodium::SecretBuffer)
|
81
|
+
crypto_sign(sealed_message, sealed_message_len, message, message_len, secret_key)
|
82
|
+
secret_key.noaccess if secret_key.is_a?(Sodium::SecretBuffer)
|
83
|
+
|
84
|
+
[sealed_message, sealed_message_len.read_ulong_long]
|
85
|
+
end
|
86
|
+
|
87
|
+
def open(sealed_message, smlen, public_key)
|
88
|
+
sealed_message_len = get_int(smlen)
|
89
|
+
check_length(public_key, PUBLICKEYBYTES, :PublicKey)
|
90
|
+
|
91
|
+
unsealed_message = FFI::MemoryPointer.new(:uchar, sealed_message_len)
|
92
|
+
unsealed_message_len = FFI::MemoryPointer.new(:ulong_long)
|
93
|
+
crypto_sign_open(unsealed_message, unsealed_message_len, sealed_message, sealed_message_len, public_key)
|
94
|
+
|
95
|
+
[unsealed_message, unsealed_message_len.read_ulong_long]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.sign(*args)
|
100
|
+
Sign.sign(*args)
|
101
|
+
end
|
102
|
+
end
|
data/lib/libsodium.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'sodium'
|
2
|
+
require_relative 'sodium/utils'
|
3
|
+
require_relative 'sodium/buffer'
|
4
|
+
require_relative 'sodium/secret_buffer'
|
5
|
+
require_relative 'random_bytes'
|
6
|
+
require_relative 'crypto/secret_box'
|
7
|
+
require_relative 'crypto/auth'
|
8
|
+
require_relative 'crypto/aead/chacha20_poly1305'
|
9
|
+
require_relative 'crypto/box'
|
10
|
+
require_relative 'crypto/sign'
|
11
|
+
require_relative 'crypto/generic_hash'
|
12
|
+
require_relative 'crypto/short_hash'
|
13
|
+
require_relative 'crypto/pw_hash/scrypt_salsa208_sha256'
|
14
|
+
require_relative 'crypto/one_time_auth'
|
15
|
+
require_relative 'crypto/scalar_mult'
|
16
|
+
|
17
|
+
Thread.exclusive do
|
18
|
+
if Sodium.init == -1
|
19
|
+
fail LoadError, 'Could not initialize sodium'
|
20
|
+
end
|
21
|
+
end
|
data/lib/random_bytes.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'sodium/buffer'
|
2
|
+
|
3
|
+
module RandomBytes
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib :libsodium
|
6
|
+
|
7
|
+
attach_function :randombytes_buf, [:buffer_out, :size_t], :void, blocking: true
|
8
|
+
|
9
|
+
attach_function :random, :randombytes_random, [], :uint32, blocking: true
|
10
|
+
attach_function :uniform, :randombytes_uniform, [:uint32], :uint32, blocking: true
|
11
|
+
attach_function :close, :randombytes_close, [], :int, blocking: true
|
12
|
+
attach_function :stir, :randombytes_stir, [], :void, blocking: true
|
13
|
+
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def buf(size)
|
17
|
+
buf = Sodium::Buffer.new(:uchar, size)
|
18
|
+
randombytes_buf(buf, size)
|
19
|
+
buf
|
20
|
+
end
|
21
|
+
end
|
data/lib/sodium.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Sodium
|
4
|
+
class CryptoError < StandardError; end
|
5
|
+
class LengthError < ArgumentError; end
|
6
|
+
class MemoryError < StandardError; end
|
7
|
+
|
8
|
+
extend FFI::Library
|
9
|
+
ffi_lib :libsodium
|
10
|
+
|
11
|
+
attach_function :init, :sodium_init, [], :int, blocking: true
|
12
|
+
|
13
|
+
attach_function :memcmp, :sodium_memcmp, [:buffer_in, :buffer_in, :size_t], :int
|
14
|
+
attach_function :memzero, :sodium_memzero, [:pointer, :size_t], :void, blocking: true
|
15
|
+
attach_function :free, :sodium_free, [:pointer], :void, blocking: true
|
16
|
+
attach_function :sodium_mlock, [:pointer, :size_t], :int, blocking: true
|
17
|
+
attach_function :sodium_munlock, [:pointer, :size_t], :int, blocking: true
|
18
|
+
attach_function :sodium_malloc, [:size_t], :pointer, blocking: true
|
19
|
+
attach_function :sodium_allocarray, [:size_t, :size_t], :pointer, blocking: true
|
20
|
+
attach_function :sodium_mprotect_noaccess, [:pointer], :int, blocking: true
|
21
|
+
attach_function :sodium_mprotect_readonly, [:pointer], :int, blocking: true
|
22
|
+
attach_function :sodium_mprotect_readwrite, [:pointer], :int, blocking: true
|
23
|
+
|
24
|
+
module_function
|
25
|
+
|
26
|
+
def mlock(addr, len)
|
27
|
+
if sodium_mlock(addr, len) == -1
|
28
|
+
raise MemoryError, "Could not lock length=#{len.to_int} bytes memory at address=#{addr.address}", caller
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def munlock(addr, len)
|
33
|
+
if sodium_munlock(addr, len) == -1
|
34
|
+
raise MemoryError, "Could not unlock length=#{len.to_int} bytes memory at address=#{addr.address}", caller
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def malloc(size)
|
39
|
+
unless (mem = sodium_malloc(size))
|
40
|
+
raise NoMemoryError, "Failed to allocate memory size=#{size.to_int} bytes", caller
|
41
|
+
end
|
42
|
+
mem
|
43
|
+
end
|
44
|
+
|
45
|
+
def allocarray(count, size)
|
46
|
+
unless (mem = sodium_allocarray(count, size))
|
47
|
+
raise NoMemoryError, "Failed to allocate memory size=#{count.to_int * size.to_int} bytes", caller
|
48
|
+
end
|
49
|
+
mem
|
50
|
+
end
|
51
|
+
|
52
|
+
def noaccess(ptr)
|
53
|
+
if sodium_mprotect_noaccess(ptr) == -1
|
54
|
+
raise MemoryError, "Memory at address=#{ptr.address} is not secured with #{self}.malloc", caller
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def readonly(ptr)
|
59
|
+
if sodium_mprotect_readonly(ptr) == -1
|
60
|
+
raise MemoryError, "Memory at address=#{ptr.address} is not secured with #{self}.malloc", caller
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def readwrite(ptr)
|
65
|
+
if sodium_mprotect_readwrite(ptr) == -1
|
66
|
+
raise MemoryError, "Memory at address=#{ptr.address} is not secured with #{self}.malloc", caller
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative '../sodium'
|
2
|
+
|
3
|
+
module Sodium
|
4
|
+
class SecretBuffer
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@buffer, :address, :to_i
|
8
|
+
|
9
|
+
attr_reader :size
|
10
|
+
|
11
|
+
def initialize(size)
|
12
|
+
@size = Utils.get_int(size)
|
13
|
+
@buffer = Sodium.malloc(@size)
|
14
|
+
setup_finalizer
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_ptr
|
18
|
+
@buffer
|
19
|
+
end
|
20
|
+
|
21
|
+
def free
|
22
|
+
remove_finalizer
|
23
|
+
readwrite
|
24
|
+
Sodium.free(@buffer)
|
25
|
+
@size = @buffer = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def noaccess
|
29
|
+
Sodium.noaccess(@buffer)
|
30
|
+
end
|
31
|
+
|
32
|
+
def readonly
|
33
|
+
Sodium.readonly(@buffer)
|
34
|
+
end
|
35
|
+
|
36
|
+
def readwrite
|
37
|
+
Sodium.readwrite(@buffer)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def setup_finalizer
|
43
|
+
ObjectSpace.define_finalizer(@buffer, self.class.free(address))
|
44
|
+
end
|
45
|
+
|
46
|
+
def remove_finalizer
|
47
|
+
ObjectSpace.undefine_finalizer @buffer
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.free(address)
|
51
|
+
->(obj_id) do
|
52
|
+
Sodium.readwrite(FFI::Pointer.new(address))
|
53
|
+
Sodium.free(FFI::Pointer.new(address))
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/sodium/utils.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../sodium'
|
2
|
+
|
3
|
+
module Sodium
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def check_length(data, length, description)
|
9
|
+
if data.is_a?(String) ||data.respond_to?(:bytesize)
|
10
|
+
unless data.bytesize == length.to_int
|
11
|
+
fail Sodium::LengthError, "Expected a length=#{length.to_int} bytes #{description}, got size=#{data.bytesize} bytes", caller
|
12
|
+
end
|
13
|
+
elsif data.is_a?(FFI::Pointer) ||data.respond_to?(:size)
|
14
|
+
unless data.size == length.to_int
|
15
|
+
fail Sodium::LengthError, "Expected a length=#{length.to_int} bytes #{description}, got size=#{data.size} bytes", caller
|
16
|
+
end
|
17
|
+
else
|
18
|
+
fail ArgumentError, "#{description} must be of type String or FFI::Pointer and be length=#{length.to_int} bytes long", caller
|
19
|
+
end
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_pointer(ptr)
|
24
|
+
if ptr.is_a?(FFI::Pointer)
|
25
|
+
ptr
|
26
|
+
elsif ptr.respond_to?(:to_ptr)
|
27
|
+
ptr.to_ptr
|
28
|
+
else
|
29
|
+
fail ArgumentError, "#{ptr.class} is not a FFI::Pointer", caller
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_string(string)
|
34
|
+
if string.is_a?(String)
|
35
|
+
string
|
36
|
+
elsif string.respond_to?(:to_str)
|
37
|
+
string.to_str
|
38
|
+
elsif string.respond_to?(:read_string)
|
39
|
+
string.read_string
|
40
|
+
else
|
41
|
+
fail ArgumentError, "#{string.class} is not a String", caller
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_int(int)
|
46
|
+
if int.is_a?(Integer)
|
47
|
+
int
|
48
|
+
elsif int.respond_to?(:to_int)
|
49
|
+
int.to_int
|
50
|
+
else
|
51
|
+
fail ArgumentError, "#{int.class} is not a Integer", caller
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_size(data)
|
56
|
+
if data.is_a?(String) ||data.respond_to?(:bytesize)
|
57
|
+
data.bytesize
|
58
|
+
elsif data.is_a?(FFI::Pointer) ||data.respond_to?(:size)
|
59
|
+
data.size
|
60
|
+
else
|
61
|
+
fail ArgumentError, "#{data.class} doesn't respond to :bytesize or :size", caller
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ZERO = "\0".force_encoding(Encoding::ASCII_8BIT).freeze
|
66
|
+
|
67
|
+
def zeros(n)
|
68
|
+
ZERO * n
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-libsodium
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hendrik Beskow
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.9.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.9.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
description: libsodium ffi wrapper
|
42
|
+
email:
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- lib/crypto/aead/chacha20_poly1305.rb
|
50
|
+
- lib/crypto/auth.rb
|
51
|
+
- lib/crypto/box.rb
|
52
|
+
- lib/crypto/generic_hash.rb
|
53
|
+
- lib/crypto/one_time_auth.rb
|
54
|
+
- lib/crypto/pw_hash/scrypt_salsa208_sha256.rb
|
55
|
+
- lib/crypto/scalar_mult.rb
|
56
|
+
- lib/crypto/secret_box.rb
|
57
|
+
- lib/crypto/short_hash.rb
|
58
|
+
- lib/crypto/sign.rb
|
59
|
+
- lib/libsodium.rb
|
60
|
+
- lib/random_bytes.rb
|
61
|
+
- lib/sodium.rb
|
62
|
+
- lib/sodium/buffer.rb
|
63
|
+
- lib/sodium/secret_buffer.rb
|
64
|
+
- lib/sodium/utils.rb
|
65
|
+
- lib/sodium/version.rb
|
66
|
+
homepage: https://github.com/Asmod4n/ruby-ffi-libsodium
|
67
|
+
licenses:
|
68
|
+
- Apache-2.0
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.9.3
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.4.1
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: libsodium ffi wrapper
|
90
|
+
test_files: []
|