From 3855d7ea022caf02b5daf4a59827862ed16529cd Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Sun, 17 May 2020 22:01:22 +0200 Subject: [PATCH 01/40] type test for salt in crypto_password_hash/3 --- src/prolog/lib/crypto.pl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index b93a9cff..be6b6b31 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -379,7 +379,7 @@ crypto_password_hash(Password0, Hash, Options) :- Algorithm = 'pbkdf2-sha512', % current default and only option option(algorithm(Algorithm), Options, Algorithm), ( member(salt(SaltBytes), Options) -> - true + must_be_bytes(SaltBytes, crypto_password_hash/2) ; crypto_n_random_bytes(16, SaltBytes) ), '$crypto_password_hash'(Password, SaltBytes, Iterations, HashBytes), @@ -601,8 +601,6 @@ encoding_bytes(utf8, Cs, Bs) :- ; domain_error(encryption_encoding, Cs, crypto) ). -char_code(Char, Code) :- atom_codes(Char, [Code]). - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Modular multiplicative inverse. From 95ca8a263007666fb210ed8ff6be6429c78b8bc2 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 09:58:09 +0200 Subject: [PATCH 02/40] throw instantiation error if the list of options contains a variable (#523) Many thanks to @notoria for the test case! --- src/prolog/lib/crypto.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index be6b6b31..7a246432 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -259,6 +259,10 @@ crypto_data_hkdf(Data0, L, Bytes, Options0) :- '$crypto_data_hkdf'(Data, SaltBytes, Info, Algorithm, L, Bytes). option(What, Options, Default) :- + ( member(V, Options), var(V) -> + instantiation_error(option/3) + ; true + ), ( member(What, Options) -> true ; What =.. [_,Default] ). From 70ad44adfd7110d7627949e4be29230aff784737 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 11:12:59 +0200 Subject: [PATCH 03/40] type check for length argument in crypto_data_hkdf/4 Reported by @notoria in #527. --- src/prolog/lib/crypto.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 7a246432..419c3d4c 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -250,6 +250,8 @@ hash_algorithm(sha512_256). crypto_data_hkdf(Data0, L, Bytes, Options0) :- functor_hash_options(algorithm, Algorithm, Options0, Options), + must_be(integer, L), + L >= 0, option(encoding(Encoding), Options, utf8), encoding_bytes(Encoding, Data0, Data), option(salt(SaltBytes), Options, []), From 23034dd4f54b45c9a707e8f8a6d37dffd0fe7a01 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 11:21:38 +0200 Subject: [PATCH 04/40] raise instantiation errors for variable encoding Reported by notoria in #527. Note that from a declarative perspective, it would indeed be valid to give answers for both available encodings. --- src/prolog/lib/crypto.pl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 419c3d4c..bbb80a60 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -184,7 +184,7 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). crypto_data_hash(Data0, Hash, Options0) :- must_be(list, Options0), - option(encoding(Encoding), Options0, utf8), + encoding_options(Encoding, Options0), encoding_bytes(Encoding, Data0, Data), functor_hash_options(algorithm, A, Options0, _), ( hash_algorithm(A) -> true @@ -193,6 +193,9 @@ crypto_data_hash(Data0, Hash, Options0) :- '$crypto_data_hash'(Data, HashBytes, A), hex_bytes(Hash, HashBytes). +encoding_options(Encoding, Options) :- + option(encoding(Encoding), Options, utf8), + must_be(atom, Encoding). default_hash(sha256). @@ -252,7 +255,7 @@ crypto_data_hkdf(Data0, L, Bytes, Options0) :- functor_hash_options(algorithm, Algorithm, Options0, Options), must_be(integer, L), L >= 0, - option(encoding(Encoding), Options, utf8), + encoding_options(Encoding, Options), encoding_bytes(Encoding, Data0, Data), option(salt(SaltBytes), Options, []), must_be_bytes(SaltBytes, crypto_data_hkdf/4), @@ -537,7 +540,7 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z], - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :- - option(encoding(Encoding), Options, utf8), + encoding_options(Encoding, Options), encoding_bytes(Encoding, PlainText0, PlainText), option(tag(Tag), Options, _), ( nonvar(Tag) -> @@ -586,7 +589,7 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :- must_be_bytes(Key, crypto_data_decrypt/6), must_be_bytes(IV, crypto_data_decrypt/6), must_be(atom, Algorithm), - option(encoding(Encoding), Options, utf8), + encoding_options(Encoding, Options), must_be(list, CipherText0), encoding_bytes(octet, CipherText0, CipherText1), append(CipherText1, Tag, CipherText), From e9f8b3591832cad22788c6507c8c81b8e5e31be3 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 11:29:35 +0200 Subject: [PATCH 05/40] centralize reasoning about encoding --- src/prolog/lib/crypto.pl | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index bbb80a60..12c5b73b 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -184,8 +184,7 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). crypto_data_hash(Data0, Hash, Options0) :- must_be(list, Options0), - encoding_options(Encoding, Options0), - encoding_bytes(Encoding, Data0, Data), + options_data_bytes(Options0, Data0, Data), functor_hash_options(algorithm, A, Options0, _), ( hash_algorithm(A) -> true ; domain_error(hash_algorithm, A, crypto_data_hash/3) @@ -193,9 +192,10 @@ crypto_data_hash(Data0, Hash, Options0) :- '$crypto_data_hash'(Data, HashBytes, A), hex_bytes(Hash, HashBytes). -encoding_options(Encoding, Options) :- +options_data_bytes(Options, Data, Bytes) :- option(encoding(Encoding), Options, utf8), - must_be(atom, Encoding). + must_be(atom, Encoding), + encoding_bytes(Encoding, Data, Bytes). default_hash(sha256). @@ -255,8 +255,7 @@ crypto_data_hkdf(Data0, L, Bytes, Options0) :- functor_hash_options(algorithm, Algorithm, Options0, Options), must_be(integer, L), L >= 0, - encoding_options(Encoding, Options), - encoding_bytes(Encoding, Data0, Data), + options_data_bytes(Options, Data0, Data), option(salt(SaltBytes), Options, []), must_be_bytes(SaltBytes, crypto_data_hkdf/4), option(info(Info0), Options, []), @@ -540,8 +539,7 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z], - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :- - encoding_options(Encoding, Options), - encoding_bytes(Encoding, PlainText0, PlainText), + options_data_bytes(Options, PlainText0, PlainText), option(tag(Tag), Options, _), ( nonvar(Tag) -> must_be_bytes(Tag, crypto_data_encrypt/6) From fac7ba70c87a3c69daec45bd18d078920ff0b510 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 13:11:39 +0200 Subject: [PATCH 06/40] crypto_password_hash/3: fail if the number of iterations is too high Discussed in #527. --- src/prolog/machine/system_calls.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4061bdbc..17d456ed 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5285,7 +5285,10 @@ impl MachineState { u64::try_from(n).unwrap() } Ok(Number::Integer(n)) => { - n.to_u64().unwrap() + match n.to_u64() { + Some(i) => { i } + None => { self.fail = true; return Ok(()); } + } } _ => { unreachable!() From fd732550d8ae83cbe17e83e366175227914ea64c Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 13:21:20 +0200 Subject: [PATCH 07/40] crypto_data_hkdf/4: Fail if the length is too long. Due to the way the counter is constructed in the HKDF specification, the requested output length can be at most 255 times the size of the digest algorithm's output. Reported by @notoria in #527. Many thanks! --- src/prolog/machine/system_calls.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 17d456ed..c71fc5ba 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5266,7 +5266,10 @@ impl MachineState { let salt = hkdf::Salt::new(digest_alg, &salt); let mut bytes : Vec = Vec::new(); bytes.resize(length, 0); - salt.extract(&data).expand(&[&info[..]], MyKey(length)).unwrap().fill(&mut bytes).unwrap(); + match salt.extract(&data).expand(&[&info[..]], MyKey(length)) { + Ok(r) => { r.fill(&mut bytes).unwrap(); } + _ => { self.fail = true; return Ok(()); } + } Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) }; From a423eb53237a01e468796ac3900e9fb4ae826811 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 18 May 2020 13:28:52 +0200 Subject: [PATCH 08/40] stronger validation of input lists for cryptographic routines Example: ?- crypto_data_hkdf(Var, 32, Bs, []). caught: error(instantiation_error,must_be/2) Reported by @notoria in #527. Many thanks! --- src/prolog/lib/crypto.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 12c5b73b..909127d4 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -597,12 +597,14 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :- '$crypto_data_decrypt'(CipherText, Key, IV, Encoding, PlainText). encoding_bytes(octet, Bs0, Bs) :- + must_be(list, Bs0), ( maplist(integer, Bs0) -> Bs0 = Bs ; maplist(char_code, Bs0, Bs) ), must_be_bytes(Bs, crypto_encoding). encoding_bytes(utf8, Cs, Bs) :- + must_be(list, Cs), ( maplist(atom, Cs) -> chars_bytes_(Cs, Bs, crypto_encoding) ; domain_error(encryption_encoding, Cs, crypto) From c14259060ccac0d6e582ad703dbaddfda6a1c575 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Mon, 18 May 2020 11:14:27 -0600 Subject: [PATCH 09/40] fix partial_string_tail panic (#530) --- src/prolog/machine/compile.rs | 4 +++- src/prolog/machine/system_calls.rs | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/prolog/machine/compile.rs b/src/prolog/machine/compile.rs index c4842978..ac43d01c 100644 --- a/src/prolog/machine/compile.rs +++ b/src/prolog/machine/compile.rs @@ -348,7 +348,9 @@ fn compile_into_module( ); match compile_into_module_impl(wam, &mut compiler, module, src, indices) { - Ok(()) => EvalSession::EntrySuccess, + Ok(()) => { + EvalSession::EntrySuccess + } Err(e) => { compiler.drop_expansions(&mut wam.code_repo); EvalSession::from(e) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4061bdbc..8281259e 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -1154,10 +1154,19 @@ impl MachineState { match pstr { Addr::PStrLocation(h, _) => { - let tail = self.heap[h + 1].as_addr(h + 1); - let target = self[temp_v!(2)]; + if let HeapCellValue::PartialString(_, true) = &self.heap[h] { + let tail = self.heap[h + 1].as_addr(h + 1); + let target = self[temp_v!(2)]; - self.unify(tail, target); + self.unify(tail, target); + } else { + self.fail = true; + return Ok(()); + } + } + Addr::EmptyList => { + self.fail = true; + return Ok(()); } _ => { unreachable!() From 041dc039d4a32e0e7fa6c3154f3458a7bfb8aa8a Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Mon, 18 May 2020 12:27:26 -0600 Subject: [PATCH 10/40] add Addr::Lis as case in PartialStringTail (#530) --- src/prolog/iterators.rs | 4 ++++ src/prolog/machine/system_calls.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/prolog/iterators.rs b/src/prolog/iterators.rs index 28da88e4..55087f54 100644 --- a/src/prolog/iterators.rs +++ b/src/prolog/iterators.rs @@ -92,6 +92,10 @@ fn is_partial_string<'a>( Term::Constant(_, Constant::EmptyList) => { return Some((string, None)); } + Term::Constant(_, Constant::String(tail)) => { + string += &tail; + return Some((string, None)); + } _ => { return None; } diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 9554badb..47b36012 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -1164,6 +1164,9 @@ impl MachineState { return Ok(()); } } + Addr::Lis(h) => { + self.unify(Addr::HeapCell(h + 1), self[temp_v!(2)]); + } Addr::EmptyList => { self.fail = true; return Ok(()); From e0e3b180e783f6e1e81d6867b59852216a0d3e51 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Mon, 18 May 2020 23:45:21 -0600 Subject: [PATCH 11/40] accomodate \0\ in partial strings, print null as \0\ (#267, #526), update prolog parser, version bump --- Cargo.lock | 6 ++-- Cargo.toml | 4 +-- src/prolog/heap_print.rs | 2 +- src/prolog/machine/heap.rs | 19 +++-------- src/prolog/machine/machine_state_impl.rs | 10 ++++-- src/prolog/machine/partial_string.rs | 40 +++--------------------- src/prolog/machine/system_calls.rs | 5 +++ 7 files changed, 28 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be47e095..5af43295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "prolog_parser" -version = "0.8.58" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90d34a4268bf5256d4e55f8ec920220aa96dc7ee779441b32f97635cba72aa9" +checksum = "029a4682cf40923b8eb05c4b943d1d6be73775e7bf80bcb0866b5ef29abb0802" dependencies = [ "lexical", "num-rug-adapter", @@ -740,7 +740,7 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scryer-prolog" -version = "0.8.122" +version = "0.8.123" dependencies = [ "cpu-time", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index e8f52cd7..3f48283c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scryer-prolog" -version = "0.8.122" +version = "0.8.123" authors = ["Mark Thom "] build = "build.rs" repository = "https://github.com/mthom/scryer-prolog" @@ -29,7 +29,7 @@ libc = "0.2.62" nix = "0.15.0" num-rug-adapter = { optional = true, version = "0.1.3" } ordered-float = "0.5.0" -prolog_parser = { version = "0.8.58", default-features = false } +prolog_parser = { version = "0.8.59", default-features = false } ref_thread_local = "0.0.0" rug = { version = "1.4.0", optional = true } rustyline = "6.0.0" diff --git a/src/prolog/heap_print.rs b/src/prolog/heap_print.rs index ca462e02..ee92ff0c 100644 --- a/src/prolog/heap_print.rs +++ b/src/prolog/heap_print.rs @@ -160,7 +160,7 @@ fn char_to_string(is_quoted: bool, c: char) -> String { '\u{d8}' ..= '\u{f6}' => c.to_string(), '\u{f8}' ..= '\u{74f}' => c.to_string(), '\x20' ..= '\x7e' => c.to_string(), - _ => format!("\\x{:x}\\", c as u32), + _ => format!("\\{:x}\\", c as u32), } } diff --git a/src/prolog/machine/heap.rs b/src/prolog/machine/heap.rs index 68d4a2f9..91639423 100644 --- a/src/prolog/machine/heap.rs +++ b/src/prolog/machine/heap.rs @@ -189,6 +189,10 @@ impl HeapTemplate { #[inline] pub(crate) fn put_complete_string(&mut self, s: &str) -> Addr { + if s.is_empty() { + return Addr::EmptyList; + } + let addr = self.allocate_pstr(s); self.pop(); @@ -316,20 +320,7 @@ impl HeapTemplate { pub(crate) fn allocate_pstr(&mut self, src: &str) -> Addr { self.write_pstr(src) - .unwrap_or_else(|| { - let h = self.h(); - - self.push(HeapCellValue::PartialString( - PartialString::empty(), - true, - )); - - self.push(HeapCellValue::Addr( - Addr::HeapCell(h + 1) - )); - - Addr::PStrLocation(h, 0) - }) + .unwrap_or_else(|| Addr::EmptyList) } #[inline] diff --git a/src/prolog/machine/machine_state_impl.rs b/src/prolog/machine/machine_state_impl.rs index ec06f9fc..a26c4cde 100644 --- a/src/prolog/machine/machine_state_impl.rs +++ b/src/prolog/machine/machine_state_impl.rs @@ -1492,9 +1492,13 @@ impl MachineState { &QueryInstruction::PutPartialString(_, ref string, reg, has_tail) => { let pstr_addr = if has_tail { - let pstr_addr = self.heap.allocate_pstr(&string); - self.heap.pop(); // the tail will be added by the next instruction. - pstr_addr + if !string.is_empty() { + let pstr_addr = self.heap.allocate_pstr(&string); + self.heap.pop(); // the tail will be added by the next instruction. + pstr_addr + } else { + Addr::EmptyList + } } else { self.heap.put_complete_string(&string) }; diff --git a/src/prolog/machine/partial_string.rs b/src/prolog/machine/partial_string.rs index 2d821ac4..1413b37c 100644 --- a/src/prolog/machine/partial_string.rs +++ b/src/prolog/machine/partial_string.rs @@ -37,8 +37,8 @@ fn scan_for_terminator>(iter: Iter) -> usize { let mut terminator_idx = 0; for c in iter { - if c == '\u{0}' { - break; + if c == '\u{0}' && terminator_idx != 0 { + return terminator_idx; } terminator_idx += c.len_utf8(); @@ -98,37 +98,9 @@ impl PartialString { } } - #[inline] - pub(super) - fn empty() -> Self { - let mut pstr = PartialString { - buf: ptr::null(), - len: 0, - _marker: PhantomData, - }; - - unsafe { - let layout = alloc::Layout::from_size_align_unchecked( - '\u{0}'.len_utf8(), - mem::align_of::(), - ); - - pstr.buf = alloc::alloc(layout) as *const _; - pstr.len = '\u{0}'.len_utf8(); - - pstr.write_terminator_at(0); - } - - pstr - } - unsafe fn append_chars(mut self, src: &str) -> Option<(Self, &str)> { let terminator_idx = scan_for_terminator(src.chars()); - if terminator_idx == 0 { - return None; - } - let layout = alloc::Layout::from_size_align_unchecked( terminator_idx + '\u{0}'.len_utf8(), mem::align_of::(), @@ -145,8 +117,8 @@ impl PartialString { self.write_terminator_at(terminator_idx); - Some(if terminator_idx != src.len() { - (self, &src[terminator_idx + '\u{0}'.len_utf8() ..]) + Some(if terminator_idx != src.as_bytes().len() { + (self, &src[terminator_idx ..]) } else { (self, "") }) @@ -211,9 +183,7 @@ impl PartialString { #[inline] pub fn at_end(&self, end_n: usize) -> bool { - unsafe { - ptr::read((self.buf as usize + end_n) as *const u8) == 0u8 - } + end_n + 1 == self.len } #[inline] diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 47b36012..6160e7dd 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -1110,6 +1110,11 @@ impl MachineState { let h = self.heap.h(); + if atom.as_str().is_empty() { + self.fail = true; + return Ok(()); + } + let pstr = self.heap.allocate_pstr(atom.as_str()); let pstr_tail = self.heap[h + 1].as_addr(h + 1); From f5c2f6f9e9d98870eaf8b6c0dc0ae06e8fed8376 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 10:50:33 +0200 Subject: [PATCH 12/40] ADDED: Support for SHA-3 algorithms in crypto_data_hash/3 --- Cargo.toml | 1 + src/prolog/lib/crypto.pl | 19 +++++++++++-------- src/prolog/machine/system_calls.rs | 21 +++++++++++++++++++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f48283c..6e5c657e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ rustyline = "6.0.0" unicode_reader = "1.0.0" ring = "0.16.13" ripemd160 = "0.8.0" +sha3 = "0.8.2" diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 909127d4..9881afba 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -159,10 +159,10 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). - algorithm(+A) where A is one of ripemd160, sha256, sha384, sha512, - sha512_256, or a variable. If A is a variable, then it is - unified with the default algorithm, which is an algorithm that - is considered cryptographically secure at the time of this - writing. + sha512_256, sha3_224, sha3_256, sha3_384, sha3_512, or a + variable. If A is a variable, then it is unified with the + default algorithm, which is an algorithm that is considered + cryptographically secure at the time of this writing. - encoding(+Encoding) The default encoding is utf8. The alternative is octet, to treat the input as a list of raw bytes. @@ -215,6 +215,10 @@ hash_algorithm(sha256). hash_algorithm(sha512). hash_algorithm(sha384). hash_algorithm(sha512_256). +hash_algorithm(sha3_224). +hash_algorithm(sha3_256). +hash_algorithm(sha3_384). +hash_algorithm(sha3_512). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -230,10 +234,9 @@ hash_algorithm(sha512_256). Admissible options are: - algorithm(+Algorithm) - A hashing algorithm as specified to crypto_data_hash/3. The - default is a cryptographically secure algorithm. If you - specify a variable, then it is unified with the algorithm - that was used, which is a cryptographically secure algorithm. + One of sha256, sha384 or sha512. If you specify a variable, + then it is unified with the algorithm that was used, which is a + cryptographically secure algorithm by default. - info(+Info) Optional context and application specific information, specified as a list of bytes or characters. The default is []. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 6160e7dd..9643aac5 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -42,6 +42,7 @@ use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; use ring::{digest,hkdf,pbkdf2,aead,error}; use ripemd160::{Ripemd160, Digest}; +use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; pub fn get_key() -> KeyEvent { let key; @@ -5219,7 +5220,23 @@ impl MachineState { }; let ints_list = - if algorithm_str == "ripemd160" { + if algorithm_str == "sha3_224" { + let mut context = Sha3_224::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "sha3_256" { + let mut context = Sha3_256::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "sha3_384" { + let mut context = Sha3_384::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "sha3_512" { + let mut context = Sha3_512::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "ripemd160" { let mut context = Ripemd160::new(); context.input(&bytes); Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) @@ -5278,7 +5295,7 @@ impl MachineState { "sha256" => { hkdf::HKDF_SHA256 } "sha384" => { hkdf::HKDF_SHA384 } "sha512" => { hkdf::HKDF_SHA512 } - _ => { unreachable!() } + _ => { self.fail = true; return Ok(()); } }; let salt = hkdf::Salt::new(digest_alg, &salt); let mut bytes : Vec = Vec::new(); From 9f5322d309a395fd38922e736b47335aa2f8b72b Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 12:22:49 +0200 Subject: [PATCH 13/40] ADDED: Support for BLAKE2 algorithms in crypto_data_hash/3. --- Cargo.toml | 1 + src/prolog/lib/crypto.pl | 8 +++++--- src/prolog/machine/system_calls.rs | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e5c657e..b869277c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ unicode_reader = "1.0.0" ring = "0.16.13" ripemd160 = "0.8.0" sha3 = "0.8.2" +blake2 = "0.8.1" diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 9881afba..0df0dbd7 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -158,9 +158,9 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). Options is a list of: - algorithm(+A) - where A is one of ripemd160, sha256, sha384, sha512, - sha512_256, sha3_224, sha3_256, sha3_384, sha3_512, or a - variable. If A is a variable, then it is unified with the + where A is one of ripemd160, sha256, sha384, sha512, sha512_256, + sha3_224, sha3_256, sha3_384, sha3_512, blake2s256, blake2b512, + or a variable. If A is a variable, then it is unified with the default algorithm, which is an algorithm that is considered cryptographically secure at the time of this writing. - encoding(+Encoding) @@ -219,6 +219,8 @@ hash_algorithm(sha3_224). hash_algorithm(sha3_256). hash_algorithm(sha3_384). hash_algorithm(sha3_512). +hash_algorithm(blake2s256). +hash_algorithm(blake2b512). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 9643aac5..b3df3c78 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -43,6 +43,7 @@ use ring::rand::{SecureRandom, SystemRandom}; use ring::{digest,hkdf,pbkdf2,aead,error}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; +use blake2::{Blake2s, Blake2b}; pub fn get_key() -> KeyEvent { let key; @@ -5236,6 +5237,14 @@ impl MachineState { let mut context = Sha3_512::new(); context.input(&bytes); Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "blake2s256" { + let mut context = Blake2s::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } else if algorithm_str == "blake2b512" { + let mut context = Blake2b::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } else if algorithm_str == "ripemd160" { let mut context = Ripemd160::new(); context.input(&bytes); From 2eac902c33832c8888ffd52f36d3fccf04cc6c49 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Tue, 19 May 2020 10:45:54 -0600 Subject: [PATCH 14/40] correct fixnum overflow on negation (#528) --- src/prolog/arithmetic.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/prolog/arithmetic.rs b/src/prolog/arithmetic.rs index 87df3dbd..e889b019 100644 --- a/src/prolog/arithmetic.rs +++ b/src/prolog/arithmetic.rs @@ -474,7 +474,12 @@ impl Neg for Number { fn neg(self) -> Self::Output { match self { - Number::Fixnum(n) => Number::Fixnum(-n), + Number::Fixnum(n) => + if let Some(n) = n.checked_neg() { + Number::Fixnum(n) + } else { + Number::from(-Integer::from(n)) + } Number::Integer(n) => Number::Integer(Rc::new(-Integer::from(&*n))), Number::Float(OrderedFloat(f)) => Number::Float(OrderedFloat(-f)), Number::Rational(r) => Number::Rational(Rc::new(-Rational::from(&*r))), From dd32e690618862e87d529f1a6876d5299bba4992 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 16:36:06 +0200 Subject: [PATCH 15/40] use matching to select the hashing algorithm Suggested by @notoria in #533. Many thanks! --- src/prolog/machine/system_calls.rs | 72 ++++++++++++++---------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index b3df3c78..847d4e6d 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5221,45 +5221,39 @@ impl MachineState { }; let ints_list = - if algorithm_str == "sha3_224" { - let mut context = Sha3_224::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "sha3_256" { - let mut context = Sha3_256::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "sha3_384" { - let mut context = Sha3_384::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "sha3_512" { - let mut context = Sha3_512::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "blake2s256" { - let mut context = Blake2s::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "blake2b512" { - let mut context = Blake2b::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else if algorithm_str == "ripemd160" { - let mut context = Ripemd160::new(); - context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) - } else { - let ints = digest::digest( - match algorithm_str { - "sha256" => { &digest::SHA256 } - "sha384" => { &digest::SHA384 } - "sha512" => { &digest::SHA512 } - "sha512_256" => { &digest::SHA512_256 } - _ => { unreachable!() } - }, - &bytes); - Addr::HeapCell(self.heap.to_list(ints.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + match algorithm_str { + "sha3_224" => { let mut context = Sha3_224::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "sha3_256" => { let mut context = Sha3_256::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "sha3_384" => { let mut context = Sha3_384::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "sha3_512" => { let mut context = Sha3_512::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "blake2s256" => { let mut context = Blake2s::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "blake2b512" => { let mut context = Blake2b::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + "ripemd160" => { let mut context = Ripemd160::new(); + context.input(&bytes); + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + _ => { let ints = digest::digest( + match algorithm_str { + "sha256" => { &digest::SHA256 } + "sha384" => { &digest::SHA384 } + "sha512" => { &digest::SHA512 } + "sha512_256" => { &digest::SHA512_256 } + _ => { unreachable!() } + }, + &bytes); + Addr::HeapCell(self.heap.to_list(ints.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + } }; self.unify(self[temp_v!(2)], ints_list); From 85155439be7378105af65481c7e49fa1461cb1fb Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 16:43:44 +0200 Subject: [PATCH 16/40] use Fixnums for bytes in hashing. Suggested by @notoria in #533. Many thanks! --- src/prolog/machine/system_calls.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 847d4e6d..fe90950d 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5224,25 +5224,25 @@ impl MachineState { match algorithm_str { "sha3_224" => { let mut context = Sha3_224::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "sha3_256" => { let mut context = Sha3_256::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "sha3_384" => { let mut context = Sha3_384::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "sha3_512" => { let mut context = Sha3_512::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "blake2s256" => { let mut context = Blake2s::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "blake2b512" => { let mut context = Blake2b::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } "ripemd160" => { let mut context = Ripemd160::new(); context.input(&bytes); - Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) } + Addr::HeapCell(self.heap.to_list(context.result().as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } _ => { let ints = digest::digest( match algorithm_str { "sha256" => { &digest::SHA256 } From da4d061067d9f51e3a48c488bb5772d9fe926e24 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 16:56:34 +0200 Subject: [PATCH 17/40] correct option processing in crypto_data_decrypt/6 --- src/prolog/lib/crypto.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 0df0dbd7..bf613603 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -592,7 +592,9 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :- must_be_bytes(Key, crypto_data_decrypt/6), must_be_bytes(IV, crypto_data_decrypt/6), must_be(atom, Algorithm), - encoding_options(Encoding, Options), + option(encoding(Encoding), Options, utf8), + must_be(atom, Encoding), + member(Encoding, [utf8,octet]), must_be(list, CipherText0), encoding_bytes(octet, CipherText0, CipherText1), append(CipherText1, Tag, CipherText), From 34f7752c0f864218e0530e85563a3a3bfe80dc76 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 17:02:15 +0200 Subject: [PATCH 18/40] use more fixnums in cryptographic routines --- src/prolog/machine/system_calls.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index fe90950d..1fd341c0 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5252,7 +5252,7 @@ impl MachineState { _ => { unreachable!() } }, &bytes); - Addr::HeapCell(self.heap.to_list(ints.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + Addr::HeapCell(self.heap.to_list(ints.as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) } }; @@ -5308,7 +5308,7 @@ impl MachineState { _ => { self.fail = true; return Ok(()); } } - Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) }; self.unify(self[temp_v!(6)], ints_list); @@ -5341,7 +5341,7 @@ impl MachineState { NonZeroU32::new(iterations as u32).unwrap(), &salt, &data, &mut bytes); - Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))) + Addr::HeapCell(self.heap.to_list(bytes.iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))) }; self.unify(self[temp_v!(4)], ints_list); @@ -5366,7 +5366,7 @@ impl MachineState { }; let tag_list = - Addr::HeapCell(self.heap.to_list(tag.as_ref().iter().map(|b| HeapCellValue::Integer(Rc::new(Integer::from(*b)))))); + Addr::HeapCell(self.heap.to_list(tag.as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))); let complete_string = { let buffer = String::from_iter(in_out.iter().map(|b| *b as char)); From d19d8ea77067319fbc1cbfb4238549107ef9585e Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 17:31:36 +0200 Subject: [PATCH 19/40] crypto_data_hkdf/4: do not crash for length > usize::max_value() For now, we fail silently in such cases. Noted by @notoria in #533. Many thanks! --- src/prolog/machine/system_calls.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 1fd341c0..ad3973ac 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5285,11 +5285,12 @@ impl MachineState { usize::try_from(n).unwrap() } Ok(Number::Integer(n)) => { - n.to_usize().unwrap() - } - _ => { - unreachable!() + match n.to_usize() { + Some(u) => { u } + _ => { self.fail = true; return Ok(()); } + } } + _ => { unreachable!() } }; let ints_list = From dec2ef70c74faf05262edc27f78f511a1fb43644 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 18:21:07 +0200 Subject: [PATCH 20/40] better error handling in crypto_data_hkdf/4 Noted by @notoria in #533. Many thanks! --- src/prolog/lib/crypto.pl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index bf613603..e07b4b1d 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -258,6 +258,9 @@ hash_algorithm(blake2b512). crypto_data_hkdf(Data0, L, Bytes, Options0) :- functor_hash_options(algorithm, Algorithm, Options0, Options), + ( hkdf_algorithm(Algorithm) -> true + ; domain_error(hkdf_algorithm, Algorithm, crypto_data_hkdf/4) + ), must_be(integer, L), L >= 0, options_data_bytes(Options, Data0, Data), @@ -267,6 +270,10 @@ crypto_data_hkdf(Data0, L, Bytes, Options0) :- chars_bytes_(Info0, Info, crypto_data_hkdf/4), '$crypto_data_hkdf'(Data, SaltBytes, Info, Algorithm, L, Bytes). +hkdf_algorithm(sha256). +hkdf_algorithm(sha384). +hkdf_algorithm(sha512). + option(What, Options, Default) :- ( member(V, Options), var(V) -> instantiation_error(option/3) From 0bd9831eecf92bb5429301918f37273c5eb86488 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 18:44:28 +0200 Subject: [PATCH 21/40] ADDED: phrase_from_file/3 in library(pio) This allows us to specify type(binary), and read from binary files with DCGs. --- src/prolog/clause_types.rs | 2 +- src/prolog/lib/pio.pl | 19 +++++++--- src/prolog/machine/system_calls.rs | 58 ++++++++++++++++++++---------- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index cf4e1aa6..78ca09de 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -533,7 +533,7 @@ impl SystemClauseType { ("$expand_goal", 2) => Some(SystemClauseType::ExpandGoal), ("$fetch_global_var", 2) => Some(SystemClauseType::FetchGlobalVar), ("$fetch_global_var_with_offset", 3) => Some(SystemClauseType::FetchGlobalVarWithOffset), - ("$file_to_chars", 2) => Some(SystemClauseType::FileToChars), + ("$file_to_chars", 3) => Some(SystemClauseType::FileToChars), ("$get_byte", 2) => Some(SystemClauseType::GetByte), ("$get_char", 2) => Some(SystemClauseType::GetChar), ("$get_code", 2) => Some(SystemClauseType::GetCode), diff --git a/src/prolog/lib/pio.pl b/src/prolog/lib/pio.pl index 4f387625..8467dacb 100644 --- a/src/prolog/lib/pio.pl +++ b/src/prolog/lib/pio.pl @@ -1,12 +1,23 @@ -:- module(pio, [phrase_from_file/2]). +:- module(pio, [phrase_from_file/2, + phrase_from_file/3]). :- use_module(library(dcgs)). :- use_module(library(error)). phrase_from_file(NT, File) :- - ( var(File) -> instantiation_error(phrase_from_file/2) + phrase_from_file(NT, File, []). + +phrase_from_file(NT, File, Options) :- + ( var(File) -> instantiation_error(phrase_from_file/3) ; (\+ atom(File) ; File = []) -> - domain_error(source_sink, File, phrase_from_file/2) - ; '$file_to_chars'(File, Chars), + domain_error(source_sink, File, phrase_from_file/3) + ; must_be(list, Options), + ( member(Var, Options), var(Var) -> instantiation_error(phrase_from_file/3) + ; member(type(Type), Options) -> + must_be(atom, Type), + member(Type, [text,binary]) + ; Type = text + ), + '$file_to_chars'(File, Chars, Type), phrase(NT, Chars) ). diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 6160e7dd..927fa028 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -1905,29 +1905,51 @@ impl MachineState { } }; - let complete_string = { - let mut buffer = String::new(); - match file.read_to_string(&mut buffer) { - Ok(_size) => { - self.heap.put_complete_string(&buffer) - } - Err(_e) => { - // This case if the data isn't UTF-8 valid. - let mut buffer = Vec::new(); - let _ = match file.read_to_end(&mut buffer) { - Ok(size) => size, - Err(_e) => unreachable!() - }; - let buffer = String::from_iter( - buffer.into_iter().map(|b| b as char) - ); - - self.heap.put_complete_string(&buffer) + let type_str = match self.store(self.deref(self[temp_v!(3)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { + atom.as_str() + } else { + unreachable!() } } + _ => { + unreachable!() + } }; + let complete_string = { + let mut buffer = String::new(); + match type_str { + "text" => { match file.read_to_string(&mut buffer) { + Ok(_size) => { + self.heap.put_complete_string(&buffer) + } + Err(_e) => { + // the data isn't valid UTF-8, so we fail. + self.fail = true; + return Ok(()); + + } + } + } + "binary" => { let mut buffer = Vec::new(); + let _ = match file.read_to_end(&mut buffer) { + Ok(size) => size, + Err(_e) => unreachable!() + }; + + let buffer = String::from_iter( + buffer.into_iter().map(|b| b as char) + ); + + self.heap.put_complete_string(&buffer) + } + _ => { unreachable!() } + } + }; + self.unify(complete_string, a2); } &SystemClauseType::PutCode => { From e00d864199549c179a4403fba910b234646cdba5 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Tue, 19 May 2020 11:59:06 -0600 Subject: [PATCH 22/40] Revert "Created and Tested Dockerfile" --- .dockerignore | 6 ------ Dockerfile | 30 ------------------------------ README.md | 22 ---------------------- 3 files changed, 58 deletions(-) delete mode 100755 .dockerignore delete mode 100755 Dockerfile diff --git a/.dockerignore b/.dockerignore deleted file mode 100755 index 050103c6..00000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -target -Dockerfile -README.md -.git -.gitignore -.gitmodules diff --git a/Dockerfile b/Dockerfile deleted file mode 100755 index 3b2c3a05..00000000 --- a/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# Based on https://hub.docker.com/_/rust?tab=description and https://hub.docker.com/_/rust?tab=description - -# The first container is for build purposes only. -FROM rust as builder - -WORKDIR /usr/src/scryer-prolog - -# Using a dummy build.rs and src/main.rs with your Cargo.toml lets Docker cache your Rust dependencies and not rebuild -# them every time. -COPY Cargo.toml . -COPY Cargo.lock . -RUN mkdir -p src -RUN echo "fn main() {}" > src/main.rs -RUN echo "fn main() {}" > build.rs -RUN cargo build --release - -# We need to touch our real main.rs and build.rs files or else -# docker will use the cached ones. -COPY . . -RUN touch src/main.rs -RUN touch build.rs - -RUN cargo build --release - -RUN ls ./target/release - -# Finally, copy the scryer-prolog executable to a slimmer container. -FROM debian:buster-slim -COPY --from=builder /usr/src/scryer-prolog/target/release/scryer-prolog /usr/local/bin/scryer-prolog -CMD ["scryer-prolog"] diff --git a/README.md b/README.md index fbb8bb9a..ed9161f5 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,6 @@ strings. ## Installing Scryer Prolog -### Native Install (Unix Only) - First, install the latest stable version of [Rust](https://www.rust-lang.org/en-US/install.html) using your preferred method. Scryer tends to use features from newer Rust @@ -128,26 +126,6 @@ $> cargo run [--release] The optional `--release` flag will perform various optimizations, producing a faster executable. -### Docker Install (All Platforms) - -To automatically download, install, and run Scryer Prolog via Docker, -simply run: -``` -$> docker run -it mthom/scryer-prolog -``` - -To be able to load your program files, bind mount your programs folder -as a Docker volume: - -``` -$> docker run -v /home/user/prolog:/mnt -it mthom/scryer-prolog -?- consult('mnt/program.pl'). -true. -``` - -[Docker](https://hub.docker.com/editions/community/docker-ce-desktop-windows) -is currently the only way to run scryer-prolog on Windows. - ## Tutorial Prolog files are loaded by specifying them as arguments on the command From 21b7348c6a69f814729f6266bb468098464031d2 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 19 May 2020 20:10:45 +0200 Subject: [PATCH 23/40] import member/2 from library(lists) --- src/prolog/lib/pio.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prolog/lib/pio.pl b/src/prolog/lib/pio.pl index 8467dacb..4c0f9046 100644 --- a/src/prolog/lib/pio.pl +++ b/src/prolog/lib/pio.pl @@ -3,6 +3,7 @@ :- use_module(library(dcgs)). :- use_module(library(error)). +:- use_module(library(lists), [member/2]). phrase_from_file(NT, File) :- phrase_from_file(NT, File, []). From a0544cc3458b0826e0e4ac8b2d46a4e74e7aca00 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Tue, 19 May 2020 17:34:55 -0600 Subject: [PATCH 24/40] revert printing of characters --- Cargo.lock | 49 ++++++++++++++++++++++++++++++++++++++++ src/prolog/heap_print.rs | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5af43295..0f0b8616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac", + "digest", + "opaque-debug", +] + [[package]] name = "blake2b_simd" version = "0.5.10" @@ -174,6 +186,16 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "digest" version = "0.8.1" @@ -323,6 +345,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -742,6 +770,7 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" name = "scryer-prolog" version = "0.8.123" dependencies = [ + "blake2", "cpu-time", "crossterm", "dirs", @@ -761,6 +790,7 @@ dependencies = [ "ripemd160", "rug", "rustyline", + "sha3", "unicode_reader", ] @@ -779,6 +809,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +dependencies = [ + "block-buffer", + "byte-tools", + "digest", + "keccak", + "opaque-debug", +] + [[package]] name = "signal-hook" version = "0.1.13" @@ -833,6 +876,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "syn" version = "1.0.18" diff --git a/src/prolog/heap_print.rs b/src/prolog/heap_print.rs index ee92ff0c..ca462e02 100644 --- a/src/prolog/heap_print.rs +++ b/src/prolog/heap_print.rs @@ -160,7 +160,7 @@ fn char_to_string(is_quoted: bool, c: char) -> String { '\u{d8}' ..= '\u{f6}' => c.to_string(), '\u{f8}' ..= '\u{74f}' => c.to_string(), '\x20' ..= '\x7e' => c.to_string(), - _ => format!("\\{:x}\\", c as u32), + _ => format!("\\x{:x}\\", c as u32), } } From ad3be5c848dec53814d13a646c636bcf50976460 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 18:30:34 +0200 Subject: [PATCH 25/40] ADDED: Public key signatures and signature verification with Ed25519 --- README.md | 1 + src/prolog/clause_types.rs | 8 +++++- src/prolog/lib/crypto.pl | 42 +++++++++++++++++++++++++++++- src/prolog/machine/system_calls.rs | 34 +++++++++++++++++++++++- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ed9161f5..9ad29239 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,7 @@ The modules that ship with Scryer Prolog are also called * [`crypto`](src/prolog/lib/crypto.pl) Cryptographically secure random numbers and hashes, HMAC-based key derivation (HKDF), password-based key derivation (PBKDF2), + public key signatures and signature verification with Ed25519, authenticated encryption, and reasoning about elliptic curves. To read contents of external files, use `phrase_from_file/2` from diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 78ca09de..067e4437 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -291,7 +291,9 @@ pub enum SystemClauseType { CryptoDataHKDF, CryptoPasswordHash, CryptoDataEncrypt, - CryptoDataDecrypt + CryptoDataDecrypt, + Ed25519Sign, + Ed25519Verify } impl SystemClauseType { @@ -480,6 +482,8 @@ impl SystemClauseType { &SystemClauseType::CryptoPasswordHash => clause_name!("$crypto_password_hash"), &SystemClauseType::CryptoDataEncrypt => clause_name!("$crypto_data_encrypt"), &SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"), + &SystemClauseType::Ed25519Sign => clause_name!("$ed25519_sign"), + &SystemClauseType::Ed25519Verify => clause_name!("$ed25519_verify"), } } @@ -648,6 +652,8 @@ impl SystemClauseType { ("$crypto_password_hash", 4) => Some(SystemClauseType::CryptoPasswordHash), ("$crypto_data_encrypt", 5) => Some(SystemClauseType::CryptoDataEncrypt), ("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt), + ("$ed25519_sign", 3) => Some(SystemClauseType::Ed25519Sign), + ("$ed25519_verify", 3) => Some(SystemClauseType::Ed25519Verify), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index e07b4b1d..1562b4c1 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -9,7 +9,7 @@ and strings have the advantage that the atom table remains unmodified. Especially for cryptographic applications, it as an advantage that - using strings leaves little trace of what was processed in the system, + using strings leaves little trace of what was processed in the system. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(crypto, @@ -21,6 +21,8 @@ crypto_password_hash/3, % +Password, -Hash, +Options crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options + ed25519_sign/4, % +PrivateKey, +Data, -Signature, +Options + ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options crypto_name_curve/2, % +Name, -Curve crypto_curve_order/2, % +Curve, -Order crypto_curve_generator/2, % +Curve, -Generator @@ -624,6 +626,44 @@ encoding_bytes(utf8, Cs, Bs) :- ; domain_error(encryption_encoding, Cs, crypto) ). +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Digital signatures with Ed25519 + =============================== + + ed25519_sign(+Key, +Data, -Signature, +Options) + + Key and Data must be lists of characters. Key is a private key in + PKCS#8 (v1 or v2) DER format. Sign Data with Key, yielding + Signature as a list of hexadecimal characters. + + + ed25519_verify(+Key, +Data, +Signature, +Options) + + Key and Data must be lists of characters. Key is a public key in + PKCS#8 DER format. Succeeds if Data was signed with the private key + corresponding to Key, where Signature is a list of hexadecimal + characters as generated by ed25519_sign/4. Fails otherwise. + + + Currently, the only option for both predicates is: + + - encoding(+Encoding) + The default encoding of Data is utf8. The alternative is octet, + which treats Data as a list of raw bytes. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +ed25519_sign(Key0, Data0, Signature, Options) :- + options_data_bytes(Options, Data0, Data), + encoding_bytes(octet, Key0, Key), + '$ed25519_sign'(Key, Data, Signature0), + hex_bytes(Signature, Signature0). + +ed25519_verify(Key0, Data0, Signature0, Options) :- + options_data_bytes(Options, Data0, Data), + encoding_bytes(octet, Key0, Key), + hex_bytes(Signature0, Signature), + '$ed25519_verify'(Key, Data, Signature). + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Modular multiplicative inverse. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4eacea2a..4ebbcf29 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -40,7 +40,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; -use ring::{digest,hkdf,pbkdf2,aead,error}; +use ring::{digest,hkdf,pbkdf2,aead,error,signature}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use blake2::{Blake2s, Blake2b}; @@ -5448,6 +5448,38 @@ impl MachineState { self.unify(self[temp_v!(5)], complete_string); } + &SystemClauseType::Ed25519Sign => { + let stub1 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); + let key = self.integers_to_bytevec(temp_v!(1), stub1); + let stub2 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); + let data = self.integers_to_bytevec(temp_v!(2), stub2); + + let key_pair = match signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(&key) { + Ok(kp) => { kp } + _ => { self.fail = true; return Ok(()); } + }; + + let sig = key_pair.sign(&data); + + let sig_list = + Addr::HeapCell(self.heap.to_list(sig.as_ref().iter().map(|b| HeapCellValue::from(Addr::Fixnum(*b as isize))))); + + self.unify(self[temp_v!(3)], sig_list); + } + &SystemClauseType::Ed25519Verify => { + let stub1 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4); + let key = self.integers_to_bytevec(temp_v!(1), stub1); + let stub2 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4); + let data = self.integers_to_bytevec(temp_v!(2), stub2); + let stub3 = MachineError::functor_stub(clause_name!("ed25519_verify"), 4); + let signature = self.integers_to_bytevec(temp_v!(3), stub3); + + let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key); + match peer_public_key.verify(&data, &signature) { + Ok(_) => { } + _ => { self.fail = true; return Ok(()); } + } + } }; return_from_clause!(self.last_call, self) From 2d036c6b2511417f3c807fc8f75c39edfed55fae Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 20:10:26 +0200 Subject: [PATCH 26/40] "codes" --> "bytes" Support for lists of bytes may be dropped from library(crypto). Use lists of characters to make your code future-proof. Lists of characters will always be supported due to their compactness, and because Prolog applications should move towards characters. --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 1562b4c1..804c116f 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -478,7 +478,7 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z], Algorithm, key Key, and initialization vector (or nonce) IV, to give CipherText. - PlainText must be a list of codes or characters, Key and IV must be + PlainText must be a list of bytes or characters, Key and IV must be lists of bytes, and CipherText is created as a list of characters. Keys and IVs can be chosen at random (using for example From 56b04f8df8194ac7c9aef06062367e0504f64ee7 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 20:13:07 +0200 Subject: [PATCH 27/40] use LessSafeKey to simplify the implementation of authenticated encryption The nonce is explicitly specified, and the application programmer must (and always had to) ensure that it is unique for a given key. --- src/prolog/machine/system_calls.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4ebbcf29..71a86627 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -40,7 +40,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; -use ring::{digest,hkdf,pbkdf2,aead,error,signature}; +use ring::{digest,hkdf,pbkdf2,aead,signature}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use blake2::{Blake2s, Blake2b}; @@ -5378,12 +5378,12 @@ impl MachineState { let iv = self.integers_to_bytevec(temp_v!(3), stub3); let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); - let nonce_sequence = OneNonceSequence::new(aead::Nonce::try_assume_unique_for_key(&iv).unwrap()); - let mut key: aead::SealingKey = aead::BoundKey::new(unbound_key, nonce_sequence); + let nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap(); + let key = aead::LessSafeKey::new(unbound_key); let mut in_out = data.clone(); let tag = - match key.seal_in_place_separate_tag(aead::Aad::empty(), &mut in_out) { + match key.seal_in_place_separate_tag(nonce, aead::Aad::empty(), &mut in_out) { Ok(d) => { d } _ => { self.fail = true; return Ok(()); } }; @@ -5421,14 +5421,14 @@ impl MachineState { }; let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); - let nonce_sequence = OneNonceSequence::new(aead::Nonce::try_assume_unique_for_key(&iv).unwrap()); - let mut key: aead::OpeningKey = aead::BoundKey::new(unbound_key, nonce_sequence); + let nonce = aead::Nonce::try_assume_unique_for_key(&iv).unwrap(); + let key = aead::LessSafeKey::new(unbound_key); let mut in_out = data.clone(); let complete_string = { let decrypted_data = - match key.open_in_place(aead::Aad::empty(), &mut in_out) { + match key.open_in_place(nonce, aead::Aad::empty(), &mut in_out) { Ok(d) => { d } _ => { self.fail = true; return Ok(()); } }; @@ -5504,17 +5504,3 @@ impl hkdf::KeyType for MyKey { self.0 } } - -struct OneNonceSequence(Option); - -impl OneNonceSequence { - fn new(nonce: aead::Nonce) -> Self { - Self(Some(nonce)) - } -} - -impl aead::NonceSequence for OneNonceSequence { - fn advance(&mut self) -> Result { - self.0.take().ok_or(error::Unspecified) - } -} From 0ef7b5488d434936e969f7713c459b19da242f4e Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:06:09 +0200 Subject: [PATCH 28/40] ADDED: ed25519_new_keypair/1 to dynamically create a new Ed25519 key pair --- src/prolog/clause_types.rs | 5 ++++- src/prolog/lib/crypto.pl | 34 +++++++++++++++++++----------- src/prolog/machine/system_calls.rs | 9 ++++++++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 067e4437..536c9061 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -293,7 +293,8 @@ pub enum SystemClauseType { CryptoDataEncrypt, CryptoDataDecrypt, Ed25519Sign, - Ed25519Verify + Ed25519Verify, + Ed25519NewKeyPair } impl SystemClauseType { @@ -484,6 +485,7 @@ impl SystemClauseType { &SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"), &SystemClauseType::Ed25519Sign => clause_name!("$ed25519_sign"), &SystemClauseType::Ed25519Verify => clause_name!("$ed25519_verify"), + &SystemClauseType::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair") } } @@ -654,6 +656,7 @@ impl SystemClauseType { ("$crypto_data_decrypt", 5) => Some(SystemClauseType::CryptoDataDecrypt), ("$ed25519_sign", 3) => Some(SystemClauseType::Ed25519Sign), ("$ed25519_verify", 3) => Some(SystemClauseType::Ed25519Verify), + ("$ed25519_new_keypair", 1) => Some(SystemClauseType::Ed25519NewKeyPair), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 804c116f..5807292e 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -21,6 +21,7 @@ crypto_password_hash/3, % +Password, -Hash, +Options crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options + ed25519_new_keypair/1, % -KeyPair ed25519_sign/4, % +PrivateKey, +Data, -Signature, +Options ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options crypto_name_curve/2, % +Name, -Curve @@ -630,28 +631,37 @@ encoding_bytes(utf8, Cs, Bs) :- Digital signatures with Ed25519 =============================== - ed25519_sign(+Key, +Data, -Signature, +Options) + - ed25519_new_keypair(-Pair) + Yields a new Ed25519 key pair Pair, a list of characters. The + pair contains the private key and must be kept absolutely secret. + Pair can be used for signing. Its public key can be obtained + with ed25519_keypair_public_key/2. - Key and Data must be lists of characters. Key is a private key in - PKCS#8 (v1 or v2) DER format. Sign Data with Key, yielding - Signature as a list of hexadecimal characters. + - ed25519_keypair_public_key(+Pair, -PublicKey) + PublicKey is the public key of the given key pair. The public key + can be used for signature verification, and can be shared freely. + - ed25519_sign(+Key, +Data, -Signature, +Options) + Key and Data must be lists of characters. Key is a private key or + key pair in PKCS#8 (v1 or v2) DER format. Sign Data with Key, + yielding Signature as a list of hexadecimal characters. - ed25519_verify(+Key, +Data, +Signature, +Options) + - ed25519_verify(+Key, +Data, +Signature, +Options) + Key and Data must be lists of characters. Key is a public key. + Succeeds if Data was signed with the private key corresponding to + Key, where Signature is a list of hexadecimal characters as + generated by ed25519_sign/4. Fails otherwise. - Key and Data must be lists of characters. Key is a public key in - PKCS#8 DER format. Succeeds if Data was signed with the private key - corresponding to Key, where Signature is a list of hexadecimal - characters as generated by ed25519_sign/4. Fails otherwise. - - - Currently, the only option for both predicates is: + Currently, the only option for signing and verifying is: - encoding(+Encoding) The default encoding of Data is utf8. The alternative is octet, which treats Data as a list of raw bytes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +ed25519_new_keypair(Pair) :- + '$ed25519_new_keypair'(Pair). + ed25519_sign(Key0, Data0, Signature, Options) :- options_data_bytes(Options, Data0, Data), encoding_bytes(octet, Key0, Key), diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 71a86627..38f044b0 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5448,6 +5448,15 @@ impl MachineState { self.unify(self[temp_v!(5)], complete_string); } + &SystemClauseType::Ed25519NewKeyPair => { + let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(rng()).unwrap(); + let complete_string = { + let buffer = String::from_iter(pkcs8_bytes.as_ref().iter().map(|b| *b as char)); + self.heap.put_complete_string(&buffer) + }; + + self.unify(self[temp_v!(1)], complete_string); + } &SystemClauseType::Ed25519Sign => { let stub1 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); let key = self.integers_to_bytevec(temp_v!(1), stub1); From e254d84710b5ccf64310145d063f197923c9dced Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:17:42 +0200 Subject: [PATCH 29/40] clarify format of public key --- src/prolog/lib/crypto.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 5807292e..0723fb29 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -640,6 +640,7 @@ encoding_bytes(utf8, Cs, Bs) :- - ed25519_keypair_public_key(+Pair, -PublicKey) PublicKey is the public key of the given key pair. The public key can be used for signature verification, and can be shared freely. + The public key is represented as a list of characters. - ed25519_sign(+Key, +Data, -Signature, +Options) Key and Data must be lists of characters. Key is a private key or From 2845f55157b93f2d45e64a9bcbd23048ce8b6c9e Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:32:46 +0200 Subject: [PATCH 30/40] ADDED: ed25519_keypair_public_key/2, relating a key pair to its public key --- src/prolog/clause_types.rs | 7 ++++-- src/prolog/lib/crypto.pl | 35 +++++++++++++++++------------- src/prolog/machine/system_calls.rs | 18 ++++++++++++++- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index 536c9061..b783ac0c 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -294,7 +294,8 @@ pub enum SystemClauseType { CryptoDataDecrypt, Ed25519Sign, Ed25519Verify, - Ed25519NewKeyPair + Ed25519NewKeyPair, + Ed25519KeyPairPublicKey } impl SystemClauseType { @@ -485,7 +486,8 @@ impl SystemClauseType { &SystemClauseType::CryptoDataDecrypt => clause_name!("$crypto_data_decrypt"), &SystemClauseType::Ed25519Sign => clause_name!("$ed25519_sign"), &SystemClauseType::Ed25519Verify => clause_name!("$ed25519_verify"), - &SystemClauseType::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair") + &SystemClauseType::Ed25519NewKeyPair => clause_name!("$ed25519_new_keypair"), + &SystemClauseType::Ed25519KeyPairPublicKey => clause_name!("$ed25519_keypair_public_key") } } @@ -657,6 +659,7 @@ impl SystemClauseType { ("$ed25519_sign", 3) => Some(SystemClauseType::Ed25519Sign), ("$ed25519_verify", 3) => Some(SystemClauseType::Ed25519Verify), ("$ed25519_new_keypair", 1) => Some(SystemClauseType::Ed25519NewKeyPair), + ("$ed25519_keypair_public_key", 2) => Some(SystemClauseType::Ed25519KeyPairPublicKey), _ => None, } } diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 0723fb29..696bb0e6 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -13,21 +13,22 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(crypto, - [hex_bytes/2, % ?Hex, ?Bytes - crypto_n_random_bytes/2, % +N, -Bytes - crypto_data_hash/3, % +Data, -Hash, +Options - crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options - crypto_password_hash/2, % +Password, ?Hash - crypto_password_hash/3, % +Password, -Hash, +Options - crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options - crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options - ed25519_new_keypair/1, % -KeyPair - ed25519_sign/4, % +PrivateKey, +Data, -Signature, +Options - ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options - crypto_name_curve/2, % +Name, -Curve - crypto_curve_order/2, % +Curve, -Order - crypto_curve_generator/2, % +Curve, -Generator - crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result + [hex_bytes/2, % ?Hex, ?Bytes + crypto_n_random_bytes/2, % +N, -Bytes + crypto_data_hash/3, % +Data, -Hash, +Options + crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options + crypto_password_hash/2, % +Password, ?Hash + crypto_password_hash/3, % +Password, -Hash, +Options + crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options + crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options + ed25519_new_keypair/1, % -KeyPair + ed25519_keypair_public_key/2, % +KeyPair, +PublicKey + ed25519_sign/4, % +PrivateKey, +Data, -Signature, +Options + ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options + crypto_name_curve/2, % +Name, -Curve + crypto_curve_order/2, % +Curve, -Order + crypto_curve_generator/2, % +Curve, -Generator + crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result ]). :- use_module(library(error)). @@ -663,6 +664,10 @@ encoding_bytes(utf8, Cs, Bs) :- ed25519_new_keypair(Pair) :- '$ed25519_new_keypair'(Pair). +ed25519_keypair_public_key(Pair0, PublicKey) :- + encoding_bytes(octet, Pair0, Pair), + '$ed25519_keypair_public_key'(Pair, PublicKey). + ed25519_sign(Key0, Data0, Signature, Options) :- options_data_bytes(Options, Data0, Data), encoding_bytes(octet, Key0, Key), diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 38f044b0..895efad8 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -40,7 +40,7 @@ use crate::crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; use crate::crossterm::terminal::{enable_raw_mode, disable_raw_mode}; use ring::rand::{SecureRandom, SystemRandom}; -use ring::{digest,hkdf,pbkdf2,aead,signature}; +use ring::{digest,hkdf,pbkdf2,aead,signature::{self,KeyPair}}; use ripemd160::{Ripemd160, Digest}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; use blake2::{Blake2s, Blake2b}; @@ -5457,6 +5457,22 @@ impl MachineState { self.unify(self[temp_v!(1)], complete_string); } + &SystemClauseType::Ed25519KeyPairPublicKey => { + let stub1 = MachineError::functor_stub(clause_name!("ed25519_keypair_public_key"), 2); + let bytes = self.integers_to_bytevec(temp_v!(1), stub1); + + let key_pair = match signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(&bytes) { + Ok(kp) => { kp } + _ => { self.fail = true; return Ok(()); } + }; + + let complete_string = { + let buffer = String::from_iter(key_pair.public_key().as_ref().iter().map(|b| *b as char)); + self.heap.put_complete_string(&buffer) + }; + + self.unify(self[temp_v!(2)], complete_string); + } &SystemClauseType::Ed25519Sign => { let stub1 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); let key = self.integers_to_bytevec(temp_v!(1), stub1); From b43f27030eaa33e1a92a163086876003240c7838 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 20 May 2020 23:51:31 +0200 Subject: [PATCH 31/40] require PKCS#8 v2 format for better security Notably, this format requires that the public key also be present. This format is what ed25519_new_keypair/1 generates, and it is strongly encouraged for higher security. --- src/prolog/lib/crypto.pl | 6 +++--- src/prolog/machine/system_calls.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 696bb0e6..9fb5e216 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -644,9 +644,9 @@ encoding_bytes(utf8, Cs, Bs) :- The public key is represented as a list of characters. - ed25519_sign(+Key, +Data, -Signature, +Options) - Key and Data must be lists of characters. Key is a private key or - key pair in PKCS#8 (v1 or v2) DER format. Sign Data with Key, - yielding Signature as a list of hexadecimal characters. + Key and Data must be lists of characters. Key is a key pair in + PKCS#8 v2 format as generated by ed25519_new_keypair/1. Sign Data + with Key, yielding Signature as a list of hexadecimal characters. - ed25519_verify(+Key, +Data, +Signature, +Options) Key and Data must be lists of characters. Key is a public key. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 895efad8..1fe7b576 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -5461,7 +5461,7 @@ impl MachineState { let stub1 = MachineError::functor_stub(clause_name!("ed25519_keypair_public_key"), 2); let bytes = self.integers_to_bytevec(temp_v!(1), stub1); - let key_pair = match signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(&bytes) { + let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&bytes) { Ok(kp) => { kp } _ => { self.fail = true; return Ok(()); } }; @@ -5479,7 +5479,7 @@ impl MachineState { let stub2 = MachineError::functor_stub(clause_name!("ed25519_sign"), 4); let data = self.integers_to_bytevec(temp_v!(2), stub2); - let key_pair = match signature::Ed25519KeyPair::from_pkcs8_maybe_unchecked(&key) { + let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&key) { Ok(kp) => { kp } _ => { self.fail = true; return Ok(()); } }; From 5d94276c4f02b4d6fa0bdc859982107cd1560397 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 00:07:28 +0200 Subject: [PATCH 32/40] remove several mentions of list of bytes in predicate descriptions This is to focus more on the intended core representation of text: In Prolog, text is ideally represented as a list of characters, and this is what we want to encourage, especially given Scryer's compact representation for strings. For the time being, library(crypto) still also supports lists of bytes in several predicates. This will likely be removed at some point in the future. Please use lists of characters to make your code future-proof. --- src/prolog/lib/crypto.pl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 9fb5e216..d4ebf2e5 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -155,9 +155,8 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). crypto_data_hash(+Data, -Hash, +Options) - Where Data is a list of bytes (integers between 0 and 255) or - characters, and Hash is the computed hash as a list of hexadecimal - characters. + Where Data is a list of characters, and Hash is the computed hash + as a list of hexadecimal characters. Options is a list of: @@ -231,7 +230,7 @@ hash_algorithm(blake2b512). crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det. Concentrate possibly dispersed entropy of Data and then expand it - to the desired length. Data is a list of bytes or characters. + to the desired length. Data is a list of characters. Bytes is unified with a list of bytes of length Length, and is suitable as input keying material and initialization vectors to @@ -245,7 +244,7 @@ hash_algorithm(blake2b512). cryptographically secure algorithm by default. - info(+Info) Optional context and application specific information, - specified as a list of bytes or characters. The default is []. + specified as a list of characters. The default is []. - salt(+List) Optionally, a list of bytes that are used as salt. The default is all zeroes. @@ -480,8 +479,8 @@ bytes_base64_([A,B,C|Ls]) --> [W,X,Y,Z], Algorithm, key Key, and initialization vector (or nonce) IV, to give CipherText. - PlainText must be a list of bytes or characters, Key and IV must be - lists of bytes, and CipherText is created as a list of characters. + PlainText must be a list of characters, Key and IV must be lists of + bytes, and CipherText is created as a list of characters. Keys and IVs can be chosen at random (using for example crypto_n_random_bytes/2) or derived from input keying material (IKM) @@ -579,9 +578,9 @@ crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :- Decrypt the given CipherText, using the symmetric algorithm Algorithm, key Key, and initialization vector IV, to give - PlainText. CipherText must be a list of bytes or characters, and - Key and IV must be lists of bytes. PlainText is created as a list - of characters. + PlainText. CipherText must be a list of characters, and Key and IV + must be lists of bytes. PlainText is created as a list of + characters. Currently, the only supported algorithm is 'chacha20-poly1305', a very secure, fast and versatile authenticated encryption method. From 72bf45912e13953c885516bb389f8470ff9e7e46 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 00:14:15 +0200 Subject: [PATCH 33/40] "PrivateKey" --> "KeyPair" --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index d4ebf2e5..8c255a4c 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -23,7 +23,7 @@ crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options ed25519_new_keypair/1, % -KeyPair ed25519_keypair_public_key/2, % +KeyPair, +PublicKey - ed25519_sign/4, % +PrivateKey, +Data, -Signature, +Options + ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options crypto_name_curve/2, % +Name, -Curve crypto_curve_order/2, % +Curve, -Order From 93da831ef0f0eaac935f75a8c3d0a24bf20d5d34 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 01:27:06 +0200 Subject: [PATCH 34/40] correct mode indication for ed25519_verify/4 Noted by @notoria in #545. Many thanks! --- src/prolog/lib/crypto.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 8c255a4c..67fef071 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -24,7 +24,7 @@ ed25519_new_keypair/1, % -KeyPair ed25519_keypair_public_key/2, % +KeyPair, +PublicKey ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options - ed25519_verify/4, % +PublicKey, +Data, -Signature, +Options + ed25519_verify/4, % +PublicKey, +Data, +Signature, +Options crypto_name_curve/2, % +Name, -Curve crypto_curve_order/2, % +Curve, -Order crypto_curve_generator/2, % +Curve, -Generator From 1108c99688b926a00716fbe960fd416b3ed193a4 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Thu, 21 May 2020 13:41:57 +0200 Subject: [PATCH 35/40] document that lists of integers can be specified if encoding(octet) is used This seems to be a good compromise: The API now strongly encourages lists of characters, which are ideally suited to represent text, and also allows lists of bytes if the encoding(octet) option is used. --- src/prolog/lib/crypto.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prolog/lib/crypto.pl b/src/prolog/lib/crypto.pl index 67fef071..cd7f93c5 100644 --- a/src/prolog/lib/crypto.pl +++ b/src/prolog/lib/crypto.pl @@ -10,6 +10,10 @@ Especially for cryptographic applications, it as an advantage that using strings leaves little trace of what was processed in the system. + + For predicates that accept an encoding/1 option to specify the encoding + of the input data, if encoding(octet) is used, then the input can also + be specified as a list of bytes, i.e., integers between 0 and 255. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(crypto, From 9e9c3b63423940f7cc6f1eb39a324793508ac666 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Thu, 21 May 2020 22:33:22 -0600 Subject: [PATCH 36/40] modify int_pow so that a power of -1 is valid (#548) --- src/prolog/machine/arithmetic_ops.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/prolog/machine/arithmetic_ops.rs b/src/prolog/machine/arithmetic_ops.rs index ab604e32..ae3e3c62 100644 --- a/src/prolog/machine/arithmetic_ops.rs +++ b/src/prolog/machine/arithmetic_ops.rs @@ -398,7 +398,7 @@ impl MachineState { match (n1, n2) { (Number::Fixnum(n1), Number::Fixnum(n2)) => { - if n1 != 1 && n2 < 0 { + if n1 != 1 && n2 < -1 { let n = Number::from(n1); let stub = MachineError::functor_stub(clause_name!("^"), 2); @@ -424,7 +424,7 @@ impl MachineState { } } (Number::Fixnum(n1), Number::Integer(n2)) => { - if n1 != 1 && &*n2 < &0 { + if n1 != 1 && &*n2 < &-1 { let n = Number::from(n1); let stub = MachineError::functor_stub(clause_name!("^"), 2); @@ -442,7 +442,7 @@ impl MachineState { } } (Number::Integer(n1), Number::Fixnum(n2)) => { - if &*n1 != &1 && n2 < 0 { + if &*n1 != &1 && n2 < -1 { let n = Number::Integer(n1); let stub = MachineError::functor_stub(clause_name!("^"), 2); @@ -460,7 +460,7 @@ impl MachineState { } } (Number::Integer(n1), Number::Integer(n2)) => { - if &*n1 != &1 && &*n2 < &0 { + if &*n1 != &1 && &*n2 < &-1 { let n = Number::Integer(n1); let stub = MachineError::functor_stub(clause_name!("^"), 2); From cbbed310c51640f11ade3a61752f4682f5ae8572 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Fri, 22 May 2020 17:21:50 +0200 Subject: [PATCH 37/40] ADDED: format/3, writing formatted output to a stream Both binary and text streams are supported. --- README.md | 2 +- src/prolog/lib/format.pl | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ad29239..2a477a28 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ The modules that ship with Scryer Prolog are also called * [`format`](src/prolog/lib/format.pl) The nonterminal `format_//2` is used to describe formatted output, arranging arguments according to a given format string. - The predicates `format/2`, `portray_clause/1` and `listing/1` + The predicates `format/[2,3]`, `portray_clause/1` and `listing/1` provide formatted *impure* output. * [`assoc`](src/prolog/lib/assoc.pl) providing `empty_assoc/1`, `get_assoc/3`, `put_assoc/4` etc. diff --git a/src/prolog/lib/format.pl b/src/prolog/lib/format.pl index 17ab0bfc..c0ef87c1 100644 --- a/src/prolog/lib/format.pl +++ b/src/prolog/lib/format.pl @@ -49,6 +49,10 @@ The predicate format/2 is like format_//2, except that it outputs the text on the terminal instead of describing it declaratively. + format/3, used as format(Stream, FormatString, Arguments), outputs + the described string to the given Stream. If Stream is a binary + stream, then the code of each emitted character must be in 0..255. + If at all possible, format_//2 should be used, to stress pure parts that enable easy testing etc. If necessary, you can emit the list Ls with maplist(write, Ls). @@ -68,6 +72,7 @@ :- module(format, [format_//2, format/2, + format/3, portray_clause/1, listing/1 ]). @@ -359,6 +364,14 @@ format(Fs, Args) :- phrase(format_(Fs, Args), Cs), maplist(write, Cs). +format(Stream, Fs, Args) :- + phrase(format_(Fs, Args), Cs), + ( stream_property(Stream, type(binary)) -> + maplist(char_code, Cs, Bytes), + maplist(put_byte(Stream), Bytes) + ; maplist(put_char(Stream), Cs) + ). + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ?- phrase(cells("hello", [], 0, []), Cs). From 48c0b0ab3c9eb4f8c8ec9b953d0c6cc61a0a47fc Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Fri, 22 May 2020 17:30:29 +0200 Subject: [PATCH 38/40] ENHANCED: Faster format/3 for binary streams. This speeds up web servers considerably when sending binary files. --- src/prolog/clause_types.rs | 7 +++++++ src/prolog/lib/format.pl | 16 +++++++++++++-- src/prolog/machine/system_calls.rs | 31 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index b783ac0c..e685c65f 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -229,6 +229,7 @@ pub enum SystemClauseType { PeekCode, PointsToContinuationResetMarker, PutByte, + PutBytes, PutChar, PutCode, REPL(REPLCodePtr), @@ -415,6 +416,9 @@ impl SystemClauseType { &SystemClauseType::PutByte => { clause_name!("$put_byte") } + &SystemClauseType::PutBytes => { + clause_name!("$put_bytes") + } &SystemClauseType::PutChar => { clause_name!("$put_char") } @@ -552,6 +556,9 @@ impl SystemClauseType { ("$put_byte", 2) => { Some(SystemClauseType::PutByte) } + ("$put_bytes", 2) => { + Some(SystemClauseType::PutBytes) + } ("$put_char", 2) => { Some(SystemClauseType::PutChar) } diff --git a/src/prolog/lib/format.pl b/src/prolog/lib/format.pl index c0ef87c1..18289094 100644 --- a/src/prolog/lib/format.pl +++ b/src/prolog/lib/format.pl @@ -367,8 +367,20 @@ format(Fs, Args) :- format(Stream, Fs, Args) :- phrase(format_(Fs, Args), Cs), ( stream_property(Stream, type(binary)) -> - maplist(char_code, Cs, Bytes), - maplist(put_byte(Stream), Bytes) + % maplist(char_code, Cs, Bytes) is currently a lot slower + % than first converting Cs to an atom, and then to codes. + % In the future, we can ideally avoid creating an atom here, + % since an atom leaves traces in the system. + atom_chars(A, Cs), + atom_codes(A, Bytes), + ( member(NonByte, Bytes), NonByte > 255 -> + char_code(Char, NonByte), + throw(error(representation_error(Char), format/3)) + ; true + ), + % For binary streams, we use a specialised internal predicate + % that uses only a single "write" operation for efficiency. + '$put_bytes'(Stream, Bytes) ; maplist(put_char(Stream), Cs) ). diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 1fe7b576..0ab8411e 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -2153,6 +2153,37 @@ impl MachineState { } } } + &SystemClauseType::PutBytes => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "$put_bytes", 2)?; + + let stub = MachineError::functor_stub(clause_name!("$put_bytes"), 2); + let bytes = self.integers_to_bytevec(temp_v!(2), stub); + + match stream.write(&bytes) { + Ok(_) => { + return return_from_clause!(self.last_call, self); + } + _ => { + let stub = MachineError::functor_stub( + clause_name!("$put_bytes"), + 2, + ); + + let addr = self.heap.to_unifiable( + HeapCellValue::Stream(stream.clone()), + ); + + return Err(self.error_form( + MachineError::existence_error( + self.heap.h(), + ExistenceError::Stream(addr), + ), + stub, + )); + } + } + } &SystemClauseType::GetByte => { let mut stream = self.get_stream_or_alias(self[temp_v!(1)], indices, "get_byte", 2)?; From b2d720b853269ed9c6917f2078d03d28b7b6daeb Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Fri, 22 May 2020 14:50:40 -0600 Subject: [PATCH 39/40] correct get_byte/1 not emitted -1 upon discovery of end_of_stream position (#555) --- src/prolog/machine/system_calls.rs | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 1fe7b576..cf7c0a93 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -2166,6 +2166,13 @@ impl MachineState { )?; if stream.past_end_of_stream() { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("get_byte"), + 2, + )?; + if EOFAction::Reset != stream.options.eof_action { return return_from_clause!(self.last_call, self); } else if self.fail { @@ -2173,12 +2180,6 @@ impl MachineState { } } - if stream.at_end_of_stream() { - stream.set_past_end_of_stream(); - self.unify(self[temp_v!(2)], Addr::Fixnum(-1)); - return return_from_clause!(self.last_call, self); - } - let addr = match self.store(self.deref(self[temp_v!(2)])) { addr if addr.is_ref() => { @@ -2238,18 +2239,9 @@ impl MachineState { } } _ => { - self.eof_action( - self[temp_v!(2)], - &mut stream, - clause_name!("get_byte"), - 2, - )?; - - if EOFAction::Reset != stream.options.eof_action { - return return_from_clause!(self.last_call, self); - } else if self.fail { - return Ok(()); - } + stream.set_past_end_of_stream(); + self.unify(self[temp_v!(2)], Addr::Fixnum(-1)); + return return_from_clause!(self.last_call, self); } } } From 0060c8988a967d70ee69f9a779e75e552fffb500 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Fri, 22 May 2020 15:58:07 -0600 Subject: [PATCH 40/40] use -1 as eof_code for binary streams (#555) --- src/prolog/machine/streams.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/prolog/machine/streams.rs b/src/prolog/machine/streams.rs index 9e6ea491..6308017d 100644 --- a/src/prolog/machine/streams.rs +++ b/src/prolog/machine/streams.rs @@ -688,9 +688,14 @@ impl MachineState { return Err(self.open_past_eos_error(stream.clone(), caller, arity)); } EOFAction::EOFCode => { - let end_of_stream = self.heap.to_unifiable( - HeapCellValue::Atom(clause_name!("end_of_file"), None) - ); + let end_of_stream = + if stream.options.stream_type == StreamType::Binary { + Addr::Fixnum(-1) + } else { + self.heap.to_unifiable( + HeapCellValue::Atom(clause_name!("end_of_file"), None) + ) + }; stream.set_past_end_of_stream(); Ok(self.unify(result, end_of_stream))