rsa-accumulator 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cd67f53e0e5ea3506878a26976007fe1a02e7727b9fba6ecc9fa88078dc9a99
4
- data.tar.gz: d55e288f32e4cd7ef8691a9e9a15cb72ae8c6477969e1a4e110882fad751e58e
3
+ metadata.gz: cee011044852d3cda974e2a3b92cf0f762cf08982fc12abd153fe762841f0902
4
+ data.tar.gz: 4db2c478ce578bd09a0a5afad6248f616145116577390cb95a5446fd7eca3081
5
5
  SHA512:
6
- metadata.gz: 7eac33383c6998b3d4ece9c4ae554d3dea8359a12691f7e08b631a28d833e5a6ca10eec21b2b7c0b6cf3271d9b5b4b34c5f3a8b7a23f1bf1d699a15613470f8e
7
- data.tar.gz: cc87f12d335f15e97dbdbb93175f1420928da5074ee489ca42cc60c729cea3ecda36ffa3ae5b56f756019f726131b4b978210e44382274ed970533402439266a
6
+ metadata.gz: ad00946ab3cc16554b8013d166807f7265604519373288b49b1ce401037da0f7352215e98a93ef7b180751eb4d6aa3768782da4faf2c814a24c819c6a54fb9a9
7
+ data.tar.gz: d16fb0cab0cc0c715d590eba67a6a18a9bfef80ebb83cdc51632677a2644819570c60a7e80e82672c98635d33fe9015cbbb970f4641ec7e60c432334eb314c7e
@@ -1,6 +1,11 @@
1
1
  name: Ruby
2
2
 
3
- on: [push]
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
4
9
 
5
10
  jobs:
6
11
  build:
@@ -8,11 +13,11 @@ jobs:
8
13
  runs-on: ubuntu-latest
9
14
  strategy:
10
15
  matrix:
11
- ruby: ['2.5.x', '2.6.x']
16
+ ruby: ['3.0', '3.1', '3.2', '3.3']
12
17
  steps:
13
- - uses: actions/checkout@v1
18
+ - uses: actions/checkout@v2
14
19
  - name: Set up Ruby environment
15
- uses: actions/setup-ruby@v1
20
+ uses: ruby/setup-ruby@v1
16
21
  with:
17
22
  ruby-version: ${{ matrix.ruby }}
18
23
  - name: Build and test with Rake
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.3
1
+ ruby-3.0.0
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # RSA Accumulator for Ruby [![Build Status](https://travis-ci.org/chaintope/rsa-accumulatorrb.svg?branch=master)](https://travis-ci.org/chaintope/rsa-accumulatorrb) [![Gem Version](https://badge.fury.io/rb/rsa-accumulator.svg)](https://badge.fury.io/rb/rsa-accumulator) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
1
+ # RSA Accumulator for Ruby [![Build Status](https://github.com/chaintope/rsa-accumulatorrb/actions/workflows/ruby.yml/badge.svg?branch=master)](https://github.com/chaintope/rsa-accumulatorrb/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/rsa-accumulator.svg)](https://badge.fury.io/rb/rsa-accumulator) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
2
2
 
3
3
 
4
4
  Cryptographic accumulator based on the strong RSA assumption [BBF18](https://eprint.iacr.org/2018/1188.pdf) in Ruby.
@@ -23,16 +23,30 @@ Or install it yourself as:
23
23
 
24
24
  ### Setup accumulator
25
25
 
26
- First, initialize the accumulator. Since the accumulator uses groups of unknown order, it can be generated in following ways:
26
+ First, initialize the accumulator. The accumulator works in a group of unknown order, so its
27
+ security depends on **nobody knowing the factorization of the modulus**. There are three ways
28
+ to obtain one:
27
29
 
28
30
  require 'rsa-accumulator'
29
-
30
- # using RSA modulus published by RSA Laboratory
31
+
32
+ # 1. RSA-2048, the modulus published by RSA Laboratories. Its factorization has never
33
+ # been found, so no trusted setup is required. This is the recommended default.
31
34
  acc = RSA::Accumulator.generate_rsa2048
32
35
 
33
- # using Random RSA modulus with a specified bit length(default value is )
36
+ # 2. A modulus you obtained elsewhere, e.g. from a multi-party trusted setup ceremony.
37
+ acc = RSA::Accumulator.generate_with_modulus(n)
38
+
39
+ # 3. A freshly generated random RSA modulus with the given bit length (default: 3072).
34
40
  acc = RSA::Accumulator.generate_random(2048)
35
41
 
42
+ > [!WARNING]
43
+ > `generate_random` is a **trusted setup**. It derives the modulus from a newly generated RSA
44
+ > key, so the process that calls it learns `p` and `q`. Anyone holding the factorization can
45
+ > compute an x-th root of any value, and can therefore forge a membership proof for an element
46
+ > that was never added, as well as a non-membership proof for an element that was. Use it only
47
+ > when every verifier of the resulting proofs trusts the party that created the accumulator.
48
+ > Otherwise use `generate_rsa2048` or `generate_with_modulus`.
49
+
36
50
  ### Adding elements and membership proof
37
51
 
38
52
  You can add arbitrary String data to the accumulator.
@@ -6,29 +6,54 @@ module RSA
6
6
 
7
7
  using RSA::ACC::Ext
8
8
 
9
+ # Domain separation tags. Elements, Fiat-Shamir challenges and plain hashes are hashed
10
+ # with a distinct tag each, so that a caller supplied element can never be made to
11
+ # produce the same digest as a challenge over group elements.
12
+ ELEMENT_DST = 'RSA-ACC/v1/element'.freeze
13
+ CHALLENGE_DST = 'RSA-ACC/v1/challenge'.freeze
14
+ HASH_DST = 'RSA-ACC/v1/hash'.freeze
15
+
16
+ # Computes the canonical representative of +elem+ in the quotient group Z_n^* / {+-1}.
17
+ # Z_n^* always contains the known order-2 element -1, and the Adaptive Root Assumption
18
+ # which NI-PoE and NI-PoKE2 rely on does not hold in a group with a known low order
19
+ # element. Identifying y with -y removes it, so every group element MUST be normalized
20
+ # before it is hashed into a challenge, compared, or published as part of a proof.
21
+ # @param [Integer] elem an element of Z_n^*.
22
+ # @param [Integer] modulus modulus of the group.
23
+ # @return [Integer] the smaller of +elem+ and -+elem+ modulo +modulus+.
24
+ def normalize(elem, modulus)
25
+ y = elem % modulus
26
+ neg = modulus - y
27
+ y < neg ? y : neg
28
+ end
29
+
9
30
  # Convert element to prime number.
10
- # @param [Array[String] elements an element to be converted.
31
+ # @param [String] element an element to be converted.
11
32
  # @return [Integer] prime number.
12
33
  def hash_to_prime(element)
13
- nonce = 0
14
- loop do
15
- candidate = RbNaCl::Hash.blake2b(element + even_hex(nonce)).unpack("C*")
16
- candidate[-1] |= 1
17
- candidate = candidate.pack('c*').unpack("H*").first.to_i(16)
18
- if candidate.to_bn.prime?
19
- return candidate
20
- end
21
- nonce += 1
22
- end
34
+ # Not String#to_s: that would map nil and other objects onto the prime of their
35
+ # string form, so nil and "" would become the same element.
36
+ raise TypeError, "element must be a String, got #{element.class}." unless element.is_a?(String)
37
+ prime_from(ELEMENT_DST, [element])
23
38
  end
24
39
 
25
40
  # Converts a list of elements to an product of prime numbers.
26
41
  # @param [Array[String]] elements a list of element.
27
42
  # @return [Integer] an product of prime numbers
43
+ # @raise [ArgumentError] If +elements+ is not a non-empty list of String.
28
44
  def elements_to_prime(elements)
45
+ raise ArgumentError, 'elements must be a non-empty Array of String.' unless valid_elements?(elements)
29
46
  elements.map{|e|hash_to_prime(e)}.inject(:*)
30
47
  end
31
48
 
49
+ # Check whether +elements+ can be converted to a product of primes. Verifiers use this
50
+ # to turn a malformed proof into a false result instead of an exception.
51
+ # @param [Object] elements
52
+ # @return [Boolean]
53
+ def valid_elements?(elements)
54
+ elements.is_a?(Array) && !elements.empty? && elements.all? { |e| e.is_a?(String) }
55
+ end
56
+
32
57
  # Computes (xy) th root of g given xth and yth roots of g. x and y is co-prime.
33
58
  # (a, b) ← Bezout(x, y)
34
59
  #
@@ -38,10 +63,14 @@ module RSA
38
63
  # @param [Integer] y
39
64
  # @return [Integer] w1^b * w2^a
40
65
  def shamir_trick(w1, w2, x, y, modulus)
41
- raise ArgumentError, 'w1^x != w2^y' unless w1.pow(x, modulus) == w2.pow(y, modulus)
66
+ w1 = normalize(w1, modulus)
67
+ w2 = normalize(w2, modulus)
68
+ unless normalize(w1.pow(x, modulus), modulus) == normalize(w2.pow(y, modulus), modulus)
69
+ raise ArgumentError, 'w1^x != w2^y'
70
+ end
42
71
  a, b = egcd(x, y)
43
72
  raise ArgumentError, 'Inputs does not co-prime.' unless a * x + b * y == 1
44
- (w1.pow(b, modulus) * w2.pow(a, modulus)) % modulus
73
+ normalize(w1.pow(b, modulus) * w2.pow(a, modulus), modulus)
45
74
  end
46
75
 
47
76
  # Computes Bezout coefficients.
@@ -59,18 +88,54 @@ module RSA
59
88
  # @param [Array[Integer]] params
60
89
  # @return [Integer] prime number of challenge.
61
90
  def compute_challenge(*params)
62
- hash_to_prime(params.map{|p|even_hex(p)}.join)
91
+ prime_from(CHALLENGE_DST, params.map { |p| even_hex(p) })
63
92
  end
64
93
 
65
94
  # Computes hash value from +params+.
66
95
  # @param [Array[Integer]] params
67
96
  # @return [Integer] hash value.
68
97
  def blake2_hash(*params)
69
- RbNaCl::Hash.blake2b(params.map{|p|even_hex(p)}.join).unpack("H*").first.to_i(16)
98
+ digest = RbNaCl::Hash.blake2b(encode_fields(HASH_DST, params.map { |p| even_hex(p) }))
99
+ digest.unpack("H*").first.to_i(16)
70
100
  end
71
101
 
72
102
  private
73
103
 
104
+ # Serializes +dst+ and +fields+ by prefixing each of them with its byte length, so that
105
+ # the resulting byte string can only be produced by this exact sequence of inputs.
106
+ # A plain concatenation is ambiguous: even_hex is variable length, so for example
107
+ # (0x01, 0x2345) and (0x0123, 0x45) both join to "012345".
108
+ # @param [String] dst domain separation tag.
109
+ # @param [Array[String]] fields fields to be serialized.
110
+ # @return [String] a binary string.
111
+ def encode_fields(dst, fields)
112
+ fields.inject(length_prefixed(dst)) { |buf, field| buf << length_prefixed(field) }
113
+ end
114
+
115
+ # @param [String] data
116
+ # @return [String] +data+ prefixed with its byte length as a 64 bit big endian integer.
117
+ def length_prefixed(data)
118
+ bytes = data.b
119
+ [bytes.bytesize].pack('Q>') << bytes
120
+ end
121
+
122
+ # Hashes +fields+ under the domain +dst+ repeatedly, incrementing a nonce, until the
123
+ # digest is prime.
124
+ # @param [String] dst domain separation tag.
125
+ # @param [Array[String]] fields fields to be hashed.
126
+ # @return [Integer] prime number.
127
+ def prime_from(dst, fields)
128
+ nonce = 0
129
+ loop do
130
+ digest = RbNaCl::Hash.blake2b(encode_fields(dst, fields + [even_hex(nonce)])).unpack("C*")
131
+ digest[0] |= 0x80 # keep the output width fixed, a leading zero byte would shorten it
132
+ digest[-1] |= 1 # 2 is the only even prime, so only odd candidates are worth testing
133
+ candidate = digest.pack('C*').unpack("H*").first.to_i(16)
134
+ return candidate if candidate.to_bn.prime?
135
+ nonce += 1
136
+ end
137
+ end
138
+
74
139
  # Convert +num+ to even hex string.
75
140
  # @param [Integer] num
76
141
  # @return [String] hex string.
data/lib/rsa/acc/poe.rb CHANGED
@@ -17,9 +17,11 @@ module RSA
17
17
  # @param [Integer] result such as result = base^exp.
18
18
  # @param [Integer] modulus modulus using computation.
19
19
  def prove(base, exp, result, modulus)
20
+ base = normalize(base, modulus)
21
+ result = normalize(result, modulus)
20
22
  l = compute_challenge(base, exp, result)
21
23
  q = exp / l
22
- base.pow(q, modulus)
24
+ normalize(base.pow(q, modulus), modulus)
23
25
  end
24
26
 
25
27
  # Verifies that base^exp = result using the given proof to avoid computation.
@@ -29,9 +31,16 @@ module RSA
29
31
  # @param [Integer] proof an proof.
30
32
  # @param [Integer] modulus modulus using computation.
31
33
  def verify(base, exp, result, proof, modulus)
34
+ # A proof comes from an untrusted prover, so a malformed one is a false result and
35
+ # not an exception.
36
+ return false unless [base, exp, result, proof, modulus].all? { |v| v.is_a?(Integer) }
37
+ return false unless exp.positive? && modulus > 1
38
+ base = normalize(base, modulus)
39
+ result = normalize(result, modulus)
40
+ proof = normalize(proof, modulus)
32
41
  l = compute_challenge(base, exp, result)
33
42
  r = exp % l
34
- w = (proof.pow(l, modulus) * base.pow(r, modulus)) % modulus
43
+ w = normalize(proof.pow(l, modulus) * base.pow(r, modulus), modulus)
35
44
  w == result
36
45
  end
37
46
 
data/lib/rsa/acc/poke2.rb CHANGED
@@ -43,11 +43,14 @@ module RSA
43
43
  # @return [RSA::ACC::PoKE2Proof] a proof.
44
44
  def prove(base, exp, result, modulus)
45
45
  g = RSA::Accumulator::RSA2048_UNKNOWN_ELEM
46
- z = g.pow(exp, modulus)
46
+ base = normalize(base, modulus)
47
+ result = normalize(result, modulus)
48
+ z = normalize(g.pow(exp, modulus), modulus)
47
49
  l = compute_challenge(base, result, z)
48
50
  alpha = blake2_hash(base, result, z, l)
49
51
  q, r = exp.divmod(l)
50
- RSA::ACC::PoKE2Proof.new(z, ((base * g.pow(alpha, modulus)) % modulus).pow(q, modulus), r)
52
+ u = (base * g.pow(alpha, modulus)) % modulus
53
+ RSA::ACC::PoKE2Proof.new(z, normalize(u.pow(q, modulus), modulus), r)
51
54
  end
52
55
 
53
56
  # Verifies that the prover knows +exp+ s.t. +base+ ^ +exp+ = +result+
@@ -57,11 +60,21 @@ module RSA
57
60
  # @param [Integer] modulus
58
61
  # @return [Boolean] Returns true for successful verification, false otherwise.
59
62
  def verify(base, result, proof, modulus)
63
+ # A proof comes from an untrusted prover, so a malformed one is a false result and
64
+ # not an exception.
65
+ return false unless proof.is_a?(RSA::ACC::PoKE2Proof)
66
+ return false unless [base, result, modulus, proof.z, proof.q, proof.r].all? { |v| v.is_a?(Integer) }
67
+ return false unless modulus > 1 && !proof.r.negative?
60
68
  g = RSA::Accumulator::RSA2048_UNKNOWN_ELEM
61
- l = compute_challenge(base, result, proof.z)
62
- alpha = blake2_hash(base, result, proof.z, l)
63
- lhs = (proof.q.pow(l, modulus) * ((base * g.pow(alpha, modulus) % modulus)).pow(proof.r, modulus)) % modulus
64
- rhs = (result * proof.z.pow(alpha, modulus) % modulus)
69
+ base = normalize(base, modulus)
70
+ result = normalize(result, modulus)
71
+ z = normalize(proof.z, modulus)
72
+ q = normalize(proof.q, modulus)
73
+ l = compute_challenge(base, result, z)
74
+ alpha = blake2_hash(base, result, z, l)
75
+ u = (base * g.pow(alpha, modulus)) % modulus
76
+ lhs = normalize(q.pow(l, modulus) * u.pow(proof.r, modulus), modulus)
77
+ rhs = normalize(result * z.pow(alpha, modulus), modulus)
65
78
  lhs == rhs
66
79
  end
67
80
 
data/lib/rsa/acc/proof.rb CHANGED
@@ -20,10 +20,11 @@ module RSA
20
20
  end
21
21
 
22
22
  # Convert element to prime number.
23
- # @return [Integer] prime number of element.
23
+ # @return [Integer] prime number of element, or nil if the element can not be converted.
24
24
  def element_prime
25
- return nil if element.nil?
26
- element.is_a?(Array) ? elements_to_prime(element) : hash_to_prime(element)
25
+ elements = element.is_a?(Array) ? element : [element]
26
+ return nil unless valid_elements?(elements)
27
+ elements_to_prime(elements)
27
28
  end
28
29
 
29
30
  end
@@ -33,7 +34,7 @@ module RSA
33
34
 
34
35
  attr_reader :d # d = g^b
35
36
  attr_reader :v # v = A(current acc)^a
36
- attr_reader :gv_inv # gv_inv = v^{-1}
37
+ attr_reader :gv_inv # gv_inv = g * v^{-1}. Advisory only: the verifier re-derives this value and does not trust it.
37
38
  attr_reader :poke2_proof # NI-PoKE2(A, v, a)
38
39
  attr_reader :poe_proof # NI-PoE(d, x, g*v^{-1})
39
40
 
@@ -1,5 +1,5 @@
1
1
  module RSA
2
2
  module ACC
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -15,23 +15,49 @@ module RSA
15
15
  RSA2048_UNKNOWN_ELEM = 2
16
16
 
17
17
  attr_reader :n
18
- attr_accessor :value
18
+ attr_reader :value
19
19
  attr_reader :g # Initial value
20
20
  attr_reader :hold_elements # tha flag which indicate hold product of all elements.
21
21
  attr_accessor :products # (Optional) product of all elements in Accumulator
22
22
 
23
- # Generate accumulator using RSA2048 modulus.
23
+ # Generate accumulator using the RSA-2048 challenge modulus.
24
+ # Nobody is known to hold its factorization, so this requires no trusted setup and is
25
+ # the right choice whenever the party running the accumulator is not trusted by the
26
+ # party verifying its proofs.
24
27
  # @return [RSA::Accumulator]
25
28
  def self.generate_rsa2048(hold_elements: false)
26
29
  new(RSA2048_MODULUS, RSA2048_UNKNOWN_ELEM, RSA2048_UNKNOWN_ELEM, hold_elements)
27
30
  end
28
31
 
32
+ # Generate accumulator with an externally supplied modulus +n+ of unknown factorization.
33
+ # Use this with a modulus produced by a trusted setup ceremony(e.g. a multi-party
34
+ # computation) when the RSA-2048 modulus is not desired.
35
+ # @param [Integer] n modulus whose factorization is unknown to every participant.
36
+ # @return [RSA::Accumulator]
37
+ def self.generate_with_modulus(n, hold_elements: false)
38
+ raise ArgumentError, 'modulus must be an odd integer greater than 4.' unless n.is_a?(Integer) && n > 4 && n.odd?
39
+ new(n, RSA2048_UNKNOWN_ELEM, RSA2048_UNKNOWN_ELEM, hold_elements)
40
+ end
41
+
29
42
  # Generate accumulator with random modulus.
43
+ #
44
+ # WARNING: this is a TRUSTED SETUP. The modulus comes from a freshly generated RSA key,
45
+ # so the caller learns its factorization. Anyone who knows p and q can compute an x-th
46
+ # root of any value and therefore forge a membership proof for an element that was never
47
+ # added, and forge a non-membership proof for an element that was. The factorization is
48
+ # not retained by this method, but it existed in this process, so the resulting
49
+ # accumulator is only sound for verifiers who trust the caller. Use
50
+ # +generate_rsa2048+ or +generate_with_modulus+ when that trust does not hold.
51
+ #
30
52
  # @param [Integer] bit_length bit length of accumulator. Default: 3072 bits.
31
53
  # @return [RSA::Accumulator]
32
54
  def self.generate_random(bit_length = 3072, hold_elements: false)
33
55
  n = OpenSSL::PKey::RSA.generate(bit_length).n.to_i
34
56
  initial_value = SecureRandom.random_number(n)
57
+ # Reject the degenerate elements 0, 1 and n - 1, and anything sharing a factor with n.
58
+ until initial_value > 1 && initial_value < n - 1 && initial_value.gcd(n) == 1
59
+ initial_value = SecureRandom.random_number(n)
60
+ end
35
61
  new(n, initial_value, initial_value, hold_elements)
36
62
  end
37
63
 
@@ -44,11 +70,18 @@ module RSA
44
70
  # @return [RSA::Accumulator]
45
71
  def initialize(n, value, initial_acc, hold_elements, products = 1)
46
72
  @n = n
47
- @value = value
48
- @g = initial_acc
73
+ self.value = value
74
+ @g = normalize(initial_acc, n)
49
75
  @hold_elements = hold_elements
50
76
  @products = products if hold_elements
51
- puts "The feature which hold product of all elements is practical feature." if hold_elements
77
+ end
78
+
79
+ # Set the accumulator value. The value is stored as the canonical representative of the
80
+ # quotient group Z_n^* / {+-1}, so that the known order-2 element -1 cannot be used to
81
+ # break the soundness of NI-PoE and NI-PoKE2.
82
+ # @param [Integer] value a value of acc.
83
+ def value=(value)
84
+ @value = normalize(value, n)
52
85
  end
53
86
 
54
87
  # Add element to accumulator and get inclusion proof.
@@ -58,12 +91,7 @@ module RSA
58
91
  current_acc = value
59
92
  p = elements_to_prime(elements)
60
93
  self.value = value.pow(p, n)
61
- if hold_elements
62
- elements.each do |e|
63
- p = hash_to_prime(e)
64
- self.products *= p unless products.modulo(p) == 0
65
- end
66
- end
94
+ self.products *= p if hold_elements
67
95
  RSA::ACC::MembershipProof.new(elements, current_acc, value, RSA::ACC::PoE.prove(current_acc, p, value, n))
68
96
  end
69
97
 
@@ -79,7 +107,10 @@ module RSA
79
107
  # @param [RSA::ACC::MembershipProof] proof inclusion proof.
80
108
  # @return [Boolean] If element exist in acc return true, otherwise false.
81
109
  def member?(proof)
82
- RSA::ACC::PoE.verify(proof.witness, proof.element_prime, value, proof.proof, n)
110
+ return false unless proof.is_a?(RSA::ACC::MembershipProof)
111
+ x = proof.element_prime
112
+ return false if x.nil?
113
+ RSA::ACC::PoE.verify(proof.witness, x, value, proof.proof, n)
83
114
  end
84
115
 
85
116
  # Verifies a non-membership proof against the current accumulator and +elements+ whose non-inclusion is being proven.
@@ -87,9 +118,18 @@ module RSA
87
118
  # @param [RSA::ACC::NonMembershipProof] proof non-membership proof.
88
119
  # @return [Boolean]
89
120
  def non_member?(elements, proof)
121
+ return false unless proof.is_a?(RSA::ACC::NonMembershipProof)
122
+ return false unless valid_elements?(elements)
90
123
  x = elements_to_prime(elements)
91
- RSA::ACC::PoKE2.verify(value, proof.v, proof.poke2_proof, n) &&
92
- RSA::ACC::PoE.verify(proof.d, x, proof.gv_inv, proof.poe_proof, n)
124
+ return false unless valid_group_elem?(proof.v)
125
+ return false unless valid_group_elem?(proof.d)
126
+ v = normalize(proof.v, n)
127
+ d = normalize(proof.d, n)
128
+ # g * v^{-1} must be derived by the verifier. If proof#gv_inv were used as-is,
129
+ # a prover could pick an arbitrary d and claim gv_inv = d^x, which passes for any x.
130
+ gv_inv = normalize(g * v.pow(-1, n), n)
131
+ RSA::ACC::PoKE2.verify(value, v, proof.poke2_proof, n) &&
132
+ RSA::ACC::PoE.verify(d, x, gv_inv, proof.poe_proof, n)
93
133
  end
94
134
 
95
135
  # Generate membership proof for +elements+.
@@ -101,7 +141,7 @@ module RSA
101
141
  raise RSA::ACC::Error.new 'This accumulator does not hold the product of the elements.' unless hold_elements
102
142
  x = elements_to_prime(elements)
103
143
  return nil unless products.modulo(x) == 0
104
- witness = g.pow(products / x, n)
144
+ witness = normalize(g.pow(products / x, n), n)
105
145
  RSA::ACC::MembershipProof.new(elements, witness, value, RSA::ACC::PoE.prove(witness, x, value, n))
106
146
  end
107
147
 
@@ -116,9 +156,9 @@ module RSA
116
156
  a, b = egcd(s, x)
117
157
  raise ArgumentError, "Inputs not co-prime." unless a * s + b * x == 1
118
158
 
119
- v = value.pow(a, n)
120
- d = g.pow(b, n)
121
- gv_inv = (g * v.pow(-1, n)) % n
159
+ v = normalize(value.pow(a, n), n)
160
+ d = normalize(g.pow(b, n), n)
161
+ gv_inv = normalize(g * v.pow(-1, n), n)
122
162
 
123
163
  poke2_proof = RSA::ACC::PoKE2.prove(value, a, v, n)
124
164
  poe_proof = RSA::ACC::PoE.prove(d, x, gv_inv, n)
@@ -133,9 +173,12 @@ module RSA
133
173
  return RSA::ACC::MembershipProof.new(proofs.map(&:element).flatten, value, value, RSA::ACC::PoE.prove(value, 1, value, n)) if proofs.empty?
134
174
 
135
175
  witnesses = proofs.map do |proof|
176
+ raise RSA::ACC::Error, 'Invalid proof.' unless proof.is_a?(RSA::ACC::MembershipProof)
136
177
  p = proof.element_prime
137
- raise RSA::ACC::Error, 'Bad witness.' unless proof.witness.pow(p, n) == value
138
- [p, proof.witness]
178
+ raise RSA::ACC::Error, 'Invalid element.' if p.nil?
179
+ raise RSA::ACC::Error, 'Bad witness.' unless proof.witness.is_a?(Integer)
180
+ raise RSA::ACC::Error, 'Bad witness.' unless normalize(proof.witness.pow(p, n), n) == value
181
+ [p, normalize(proof.witness, n)]
139
182
  end
140
183
 
141
184
  current_value = value
@@ -156,6 +199,7 @@ module RSA
156
199
  # @param [Array[Integer]] f factorizations of the exponent x = x1, ..., xn.
157
200
  # @return [Array{Integer}] array of xi-th root
158
201
  def root_factor(*f)
202
+ raise ArgumentError, 'factorization must not be empty.' if f.empty?
159
203
  return [value] if f.size == 1
160
204
  half_n = f.size / 2
161
205
  g_l = RSA::Accumulator.new(n, value.pow(f[0...half_n].map.inject(:*), n), g, false)
@@ -165,5 +209,17 @@ module RSA
165
209
  [l, r].flatten
166
210
  end
167
211
 
212
+ private
213
+
214
+ # Check whether +elem+ is a non-degenerate element of the group Z_n^*.
215
+ # @param [Integer] elem an element supplied by an untrusted prover.
216
+ # @return [Boolean]
217
+ def valid_group_elem?(elem)
218
+ return false unless elem.is_a?(Integer)
219
+ y = normalize(elem, n)
220
+ # y == 1 covers both 1 and n - 1, which are the identity and the order-2 element.
221
+ y > 1 && y.gcd(n) == 1
222
+ end
223
+
168
224
  end
169
225
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsa-accumulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2020-07-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rbnacl
@@ -78,7 +77,6 @@ files:
78
77
  - ".rspec"
79
78
  - ".ruby-gemset"
80
79
  - ".ruby-version"
81
- - ".travis.yml"
82
80
  - CODE_OF_CONDUCT.md
83
81
  - Gemfile
84
82
  - LICENSE.txt
@@ -99,7 +97,6 @@ homepage: https://github.com/chaintope/rsa-accumulatorrb
99
97
  licenses:
100
98
  - MIT
101
99
  metadata: {}
102
- post_install_message:
103
100
  rdoc_options: []
104
101
  require_paths:
105
102
  - lib
@@ -114,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
111
  - !ruby/object:Gem::Version
115
112
  version: '0'
116
113
  requirements: []
117
- rubygems_version: 3.0.3
118
- signing_key:
114
+ rubygems_version: 3.6.9
119
115
  specification_version: 4
120
116
  summary: RSA accumulator implementation for Ruby.
121
117
  test_files: []
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: ruby
2
- addons:
3
- apt:
4
- packages:
5
- - libsodium-dev
6
- rvm:
7
- - 2.5.5
8
- - 2.6.3
9
- - 2.7.0