Upgrade to 8.3.1 from 8.0.3 breaks crypto lib call

Upgrade to 8.3.1 from 8.0.3

%!   make_login_cookie(+UName:atom, -Cookie:string) is semidet
%   Make the contents (a string of hex) of a persistent login cookie
%   using:
%       * the passed in user name
%       * the rememberme_duration
%       * a secret generated and stored in file
make_login_cookie(UName, Cookie) :-
      www_form_encode(URLEncodedUName, UName),
      setting(identity:rememberme_duration, DurSecs),
      Expires is floor(Now + DurSecs),
      atomics_to_string([URLEncodedUName, "/", Expires], PlainText),
      crypto_n_random_bytes(12, Nonce),
      % next line throws
      crypto_data_encrypt(PlainText, 'chacha20-poly1305',
                          Key, Nonce, CipherText, []),
      string_codes(CipherText, CipherTextCodes),
      append(Nonce, CipherTextCodes, TokenList),
      hex_bytes(Cookie, TokenList).

 crypto:'_crypto_data_encrypt'/8: Domain error: `cipher' expected, found `'chacha20-poly1305''

I presume the correct thing to pass is in the foreign library crypto4pl, but I’m reluctant to select randomly, as this has security implications.

Works fine here (Ubuntu 20.04. Note that you need to have compiled against a version of OpenSSL that supports this algorithm. That probably requires the right OpenSSL version (range) and possibly the right flags.

1 Like

on 18.04

Odd part is that I installed 8.0.3 on same machine. Maybe I upgraded the openssl lib?

I don’t know. I did a quick grep for chacha on the source and only found hits in the comments so I suspect crypto_data_encrypt/6 simply passes strings to OpenSSL to select he algorithm. Most likely the openssl command has some option to check which algorithms it supports. Make sure Prolog and openssl bind to the same library though. An old library or header in the wrong place is another common cause for issues like this.

1 Like

I recently upgraded from 8.0.3 to 8.2.0 and I’m having problems with strings in the FFI (also fails in 8.0.3). The following hits an assert:

flags = PL_STRING | REP_UTF8
PL_put_chars(swipl_goalCharList,  flags, -1, query.encode("utf-8"))
from pyswip.core import PL_open_foreign_frame, PL_new_term_refs, \
    PL_initialise, \
    PL_new_term_ref, PL_chars_to_term, PL_call, PL_discard_foreign_frame, PL_put_list_chars, \

def _initialize():
    MUST NOT be called twice.
    args = []
    result = PL_initialise(len(args), args)
    if not result:
        raise Exception("Could not initialize the Prolog environment."
                          "PL_initialise returned %d" % result)

    swipl_fid = PL_open_foreign_frame()
    swipl_load = PL_new_term_ref()
    PL_chars_to_term("asserta(pyrun(GoalString,BindingList) :- "
                     "call(Goal__))).", swipl_load)
    PL_call(swipl_load, None)


query = 'assertz(father(michael,john)).'

swipl_fid = PL_open_foreign_frame()
swipl_args = PL_new_term_refs(2)
swipl_goalCharList = swipl_args
swipl_bindingList = swipl_args + 1

# PL_put_list_chars(swipl_goalCharList, query) # Success!

flags = PL_STRING | REP_UTF8  #  -->  0x100005
print("{:0x}".format(flags & ~(REP_UTF8|REP_MB|REP_ISO_LATIN_1))) # --> 5
PL_put_chars(swipl_goalCharList, flags, -1, query.encode("utf-8"))
#PL_put_chars(swipl_goalCharList, PL_STRING | REP_UTF8, -1, query.encode("utf-8"))  # same failure

Fails with

PL_put_chars: Assertion failed: 0
C-stack trace labeled "assert_fail":
  [0] PL_strtod() at ??:? [0x7fea032a83c8]
  [1] __assert_fail() at ??:? [0x7fea03265de7]
  [2] PL_put_chars() at ??:? [0x7fea031c4089]
  [3] ffi_call_unix64() at ??:? [0x7fe9e8466dae]
  [4] ffi_call() at ??:? [0x7fe9e846671f]
  [5] _ctypes_callproc() at ??:? [0x7fe9e867a944]
  [6] _ctypes_callproc() at ??:? [0x7fe9e867b0d4]
  [7] /home/sir/venv/p37_default/bin/python(_PyObject_FastCallKeywords+0x4eb) [0x5d96db]
  [8] /home/sir/venv/p37_default/bin/python(_PyEval_EvalFrameDefault+0x4679) [0x551e89]

PL_put_chars is implented as:

PL_put_chars(term_t t, int flags, size_t len, const char *s)
  PL_chars_t text;
  word w = 0;
  int rc = FALSE;

  if ( len == (size_t)-1 )
    len = strlen(s);

  text.text.t    = (char*)s;
  text.encoding  = ((flags&REP_UTF8) ? ENC_UTF8 : \
		    (flags&REP_MB)   ? ENC_ANSI : ENC_ISO_LATIN_1);
  text.length    = len;
  text.canonical = FALSE;
  text.storage   = PL_CHARS_HEAP;

  flags &= ~(REP_UTF8|REP_MB|REP_ISO_LATIN_1);

  if ( flags == PL_ATOM )
    w = textToAtom(&text);
  else if ( flags == PL_STRING )
    w = textToString(&text);
  else if ( flags == PL_CODE_LIST || flags == PL_CHAR_LIST )
  { PL_put_variable(t);
    rc = PL_unify_text(t, 0, &text, flags);
  } else

  if ( w )
  { setHandle(t, w);
    rc = TRUE;


  return rc;

I don’t see a reason why it would hit the assert.

I don’t see a way to revert to 8.0.3 to test anything. There is no package in ppa stable branch:

Did it get deleted? (I’d need bionic / Ubuntu 18).

The pyswipl FFI uses hard coded constants that are copied from SWI-Prolog.h. A number of these values have changed. This is a very rare situation, but was necessary to accommodate rational numbers.

The best route is probably to resync these constants in the Python interface. Ideally such an upgrade would also support the new SWI-Prolog rational numbers …


Ooof. PL_STRING changed from 5 to 6.
I also found REP_UTF8 was incorrect in pyswip (I don’t think it has changed in SWI for some time).
Thank @jan . This saved me a lot of hunting.

This could have used a major version number increment since its a breaking API change.

1 Like

We went from 8.0 to 8.2, which in SWI-Prolog’s versioning is a new release cycle. The project is less suitable for the common major.minor.patch semantics. On the stable release we want to maintain the patch level semantics. On the development cycle things just develop and may break. As is, major releases are landmarks that indicate major changes of functionality. I agree this is all a bit fuzzy :frowning:

1 Like

Will you make a PR on pyswip?

Yes – it is already done and accepted.