Zulip Chat Archive

Stream: new members

Topic: How to deal with equalities over `BitVec.cast`


Ayhon (Apr 24 2025 at 08:30):

EDIT: subst does not suffice if the equality is over expressions, not variables.

I'm running into an issue when trying to proceed with a proof over BitVecs. I try to perform a rewriting on a term which appears both in a expression and in the type of the expression, and Lean complains that the motive used for the rewriting is not type correct. I've managed to reduce the instance of this problem to the following example.

example
  (f: (n:Nat)  BitVec n)
  (res foo: Nat)
  (h: foo = res)
: f res = (f foo).cast h
:= by
  sorry

I was wondering what was the general way to deal with this. I've tried using an extensionality lemma over BitVec to remove the use of BitVec.cast with BitVec.getElem_cast and then congr to reduce the goal to res = foo but I'm left dealing with HEqs which I don't know how to handle.

My attempt so far

Ayhon (Apr 24 2025 at 08:41):

While making sure that my examples worked in the playground, I thought of using aesop to close the goal, and discovered that I could use subst h to close the goal. This doesn't translate to the original proof I had this issue over, however, since the Nat terms are not variables, but results of an expression.

I adapted my example to include an intermediate function g which prevents subst from working.

example
  (f: (n:Nat)  BitVec n)
  (g: Nat  Nat)
  (res foo: Nat)
  (h: g foo = g res)
: f (g res) = (f (g foo)).cast h
:= by sorry

This prevents aesop? from finding a proof from the start, but still allows it to finish the proof when using BitVec.ext and congr, using simp_all only [heq_eq_eq] to prove HEq a b with a: i < g res and b: i < g foo. I'm not sure what this tactic is doing though, and how it's able to close the goal.

My attempt so far

Robin Arnez (Apr 24 2025 at 09:43):

What about

example (f : (n : Nat)  BitVec n) (g : Nat  Nat) (res foo : Nat) (h : g foo = g res) :
    f (g res) = (f (g foo)).cast h := by
  rw [BitVec.toNat_eq]
  simp only [BitVec.toNat_cast]
  rw [h]

Or is that also not what you're really dealing with?

Ayhon (Apr 24 2025 at 09:56):

Translating the equality to work over Nat instead of BitVec does away with the issue. Now I'm encountering another problem where rw will perform the rewrite but simp will not, but this is a separate concern (this is a problem because I have a bunch of rewritings I'd like to do, and manually figuring out which rewritings I have to take is not what I'd like to do. I'd like to just simp [*]), so if I don't manage to figure it out on my own I'll open a new topic.

Thanks!

Notification Bot (Apr 24 2025 at 09:58):

Ayhon has marked this topic as resolved.

Robin Arnez (Apr 24 2025 at 09:59):

What about

example (f : (n : Nat)  BitVec n) (g : Nat  Nat) (res foo : Nat) (h : g foo = g res) :
    f (g res) = (f (g foo)).cast h := by
  rw [BitVec.toNat_eq]
  simp only [BitVec.toNat_cast]
  let f' := fun n => (f n).toNat
  change f' (g res) = f' (g foo)
  simp [h]

Robin Arnez (Apr 24 2025 at 09:59):

although I'm not sure whether that works too well with simp [*]

Notification Bot (Apr 24 2025 at 10:03):

Ayhon has marked this topic as unresolved.

Ayhon (Apr 24 2025 at 10:10):

I've been trying a bit more in the larger proof I'm working with, and it seems like my previous example does not do it enough justice. I have a bunch of equalities over BitVec n which I'm not able to rewrite, probably because of similar issues. With the toNat_eq trick I'm able to perform some of the rewrites, by removing the equality over dependent types, but I'm unable to apply other rewritings in nested expressions.

The actual usecase I'm dealing with is the following goal

-- ...
new_s6 : Std.Slice Bool  Aeneas.Std.Array Bool 1600#usize
new_s6_post :  (s : Std.Slice Bool), (new_s6 s) = (if h : (s).length = 1600#usize then ⟨↑s, h else mk_s5 s5)
-- ...
 (Spec.Keccak.P 6 24 (BitVec.cast  ((new_s6 s6)).toBitVec)).toList.toBitVec.toNat =
  (absorb (BitVec.cast  (s).toBitVec)
      (Array.chunks_exact (r)
        ((rest ++ suffix).toArray ++ Specpad10*1» (r) ((rest).length + (suffix).length)))).toNat

In this case I'm unable to perform rw [new_s6_post s6]. I'll try to minimize the example after lunch, but it can probably be represented in my previous example by wrapping everything in another function call. I guess I could tweak all such functions to do away with the BitVecs and use Nats instead, but that would require that I do so for a bunch of definitions, and at that point I'd perhaps rather change the definitions themselves.

Aaron Liu (Apr 24 2025 at 11:32):

Can you use conv?

Ayhon (Apr 24 2025 at 11:33):

I tried to, but it gives a similar error about the motive not begin type correct.

Full error message

Aaron Liu (Apr 24 2025 at 11:35):

Where did you conv to?

Aaron Liu (Apr 24 2025 at 11:36):

You should enter the BitVec.cast.

Aaron Liu (Apr 24 2025 at 11:37):

Then you can use docs#apply_dite to push the cast through.

Ayhon (Apr 24 2025 at 11:40):

Fair enough. I actually didn't go as deep as I originally wanted to, since conv gives me an error when I try to go past the BitVec.cast.

Aaron Liu (Apr 24 2025 at 11:40):

This is a deficiency in conv then

Aaron Liu (Apr 24 2025 at 11:40):

specifically, a missing conv lemma

Ayhon (Apr 24 2025 at 11:43):

I see. If I understood correctly, the issue is that when trying to perform the rewriting, rw by default abstracts away too much, which we can regulate with conv, but since I have not extended conv with the appropiate lemmas, it's unable to do so. Correct?

Could the previous case also be solved by using conv instead of applying BitVec.toNat_eq?

Ayhon (Apr 24 2025 at 11:43):

Furthermore, I didn't know about conv lemmas, I'll have a look at that

Aaron Liu (Apr 24 2025 at 11:44):

Wait, which argument are you entering? I might have misinterpreted the error message.

Aaron Liu (Apr 24 2025 at 11:44):

This would be so much easier with a #mwe

Aaron Liu (Apr 24 2025 at 11:46):

Ok clearly the problem is with List.toBitVec, which I can't find any reference for and so I must assume you defined that function. What's it's type?

Ayhon (Apr 24 2025 at 11:47):

Yes, I'll work on a MWE. It just takes me a bit of time since there's a bunch of definitions I have to abstract over.

I was trying to enter the first argument, which would perform the following (in my head)

(Spec.Keccak.P 6 24 (BitVec.cast  ((new_s6 s6)).toBitVec)).toList.toBitVec
 
(Spec.Keccak.P 6 24 (BitVec.cast  ((new_s6 s6)).toBitVec)).toList

Aaron Liu (Apr 24 2025 at 11:47):

What's the type of List.toBitVec?

Ayhon (Apr 24 2025 at 11:48):

Aaron Liu said:

Ok clearly the problem is with List.toBitVec, which I can't find any reference for and so I must assume you defined that function. What's it's type?

Yes, sorry, this whole situation is happening inside a project with a bunch of definitions. This one in particular

 abbrev List.toBitVec(self: List Bool): BitVec self.length := BitVec.ofBoolListLE self

Ayhon (Apr 24 2025 at 11:48):

And BitVec.ofBoolListLE is in Init. I just wanted the postfix notation

Aaron Liu (Apr 24 2025 at 11:49):

You'll probably have to write an explicit substitution motive then

Ayhon (Apr 24 2025 at 12:00):

I'm unsure of how to do this. Looking over at the code in conv, is this the part specified after the in? I can't see anything in the page about conv in TPiL, but the language reference makes it seem to me that it's not its purpose.

Aaron Liu (Apr 24 2025 at 12:01):

Like refine Eq.rec (motive := fun a h => explicit_motive) ?_ prf

Ayhon (Apr 24 2025 at 12:01):

Ah, I see, fully manually

Aaron Liu (Apr 24 2025 at 12:03):

The reason is you have to introduce a cast and rewrite half of it, which is probably too complicated for existing tactics.

Ayhon (Apr 24 2025 at 12:09):

Sorry, I have difficulty following with this last part. I'll try to reduce to a MWE to make it easier for me to follow.

Also, I see no reference to conv lemmas. Are there any examples of custom conv lemmas anywhere I could have a look at?

Aaron Liu (Apr 24 2025 at 12:13):

I think conv uses the simp-congr lemmas

Ayhon (Apr 24 2025 at 13:02):

I was thinking that perhaps instead of changing the motive I could change the equality I wanted to use. Instead of rewriting ↑(new_s6 s6) = ... I could transform this one to BitVec.cast pf (↑(new_s6 s6)).toBitVec = BitVec.cast pf (...).toBitVec using congrArg.

However, congrArg (f := List.toBitVec) doesn't seem to work in this case.

Error message

However, this doesn't work because if a = b and f: (n: Nat) → BitVec n then f a : BitVec a, f b: BitVec b and so f a = f b is not type correct. I guess then that the congr lemma would be (h: a = b) → f a = (f b).cast h, but I see what I cannot define it as congr since it requires for the first function application to be the same.

With an auxiliary lemma, I've been able to perform the rewriting of (↑(new_s6 s6)).toBitVector.cast pf
by transforming

(new_s6 s6) = ...
    
((new_s6 s6)).toBitVector = (...).toBitVector.cast pf'
    
((new_s6 s6)).toBitVector.cast pf  = ((...).toBitVector.cast pf').cast pf

and then performing the rewriting.

However, the process was a bit involved. Ideally I'd like to be able to automate it, maybe by making more use of HEq? Would this scale?

Aaron Liu (Apr 24 2025 at 13:05):

I can think of how this can be automated

Ayhon (Apr 24 2025 at 13:19):

Aaron Liu said:

I can think of how this can be automated

Is this possible with the current tactics, or simply implementable as a new tactic?

Aaron Liu (Apr 24 2025 at 13:20):

I mean as a new tactic, but I guess you could patch together a curated simp set with some congr


Last updated: May 02 2025 at 03:31 UTC