From d83b5d37ca7d3167db33534ebc1a5b1e8b2fa563 Mon Sep 17 00:00:00 2001 From: Benjamin DENEUX Date: Wed, 11 Oct 2023 13:23:31 +0200 Subject: [PATCH] feat(logic): handle encoding option --- x/logic/predicate/atom.go | 3 ++ x/logic/predicate/crypto.go | 12 ++--- x/logic/predicate/crypto_test.go | 67 +++++++++++++++------------ x/logic/predicate/util.go | 79 +++++++++++++++++++++++++++++--- 4 files changed, 119 insertions(+), 42 deletions(-) diff --git a/x/logic/predicate/atom.go b/x/logic/predicate/atom.go index 14e343f0..1b21eafa 100644 --- a/x/logic/predicate/atom.go +++ b/x/logic/predicate/atom.go @@ -28,6 +28,9 @@ var AtomEmptyArray = engine.NewAtom("[]") // AtomNull is the term null. var AtomNull = engine.NewAtom("null") +// AtomEncoding is the term used to indicate the encoding type option. +var AtomEncoding = engine.NewAtom("encoding") + // MakeNull returns the compound term @(null). // It is used to represent the null value in json objects. func MakeNull() engine.Term { diff --git a/x/logic/predicate/crypto.go b/x/logic/predicate/crypto.go index 6726d157..7032f7c1 100644 --- a/x/logic/predicate/crypto.go +++ b/x/logic/predicate/crypto.go @@ -110,17 +110,17 @@ const ( func ECDSAVerify(vm *engine.VM, key, data, sig, options engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { return engine.Delay(func(ctx context.Context) *engine.Promise { - pubKey, err := TermToBytes(key, env) + pubKey, err := TermToBytes(key, AtomEncoding.Apply(engine.NewAtom("octet")), env) if err != nil { return engine.Error(fmt.Errorf("ecdsa_verify/4: decoding public key: %w", err)) } - msg, err := TermToBytes(data, env) + msg, err := TermToBytes(data, options, env) if err != nil { return engine.Error(fmt.Errorf("ecdsa_verify/4: decoding data: %w", err)) } - signature, err := TermToBytes(sig, env) + signature, err := TermToBytes(sig, AtomEncoding.Apply(engine.NewAtom("octet")), env) if err != nil { return engine.Error(fmt.Errorf("ecdsa_verify/4: decoding signature: %w", err)) } @@ -140,17 +140,17 @@ func ECDSAVerify(vm *engine.VM, key, data, sig, options engine.Term, cont engine func SecpVerify(vm *engine.VM, key, data, sig, options engine.Term, cont engine.Cont, env *engine.Env) *engine.Promise { return engine.Delay(func(ctx context.Context) *engine.Promise { - pubKey, err := TermToBytes(key, env) + pubKey, err := TermToBytes(key, AtomEncoding.Apply(engine.NewAtom("octet")), env) if err != nil { return engine.Error(fmt.Errorf("secp_verify/4: decoding public key: %w", err)) } - msg, err := TermToBytes(data, env) + msg, err := TermToBytes(data, options, env) if err != nil { return engine.Error(fmt.Errorf("secp_verify/4: decoding data: %w", err)) } - signature, err := TermToBytes(sig, env) + signature, err := TermToBytes(sig, AtomEncoding.Apply(engine.NewAtom("octet")), env) if err != nil { return engine.Error(fmt.Errorf("secp_verify/4: decoding signature: %w", err)) } diff --git a/x/logic/predicate/crypto_test.go b/x/logic/predicate/crypto_test.go index edc425cc..d4480ccb 100644 --- a/x/logic/predicate/crypto_test.go +++ b/x/logic/predicate/crypto_test.go @@ -186,56 +186,65 @@ func TestEDDSAVerify(t *testing.T) { hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), -ecdsa_verify(PubKey, Msg, Sig, _).`, +ecdsa_verify(PubKey, Msg, Sig, encoding(octet)).`, + query: `verify.`, + wantResult: []types.TermResults{{}}, + wantSuccess: true, + }, + { // All good with hex encoding + program: `verify :- + hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), + hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), + ecdsa_verify(PubKey, '9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Sig, encoding(hex)).`, query: `verify.`, wantResult: []types.TermResults{{}}, wantSuccess: true, }, { // Wrong Msg program: `verify :- -hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), -hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9e', Msg), -hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), -ecdsa_verify(PubKey, Msg, Sig, _).`, + hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), + hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9e', Msg), + hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), + ecdsa_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantSuccess: false, }, { // Wrong public key program: `verify :- -hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5b5b', PubKey), -hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), -hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), -ecdsa_verify(PubKey, Msg, Sig, _).`, + hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5b5b', PubKey), + hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), + hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), + ecdsa_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantSuccess: false, wantError: fmt.Errorf("ecdsa_verify/4: failed verify signature: ed25519: bad public key length: 33"), }, { // Wrong signature program: `verify :- -hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), -hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), -hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff', Sig), -ecdsa_verify(PubKey, Msg, Sig, _).`, + hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), + hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), + hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff', Sig), + ecdsa_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantSuccess: false, }, { // All good program: `verify :- -hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), -hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3be', Msg), -hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e47', Sig), -secp_verify(PubKey, Msg, Sig, _).`, + hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), + hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3be', Msg), + hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e47', Sig), + secp_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantResult: []types.TermResults{{}}, wantSuccess: true, }, { // Invalid secp signature program: `verify :- -hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), -hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), -hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), -secp_verify(PubKey, Msg, Sig, _).`, + hex_bytes('53167ac3fc4b720daa45b04fc73fe752578fa23a10048422d6904b7f4f7bba5a', PubKey), + hex_bytes('9b038f8ef6918cbb56040dfda401b56bb1ce79c472e7736e8677758c83367a9d', Msg), + hex_bytes('889bcfd331e8e43b5ebf430301dffb6ac9e2fce69f6227b43552fe3dc8cc1ee00c1cc53452a8712e9d5f80086dff8cf4999c1b93ed6c6e403c09334cb61ddd0b', Sig), + secp_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantSuccess: false, wantError: fmt.Errorf("secp_verify/4: failed verify signature: failed decode PEM public key"), @@ -243,10 +252,10 @@ secp_verify(PubKey, Msg, Sig, _).`, { // Wrong msg program: `verify :- -hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), -hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3bf', Msg), -hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e47', Sig), -secp_verify(PubKey, Msg, Sig, _).`, + hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), + hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3bf', Msg), + hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e47', Sig), + secp_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantResult: []types.TermResults{{}}, wantSuccess: false, @@ -254,10 +263,10 @@ secp_verify(PubKey, Msg, Sig, _).`, { // Wrong signature program: `verify :- -hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), -hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3be', Msg), -hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e48', Sig), -secp_verify(PubKey, Msg, Sig, _).`, + hex_bytes('2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414545386843612b527835565547393835506666565870433478446643660d0a6b75747a4c4b4d49586e6c383735734543525036654b4b79704c70514564564752526b3551396f687a64766b49392b583850756d666766356d673d3d0d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d', PubKey), + hex_bytes('e50c26e89f734b2ee12041ff27874c901891f74a0f0cf470333312a3034ce3be', Msg), + hex_bytes('30450220099e6f9dd218e0e304efa7a4224b0058a8e3aec73367ec239bee4ed8ed7d85db022100b504d3d0d2e879b04705c0e5a2b40b0521a5ab647ea207bd81134e1a4eb79e48', Sig), + secp_verify(PubKey, Msg, Sig, encoding(octet)).`, query: `verify.`, wantResult: []types.TermResults{{}}, wantSuccess: false, diff --git a/x/logic/predicate/util.go b/x/logic/predicate/util.go index 009483cb..0bbe198a 100644 --- a/x/logic/predicate/util.go +++ b/x/logic/predicate/util.go @@ -1,6 +1,7 @@ package predicate import ( + "encoding/hex" "fmt" "sort" @@ -64,17 +65,81 @@ func BytesToList(bt []byte) engine.Term { return engine.List(terms...) } -func TermToBytes(term engine.Term, env *engine.Env) ([]byte, error) { - switch b := env.Resolve(term).(type) { +func OptionsContains(atom engine.Atom, options engine.Term, env *engine.Env) (engine.Compound, error) { + switch opts := env.Resolve(options).(type) { case engine.Compound: - if b.Arity() != 2 || b.Functor().String() != "." { - return nil, fmt.Errorf("term should be a List, give %T", b) + if opts.Functor() == atom { + return opts, nil + } else if opts.Arity() == 2 && opts.Functor().String() == "." { + + iter := engine.ListIterator{List: opts, Env: env} + + for iter.Next() { + opt := env.Resolve(iter.Current()) + term, err := OptionsContains(atom, opt, env) + if err != nil { + return nil, err + } + if term != nil { + return term, nil + } + } } - iter := engine.ListIterator{List: b, Env: env} + return nil, nil + case nil: + return nil, nil + default: + return nil, fmt.Errorf("invalid options term, should be compound, give %T", opts) + } +} + +// TermToBytes try to convert a term to native golang []byte. +// By default, if no encoding options is given the term is considered as hexadecimal value. +// Available encoding option is `text`, `octet` and `hex` (default value) +func TermToBytes(term, options engine.Term, env *engine.Env) ([]byte, error) { + encoding, err := OptionsContains(AtomEncoding, options, env) + if err != nil { + return nil, err + } + + if encoding == nil { + encoding = env.Resolve(AtomEncoding.Apply(engine.NewAtom("hex"))).(engine.Compound) + } + + if encoding.Arity() != 1 { + return nil, fmt.Errorf("invalid arity for encoding option, should be 1") + } - return ListToBytes(iter, env) + switch enc := env.Resolve(encoding.Arg(0)).(type) { + case engine.Atom: + switch enc.String() { + case "octet": + switch b := env.Resolve(term).(type) { + case engine.Compound: + if b.Arity() != 2 || b.Functor().String() != "." { + return nil, fmt.Errorf("term should be a List, give %T", b) + } + iter := engine.ListIterator{List: b, Env: env} + + return ListToBytes(iter, env) + default: + return nil, fmt.Errorf("invalid term type: %T, should be a List", term) + } + case "hex": + switch b := env.Resolve(term).(type) { + case engine.Atom: + src := []byte(b.String()) + result := make([]byte, hex.DecodedLen(len(src))) + _, err := hex.Decode(result, src) + return result, err + default: + return nil, fmt.Errorf("invalid term type: %T, should be String", term) + } + default: + return nil, fmt.Errorf("invalid encoding option: %s, valid value are 'hex' or 'octet'", enc.String()) + } default: - return nil, fmt.Errorf("invalid term type: %T, should be Atom or List", term) + return nil, fmt.Errorf("invalid given options") } }