]> Repositorios git - scryer-prolog.git/commitdiff
ENHANCED: Use crrl for Ed25519 signing and signature verification.
authorMarkus Triska <[email protected]>
Tue, 26 Dec 2023 06:24:18 +0000 (07:24 +0100)
committerMarkus Triska <[email protected]>
Tue, 26 Dec 2023 06:33:44 +0000 (07:33 +0100)
The main motivation for this change is the introduction of the newly
available predicate ed25519_seed_keypair/2, allowing to generate a key
pair from a given seed. In this way, a key pair can be dynamically
generated from (for example) a password, using crypto_password_hash/3
in combination with crypto_data_hkdf/4 to generate the seed. The
advantage of this method is that the private key need not be stored at
all anywhere.

It is not possible to add a corresponding feature to ring, since it is
closed as "not planned": https://github.com/briansmith/ring/issues/1003

I also used this opportunity to move more of the logic to Prolog. We
now have total control of the key pair representation, and I also
changed the representation to conform to the PKCS#8 v2 standard,
something that only later ring versions do, while still being
backwards compatible with tools that produce a wrong representation
including earlier ring versions.

Another great advantage we get from this change is that the Ed25519
predicates now also run on the 32-bit and WASM versions of Scryer.

build/instructions_template.rs
src/lib/crypto.pl
src/machine/dispatch.rs
src/machine/system_calls.rs

index db0baae3616d1b21ea40117f634d4ad1c6580bfb..fee8e28fa36c819bba15f00d3513b68b9312c293 100644 (file)
@@ -511,18 +511,12 @@ enum SystemClauseType {
     #[cfg(feature = "crypto-full")]
     #[strum_discriminants(strum(props(Arity = "6", Name = "$crypto_data_decrypt")))]
     CryptoDataDecrypt,
-    #[cfg(feature = "crypto-full")]
-    #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_sign")))]
-    Ed25519Sign,
-    #[cfg(feature = "crypto-full")]
-    #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_verify")))]
-    Ed25519Verify,
-    #[cfg(feature = "crypto-full")]
-    #[strum_discriminants(strum(props(Arity = "1", Name = "$ed25519_new_keypair")))]
-    Ed25519NewKeyPair,
-    #[cfg(feature = "crypto-full")]
-    #[strum_discriminants(strum(props(Arity = "2", Name = "$ed25519_keypair_public_key")))]
-    Ed25519KeyPairPublicKey,
+    #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_sign_raw")))]
+    Ed25519SignRaw,
+    #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_verify_raw")))]
+    Ed25519VerifyRaw,
+    #[strum_discriminants(strum(props(Arity = "2", Name = "$ed25519_seed_to_public_key")))]
+    Ed25519SeedToPublicKey,
     #[strum_discriminants(strum(props(Arity = "2", Name = "$first_non_octet")))]
     FirstNonOctet,
     #[strum_discriminants(strum(props(Arity = "3", Name = "$load_html")))]
@@ -1874,18 +1868,17 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::CallFlushTermQueue |
                     &Instruction::CallRemoveModuleExports |
                     &Instruction::CallAddNonCountedBacktracking |
-                    &Instruction::CallPopCount => {
+                    &Instruction::CallPopCount |
+                    &Instruction::CallEd25519SignRaw |
+                    &Instruction::CallEd25519VerifyRaw |
+                    &Instruction::CallEd25519SeedToPublicKey => {
                         let (name, arity) = self.to_name_and_arity();
                         functor!(atom!("call"), [atom(name), fixnum(arity)])
                     }
                     //
                     #[cfg(feature = "crypto-full")]
                     &Instruction::CallCryptoDataEncrypt |
-                    &Instruction::CallCryptoDataDecrypt |
-                    &Instruction::CallEd25519Sign |
-                    &Instruction::CallEd25519Verify |
-                    &Instruction::CallEd25519NewKeyPair |
-                    &Instruction::CallEd25519KeyPairPublicKey => {
+                    &Instruction::CallCryptoDataDecrypt => {
                         let (name, arity) = self.to_name_and_arity();
                         functor!(atom!("call"), [atom(name), fixnum(arity)])
                     }
@@ -2110,18 +2103,17 @@ fn generate_instruction_preface() -> TokenStream {
                     &Instruction::ExecuteFlushTermQueue |
                     &Instruction::ExecuteRemoveModuleExports |
                     &Instruction::ExecuteAddNonCountedBacktracking |
-                    &Instruction::ExecutePopCount => {
+                    &Instruction::ExecutePopCount |
+                    &Instruction::ExecuteEd25519SignRaw |
+                    &Instruction::ExecuteEd25519VerifyRaw |
+                    &Instruction::ExecuteEd25519SeedToPublicKey => {
                         let (name, arity) = self.to_name_and_arity();
                         functor!(atom!("execute"), [atom(name), fixnum(arity)])
                     }
                     //
                     #[cfg(feature = "crypto-full")]
                     &Instruction::ExecuteCryptoDataEncrypt |
-                    &Instruction::ExecuteCryptoDataDecrypt |
-                    &Instruction::ExecuteEd25519Sign |
-                    &Instruction::ExecuteEd25519Verify |
-                    &Instruction::ExecuteEd25519NewKeyPair |
-                    &Instruction::ExecuteEd25519KeyPairPublicKey => {
+                    &Instruction::ExecuteCryptoDataDecrypt => {
                         let (name, arity) = self.to_name_and_arity();
                         functor!(atom!("execute"), [atom(name), fixnum(arity)])
                     }
index bec12d514187c355199fb468e51d3b44b62f6504..b03c544ac89bdff68e19f13af29696f4950086bd 100644 (file)
@@ -25,6 +25,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_seed_keypair/2,       % +Seed, -KeyPair
            ed25519_new_keypair/1,        % -KeyPair
            ed25519_keypair_public_key/2, % +KeyPair, +PublicKey
            ed25519_sign/4,               % +KeyPair, +Data, -Signature, +Options
@@ -612,6 +613,34 @@ encoding_chars(utf8, Cs, Cs) :-
    ===============================
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 
+%% ed25519_seed_keypair(+Seed, -Pair)
+%
+% Use Seed to deterministically generate an Ed25519 key pair Pair, a
+% list of characters. Seed must be a list of 32 bytes.  It can be
+% chosen at random (using for example `crypto_n_random_bytes/2`) or
+% derived from input keying material (IKM) using for example
+% `crypto_data_hkdf/4`.  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`.
+
+ed25519_seed_keypair(Seed, Pair) :-
+        must_be_bytes(Seed, ed25519_keypair_from_seed/2),
+        length(Seed, 32),
+        '$ed25519_seed_to_public_key'(Seed, Public),
+        maplist(char_code, Public, PublicBytes),
+        phrase(([0x30,81], % a sequence of 81 bytes follows
+                [2,1],     % the integer 1 denoting version 2 (awesome design!)
+                [1],       % the public key is also present
+                [48,5],    % an element of 5 bytes follows
+                [6,3,43,101,112],   % OID of Ed25519
+                [4,34],    % an octet string of 34 bytes follows
+                [4,32],    % an octet string of 32 bytes follows
+                seq(Seed), % the seed is the private key
+                [129,33],
+                [0],       % 32 bytes is divisible by 8
+                seq(PublicBytes)), ASN1),
+        maplist(char_code, Pair, ASN1).
+
 %% ed25519_new_keypair(-Pair)
 %
 %  Yields a new Ed25519 key pair Pair, a list of characters. The
@@ -620,7 +649,8 @@ encoding_chars(utf8, Cs, Cs) :-
 %  with `ed25519_keypair_public_key/2`.
 
 ed25519_new_keypair(Pair) :-
-        '$ed25519_new_keypair'(Pair).
+        crypto_n_random_bytes(32, Bytes),
+        ed25519_seed_keypair(Bytes, Pair).
 
 %% ed25519_keypair_public_key(+Pair, -PublicKey)
 %
@@ -629,8 +659,11 @@ ed25519_new_keypair(Pair) :-
 %  The public key is represented as a list of characters.
 
 ed25519_keypair_public_key(Pair, PublicKey) :-
-        must_be_octet_chars(Pair, ed25519_keypair_public_key),
-        '$ed25519_keypair_public_key'(Pair, PublicKey).
+        must_be_octet_chars(Pair, ed25519_keypair_public_key/2),
+        reverse(Pair, RPs),
+        length(RPublicKey, 32),
+        phrase((seq(RPublicKey),...), RPs),
+        reverse(RPublicKey, PublicKey).
 
 %% ed25519_sign(+Key, +Data, -Signature, +Options)
 %
@@ -638,10 +671,14 @@ ed25519_keypair_public_key(Pair, PublicKey) :-
 %  PKCS#8 v2 format as generated by `ed25519_new_keypair/1`. Sign Data
 %  with Key, yielding Signature as a list of hexadecimal characters.
 
-ed25519_sign(Key, Data0, Signature, Options) :-
-        must_be_octet_chars(Key, ed25519_sign),
+ed25519_sign(KeyPair, Data0, Signature, Options) :-
+        must_be_octet_chars(KeyPair, ed25519_sign/4),
+        length(Prefix, 16),
+        length(PrivateKeyChars, 32),
+        phrase((seq(Prefix),seq(PrivateKeyChars),...), KeyPair),
+        maplist(char_code, PrivateKeyChars, PrivateKey),
         options_data_chars(Options, Data0, Data, Encoding),
-        '$ed25519_sign'(Key, Data, Encoding, Signature0),
+        '$ed25519_sign_raw'(PrivateKey, Data, Encoding, Signature0),
         hex_bytes(Signature, Signature0).
 
 %% ed25519_verify(+Key, +Data, +Signature, +Options)
@@ -658,10 +695,10 @@ ed25519_sign(Key, Data0, Signature, Options) :-
 %    which treats Data as a list of raw bytes.
 
 ed25519_verify(Key, Data0, Signature0, Options) :-
-        must_be_octet_chars(Key, ed25519_verify),
+        must_be_octet_chars(Key, ed25519_verify/4),
         options_data_chars(Options, Data0, Data, Encoding),
         hex_bytes(Signature0, Signature),
-        '$ed25519_verify'(Key, Data, Encoding, Signature).
+        '$ed25519_verify_raw'(Key, Data, Encoding, Signature).
 
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    X25519: ECDH key exchange over Curve25519
index ff7d01889ea0803388fb7570fa2acc065f1efa83..29f05401d628ead7880c0e80a5f812c2dd7bdb61 100644 (file)
@@ -4491,44 +4491,28 @@ impl Machine {
                         self.crypto_curve_scalar_mult();
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::CallEd25519Sign => {
-                        self.ed25519_sign();
-                        step_or_fail!(self, self.machine_st.p += 1);
-                    }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::ExecuteEd25519Sign => {
-                        self.ed25519_sign();
-                        step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
-                    }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::CallEd25519Verify => {
-                        self.ed25519_verify();
+                    &Instruction::CallEd25519SignRaw => {
+                        self.ed25519_sign_raw();
                         step_or_fail!(self, self.machine_st.p += 1);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::ExecuteEd25519Verify => {
-                        self.ed25519_verify();
+                    &Instruction::ExecuteEd25519SignRaw => {
+                        self.ed25519_sign_raw();
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::CallEd25519NewKeyPair => {
-                        self.ed25519_new_key_pair();
+                    &Instruction::CallEd25519VerifyRaw => {
+                        self.ed25519_verify_raw();
                         step_or_fail!(self, self.machine_st.p += 1);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::ExecuteEd25519NewKeyPair => {
-                        self.ed25519_new_key_pair();
+                    &Instruction::ExecuteEd25519VerifyRaw => {
+                        self.ed25519_verify_raw();
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::CallEd25519KeyPairPublicKey => {
-                        self.ed25519_key_pair_public_key();
+                    &Instruction::CallEd25519SeedToPublicKey => {
+                        self.ed25519_seed_to_public_key();
                         step_or_fail!(self, self.machine_st.p += 1);
                     }
-                    #[cfg(feature = "crypto-full")]
-                    &Instruction::ExecuteEd25519KeyPairPublicKey => {
-                        self.ed25519_key_pair_public_key();
+                    &Instruction::ExecuteEd25519SeedToPublicKey => {
+                        self.ed25519_seed_to_public_key();
                         step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
                     }
                     &Instruction::CallCurve25519ScalarMult => {
index 2e3c1d54ce39f03dfd1395f7859aa2888604f99f..c5beaf9aa25b7ae9d4b3e556d97fdd894cca3ef9 100644 (file)
@@ -81,14 +81,11 @@ use ring::rand::{SecureRandom, SystemRandom};
 use ring::{digest, hkdf, pbkdf2};
 
 #[cfg(feature = "crypto-full")]
-use ring::{
-    aead,
-    signature::{self, KeyPair},
-};
+use ring::aead;
 use ripemd160::{Digest, Ripemd160};
 use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};
 
-use crrl::{secp256k1, x25519};
+use crrl::{ed25519, secp256k1, x25519};
 
 #[cfg(feature = "tls")]
 use native_tls::{Identity, TlsAcceptor, TlsConnector};
@@ -7617,33 +7614,16 @@ impl Machine {
         unify!(self.machine_st, self.machine_st.registers[4], uncompressed);
     }
 
-    #[cfg(feature = "crypto-full")]
     #[inline(always)]
-    pub(crate) fn ed25519_new_key_pair(&mut self) {
-        let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(rng()).unwrap();
-        let complete_string = self.u8s_to_string(pkcs8_bytes.as_ref());
-
-        unify!(
-            self.machine_st,
-            self.machine_st.registers[1],
-            complete_string
-        )
-    }
-
-    #[cfg(feature = "crypto-full")]
-    #[inline(always)]
-    pub(crate) fn ed25519_key_pair_public_key(&mut self) {
-        let bytes = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
+    pub(crate) fn ed25519_seed_to_public_key(&mut self) {
+        let stub_gen = || functor_stub(atom!("ed25519_seed_keypair"), 2);
+        let seed_bytes = self
+            .machine_st
+            .integers_to_bytevec(self.machine_st.registers[1], stub_gen);
 
-        let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&bytes) {
-            Ok(kp) => kp,
-            _ => {
-                self.machine_st.fail = true;
-                return;
-            }
-        };
+        let skey = ed25519::PrivateKey::from_seed(&seed_bytes);
 
-        let complete_string = self.u8s_to_string(key_pair.public_key().as_ref());
+        let complete_string = self.u8s_to_string(skey.public_key.encoded.as_ref());
 
         unify!(
             self.machine_st,
@@ -7652,22 +7632,19 @@ impl Machine {
         );
     }
 
-    #[cfg(feature = "crypto-full")]
     #[inline(always)]
-    pub(crate) fn ed25519_sign(&mut self) {
-        let key = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
+    pub(crate) fn ed25519_sign_raw(&mut self) {
+        let stub_gen = || functor_stub(atom!("ed25519_sign"), 4);
+        let seed_bytes = self
+            .machine_st
+            .integers_to_bytevec(self.machine_st.registers[1], stub_gen);
+
+        let skey = ed25519::PrivateKey::from_seed(&seed_bytes);
+
         let encoding = cell_as_atom!(self.deref_register(3));
         let data = self.string_encoding_bytes(self.machine_st.registers[2], encoding);
 
-        let key_pair = match signature::Ed25519KeyPair::from_pkcs8(&key) {
-            Ok(kp) => kp,
-            _ => {
-                self.machine_st.fail = true;
-                return;
-            }
-        };
-
-        let sig = key_pair.sign(&data);
+        let sig = skey.sign_raw(&data);
 
         let sig_list = heap_loc_as_cell!(iter_to_heap_list(
             &mut self.machine_st.heap,
@@ -7679,25 +7656,21 @@ impl Machine {
         unify!(self.machine_st, self.machine_st.registers[4], sig_list);
     }
 
-    #[cfg(feature = "crypto-full")]
     #[inline(always)]
-    pub(crate) fn ed25519_verify(&mut self) {
-        let key = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
+    pub(crate) fn ed25519_verify_raw(&mut self) {
+        let key_bytes = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
+        let pkey = ed25519::PublicKey::decode(&key_bytes).unwrap();
+
         let encoding = cell_as_atom!(self.deref_register(3));
         let data = self.string_encoding_bytes(self.machine_st.registers[2], encoding);
-        let stub_gen = || functor_stub(atom!("ed25519_verify"), 5);
+
+        let stub_gen = || functor_stub(atom!("ed25519_verify"), 4);
+
         let signature = self
             .machine_st
             .integers_to_bytevec(self.machine_st.registers[4], stub_gen);
 
-        let peer_public_key = signature::UnparsedPublicKey::new(&signature::ED25519, &key);
-
-        match peer_public_key.verify(&data, &signature) {
-            Ok(_) => {}
-            _ => {
-                self.machine_st.fail = true;
-            }
-        }
+        self.machine_st.fail = !pkey.verify_raw(&signature, &data);
     }
 
     #[inline(always)]