Skip to content

Commit

Permalink
Support Anoma stdlib APIs sign and verify (#2788)
Browse files Browse the repository at this point in the history
This PR adds support for the Anoma stdlib `sign` and `verify` APIs.

```
builtin anoma-sign
axiom anomaSign : {A : Type}
  -- message to sign
  -> A
  -- secret key
  -> Nat
  -- signed message
  -> Nat;

builtin anoma-verify
axiom anomaVerify : {A : Type}
  -- signed message to verify 
  -> Nat 
  -- public key
  -> Nat 
  -- message with signature removed
  -> A;
```

These correspond to the
[`sign`](https://hexdocs.pm/enacl/enacl.html#sign-2) and
[`sign_open`](https://hexdocs.pm/enacl/enacl.html#sign_open-2) APIs from
libsodium respectively.

If signature verification fails in `anomaVerify`, the Anoma program
exits. We copy this behaviour in the evaluator by throwing an error in
this case.

## Notes

The Haskell Ed25519 library does not support `sign_open`. Its
verification function returns Bool, i.e it checks that the signature is
valid. The signed message is simply the concatenation of the signature
(64 bytes) and the original message so I added a function to remove the
signature from a signed message.
  • Loading branch information
paulcadman authored May 28, 2024
1 parent d233bbd commit e30905a
Show file tree
Hide file tree
Showing 29 changed files with 269 additions and 7 deletions.
26 changes: 26 additions & 0 deletions src/Juvix/Compiler/Builtins/Anoma.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,29 @@ registerAnomaVerifyDetached f = do
((ftype ==% (u <>--> nat --> decodeT --> nat --> bool_)) freeVars)
(error "anomaVerifyDetached must be of type {A : Type} -> Nat -> A -> Nat -> Bool")
registerBuiltin BuiltinAnomaVerifyDetached (f ^. axiomName)

registerAnomaSign :: (Members '[Builtins, NameIdGen] r) => AxiomDef -> Sem r ()
registerAnomaSign f = do
let ftype = f ^. axiomType
u = ExpressionUniverse smallUniverseNoLoc
l = getLoc f
dataT <- freshVar l "dataT"
nat <- getBuiltinName (getLoc f) BuiltinNat
let freeVars = HashSet.fromList [dataT]
unless
((ftype ==% (u <>--> dataT --> nat --> nat)) freeVars)
(error "anomaSign must be of type {A : Type} -> A -> Nat -> Nat")
registerBuiltin BuiltinAnomaSign (f ^. axiomName)

registerAnomaVerify :: (Members '[Builtins, NameIdGen] r) => AxiomDef -> Sem r ()
registerAnomaVerify f = do
let ftype = f ^. axiomType
u = ExpressionUniverse smallUniverseNoLoc
l = getLoc f
dataT <- freshVar l "dataT"
nat <- getBuiltinName (getLoc f) BuiltinNat
let freeVars = HashSet.fromList [dataT]
unless
((ftype ==% (u <>--> nat --> nat --> dataT)) freeVars)
(error "anomaVerify must be of type {A : Type} -> Nat -> Nat -> A")
registerBuiltin BuiltinAnomaVerify (f ^. axiomName)
4 changes: 4 additions & 0 deletions src/Juvix/Compiler/Concrete/Data/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ data BuiltinAxiom
| BuiltinAnomaEncode
| BuiltinAnomaDecode
| BuiltinAnomaVerifyDetached
| BuiltinAnomaSign
| BuiltinAnomaVerify
| BuiltinPoseidon
| BuiltinEcOp
| BuiltinRandomEcPoint
Expand Down Expand Up @@ -229,6 +231,8 @@ instance Pretty BuiltinAxiom where
BuiltinAnomaEncode -> Str.anomaEncode
BuiltinAnomaDecode -> Str.anomaDecode
BuiltinAnomaVerifyDetached -> Str.anomaVerifyDetached
BuiltinAnomaSign -> Str.anomaSign
BuiltinAnomaVerify -> Str.anomaVerify
BuiltinPoseidon -> Str.cairoPoseidon
BuiltinEcOp -> Str.cairoEcOp
BuiltinRandomEcPoint -> Str.cairoRandomEcPoint
Expand Down
20 changes: 20 additions & 0 deletions src/Juvix/Compiler/Core/Evaluator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ geval opts herr ctx env0 = eval' env0
OpAnomaEncode -> anomaEncodeOp
OpAnomaDecode -> anomaDecodeOp
OpAnomaVerifyDetached -> anomaVerifyDetachedOp
OpAnomaSign -> anomaSignOp
OpAnomaVerify -> anomaVerifyOp
OpPoseidonHash -> poseidonHashOp
OpEc -> ecOp
OpRandomEcPoint -> randomEcPointOp
Expand Down Expand Up @@ -367,6 +369,24 @@ geval opts herr ctx env0 = eval' env0
err "unsupported builtin operation: OpAnomaVerifyDetached"
{-# INLINE anomaVerifyDetachedOp #-}

anomaSignOp :: [Node] -> Node
anomaSignOp = checkApply $ \arg1 arg2 ->
if
| opts ^. evalOptionsNormalize || opts ^. evalOptionsNoFailure ->
mkBuiltinApp' OpAnomaSign (eval' env <$> [arg1, arg2])
| otherwise ->
err "unsupported builtin operation: OpAnomaSign"
{-# INLINE anomaSignOp #-}

anomaVerifyOp :: [Node] -> Node
anomaVerifyOp = checkApply $ \arg1 arg2 ->
if
| opts ^. evalOptionsNormalize || opts ^. evalOptionsNoFailure ->
mkBuiltinApp' OpAnomaVerify (eval' env <$> [arg1, arg2])
| otherwise ->
err "unsupported builtin operation: OpAnomaVerify"
{-# INLINE anomaVerifyOp #-}

poseidonHashOp :: [Node] -> Node
poseidonHashOp = unary $ \arg ->
if
Expand Down
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Core/Extra/Utils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,8 @@ builtinOpArgTypes = \case
OpAnomaEncode -> [mkDynamic']
OpAnomaDecode -> [mkDynamic']
OpAnomaVerifyDetached -> [mkDynamic', mkDynamic', mkDynamic']
OpAnomaSign -> [mkDynamic', mkDynamic']
OpAnomaVerify -> [mkDynamic', mkDynamic']
OpPoseidonHash -> [mkDynamic']
OpEc -> [mkDynamic', mkTypeField', mkDynamic']
OpRandomEcPoint -> []
Expand Down
15 changes: 14 additions & 1 deletion src/Juvix/Compiler/Core/Language/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ data BuiltinOp
| OpAnomaEncode
| OpAnomaDecode
| OpAnomaVerifyDetached
| OpAnomaSign
| OpAnomaVerify
| OpPoseidonHash
| OpEc
| OpRandomEcPoint
Expand Down Expand Up @@ -77,6 +79,8 @@ builtinOpArgsNum = \case
OpAnomaEncode -> 1
OpAnomaDecode -> 1
OpAnomaVerifyDetached -> 3
OpAnomaSign -> 2
OpAnomaVerify -> 2
OpPoseidonHash -> 1
OpEc -> 3
OpRandomEcPoint -> 0
Expand Down Expand Up @@ -117,6 +121,8 @@ builtinIsFoldable = \case
OpAnomaEncode -> False
OpAnomaDecode -> False
OpAnomaVerifyDetached -> False
OpAnomaSign -> False
OpAnomaVerify -> False
OpPoseidonHash -> False
OpEc -> False
OpRandomEcPoint -> False
Expand All @@ -134,4 +140,11 @@ builtinsCairo :: [BuiltinOp]
builtinsCairo = [OpPoseidonHash, OpEc, OpRandomEcPoint]

builtinsAnoma :: [BuiltinOp]
builtinsAnoma = [OpAnomaGet, OpAnomaEncode, OpAnomaDecode, OpAnomaVerifyDetached]
builtinsAnoma =
[ OpAnomaGet,
OpAnomaEncode,
OpAnomaDecode,
OpAnomaVerifyDetached,
OpAnomaSign,
OpAnomaVerify
]
8 changes: 8 additions & 0 deletions src/Juvix/Compiler/Core/Pretty/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ instance PrettyCode BuiltinOp where
OpAnomaEncode -> return primAnomaEncode
OpAnomaDecode -> return primAnomaDecode
OpAnomaVerifyDetached -> return primAnomaVerifyDetached
OpAnomaSign -> return primAnomaSign
OpAnomaVerify -> return primAnomaVerify
OpPoseidonHash -> return primPoseidonHash
OpEc -> return primEc
OpRandomEcPoint -> return primRandomEcPoint
Expand Down Expand Up @@ -813,6 +815,12 @@ primAnomaDecode = primitive Str.anomaDecode
primAnomaVerifyDetached :: Doc Ann
primAnomaVerifyDetached = primitive Str.anomaVerifyDetached

primAnomaSign :: Doc Ann
primAnomaSign = primitive Str.anomaSign

primAnomaVerify :: Doc Ann
primAnomaVerify = primitive Str.anomaVerify

primPoseidonHash :: Doc Ann
primPoseidonHash = primitive Str.cairoPoseidon

Expand Down
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Core/Transformation/ComputeTypeInfo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ computeNodeTypeInfo md = umapL go
OpAnomaEncode -> Info.getNodeType node
OpAnomaDecode -> Info.getNodeType node
OpAnomaVerifyDetached -> Info.getNodeType node
OpAnomaSign -> Info.getNodeType node
OpAnomaVerify -> Info.getNodeType node
OpPoseidonHash -> case _builtinAppArgs of
[arg] -> Info.getNodeType arg
_ -> error "incorrect poseidon builtin application"
Expand Down
31 changes: 30 additions & 1 deletion src/Juvix/Compiler/Core/Translation/FromInternal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,8 @@ goAxiomInductive a = whenJust (a ^. Internal.axiomBuiltin) builtinInductive
Internal.BuiltinAnomaEncode -> return ()
Internal.BuiltinAnomaDecode -> return ()
Internal.BuiltinAnomaVerifyDetached -> return ()
Internal.BuiltinAnomaSign -> return ()
Internal.BuiltinAnomaVerify -> return ()
Internal.BuiltinPoseidon -> return ()
Internal.BuiltinEcOp -> return ()
Internal.BuiltinRandomEcPoint -> return ()
Expand Down Expand Up @@ -713,7 +715,6 @@ goAxiomDef a = maybe goAxiomNotBuiltin builtinBody (a ^. Internal.axiomBuiltin)
mkSmallUniv
(mkLambda' natType (mkBuiltinApp' OpAnomaDecode [mkVar' 0]))
)
-- ((ftype ==% (u <>--> nat --> decodeT --> nat --> bool_)) freeVars)
Internal.BuiltinAnomaVerifyDetached -> do
natType <- getNatType
registerAxiomDef
Expand All @@ -730,6 +731,32 @@ goAxiomDef a = maybe goAxiomNotBuiltin builtinBody (a ^. Internal.axiomBuiltin)
)
)
)
Internal.BuiltinAnomaSign -> do
natType <- getNatType
registerAxiomDef
( mkLambda'
mkSmallUniv
( mkLambda'
(mkVar' 0)
( mkLambda'
natType
(mkBuiltinApp' OpAnomaSign [mkVar' 1, mkVar' 0])
)
)
)
Internal.BuiltinAnomaVerify -> do
natType <- getNatType
registerAxiomDef
( mkLambda'
mkSmallUniv
( mkLambda'
natType
( mkLambda'
natType
(mkBuiltinApp' OpAnomaVerify [mkVar' 1, mkVar' 0])
)
)
)
Internal.BuiltinPoseidon -> do
psName <- getPoseidonStateName
psSym <- getPoseidonStateSymbol
Expand Down Expand Up @@ -1137,6 +1164,8 @@ goApplication a = do
Just Internal.BuiltinAnomaEncode -> app
Just Internal.BuiltinAnomaDecode -> app
Just Internal.BuiltinAnomaVerifyDetached -> app
Just Internal.BuiltinAnomaSign -> app
Just Internal.BuiltinAnomaVerify -> app
Just Internal.BuiltinPoseidon -> app
Just Internal.BuiltinEcOp -> app
Just Internal.BuiltinRandomEcPoint -> app
Expand Down
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Core/Translation/Stripped/FromCore.hs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ fromCore fsize tab =
BuiltinAnomaEncode -> False
BuiltinAnomaDecode -> False
BuiltinAnomaVerifyDetached -> False
BuiltinAnomaSign -> False
BuiltinAnomaVerify -> False
BuiltinPoseidon -> False
BuiltinEcOp -> False
BuiltinRandomEcPoint -> False
Expand Down
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Internal/Translation/FromConcrete.hs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,8 @@ registerBuiltinAxiom d = \case
BuiltinAnomaEncode -> registerAnomaEncode d
BuiltinAnomaDecode -> registerAnomaDecode d
BuiltinAnomaVerifyDetached -> registerAnomaVerifyDetached d
BuiltinAnomaSign -> registerAnomaSign d
BuiltinAnomaVerify -> registerAnomaVerify d
BuiltinPoseidon -> registerPoseidon d
BuiltinEcOp -> registerEcOp d
BuiltinRandomEcPoint -> registerRandomEcPoint d
Expand Down
9 changes: 9 additions & 0 deletions src/Juvix/Compiler/Nockma/Encoding/Ed25519.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Juvix.Compiler.Nockma.Encoding.Ed25519 where

import Data.ByteString qualified as BS
import Juvix.Prelude.Base

-- | Remove the Ed25519 signature from a signed message.
-- The signaure of an Ed25519 message is the first 64 bytes of the signed message.
removeSignature :: ByteString -> ByteString
removeSignature = BS.drop 64
24 changes: 23 additions & 1 deletion src/Juvix/Compiler/Nockma/Evaluator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Crypto.Sign.Ed25519
import Data.HashMap.Strict qualified as HashMap
import Juvix.Compiler.Nockma.Encoding
import Juvix.Compiler.Nockma.Encoding qualified as Encoding
import Juvix.Compiler.Nockma.Encoding.Ed25519
import Juvix.Compiler.Nockma.Evaluator.Error
import Juvix.Compiler.Nockma.Evaluator.Options
import Juvix.Compiler.Nockma.Evaluator.Storage
Expand Down Expand Up @@ -239,7 +240,13 @@ evalProfile inistack initerm =
TermCell {} -> throwDecodingFailed args' DecodingErrorExpectedAtom
StdlibVerifyDetached -> case args' of
TCell (TermAtom sig) (TCell (TermAtom message) (TermAtom pubKey)) -> goVerifyDetached sig message pubKey
_ -> error "expected a term of the form [signature (atom) message (term) public_key (atom)]"
_ -> error "expected a term of the form [signature (atom) message (encoded term) public_key (atom)]"
StdlibSign -> case args' of
TCell (TermAtom message) (TermAtom privKey) -> goSign message privKey
_ -> error "expected a term of the form [message (encoded term) private_key (atom)]"
StdlibVerify -> case args' of
TCell (TermAtom signedMessage) (TermAtom pubKey) -> goVerify signedMessage pubKey
_ -> error "expected a term of the form [signedMessage (atom) public_key (atom)]"
where
goVerifyDetached :: Atom a -> Atom a -> Atom a -> Sem r (Term a)
goVerifyDetached sigT messageT pubKeyT = do
Expand All @@ -249,6 +256,21 @@ evalProfile inistack initerm =
let res = dverify pubKey message sig
return (TermAtom (nockBool res))

goSign :: Atom a -> Atom a -> Sem r (Term a)
goSign messageT privKeyT = do
privKey <- SecretKey <$> atomToByteString privKeyT
message <- atomToByteString messageT
res <- byteStringToAtom (sign privKey message)
return (TermAtom res)

goVerify :: Atom a -> Atom a -> Sem r (Term a)
goVerify signedMessageT pubKeyT = do
pubKey <- PublicKey <$> atomToByteString pubKeyT
signedMessage <- atomToByteString signedMessageT
if
| verify pubKey signedMessage -> TermAtom <$> byteStringToAtom (removeSignature signedMessage)
| otherwise -> throwVerificationFailed signedMessageT pubKeyT

goAutoConsCell :: AutoConsCell a -> Sem r (Term a)
goAutoConsCell c = do
let w a =
Expand Down
35 changes: 35 additions & 0 deletions src/Juvix/Compiler/Nockma/Evaluator/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ data NockEvalError a
ErrAssignmentNotFound Text
| ErrKeyNotInStorage (KeyNotInStorage a)
| ErrDecodingFailed (DecodingFailed a)
| ErrVerificationFailed (VerificationFailed a)

newtype GenericNockEvalError = GenericNockEvalError
{ _genericNockEvalErrorMessage :: AnsiText
Expand Down Expand Up @@ -66,6 +67,12 @@ data DecodingFailed a = DecodingFailed
_decodingFailedReason :: DecodingError
}

data VerificationFailed a = VerificationFailed
{ _verificationFailedCtx :: EvalCtx,
_verificationFailedMessage :: Atom a,
_verificationFailedPublicKey :: Atom a
}

throwInvalidNockOp :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Atom a -> Sem r x
throwInvalidNockOp a = do
ctx <- ask
Expand Down Expand Up @@ -128,6 +135,17 @@ throwDecodingFailed a e = do
_decodingFailedReason = e
}

throwVerificationFailed :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Atom a -> Atom a -> Sem r x
throwVerificationFailed m k = do
ctx <- ask
throw $
ErrVerificationFailed
VerificationFailed
{ _verificationFailedCtx = ctx,
_verificationFailedMessage = m,
_verificationFailedPublicKey = k
}

instance PrettyCode NoStack where
ppCode _ = return "Missing stack"

Expand Down Expand Up @@ -178,6 +196,22 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (DecodingFailed a) where
r <- ppCode _decodingFailedReason
return (ctx <> "Decoding the term" <+> t <+> "failed with reason:" <> line <> r)

instance (PrettyCode a, NockNatural a) => PrettyCode (VerificationFailed a) where
ppCode VerificationFailed {..} = do
m <- ppCode _verificationFailedMessage
ctx <- ppCtx _verificationFailedCtx
k <- ppCode _verificationFailedPublicKey
return
( ctx
<> "Signature verification failed for message atom:"
<> line
<> m
<> line
<> "and public key atom:"
<> line
<> k
)

instance (PrettyCode a, NockNatural a) => PrettyCode (NockEvalError a) where
ppCode = \case
ErrInvalidPath e -> ppCode e
Expand All @@ -188,3 +222,4 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (NockEvalError a) where
ErrAssignmentNotFound e -> return (pretty e)
ErrKeyNotInStorage e -> ppCode e
ErrDecodingFailed e -> ppCode e
ErrVerificationFailed e -> ppCode e
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Nockma/StdlibFunction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ stdlibPath = \case
StdlibEncode -> [nock| [9 22 0 3] |]
StdlibDecode -> [nock| [9 94 0 3] |]
StdlibVerifyDetached -> [nock| [9 22 0 1] |]
StdlibSign -> [nock| [9 10 0 1] |]
StdlibVerify -> [nock| [9 4 0 1] |]
4 changes: 4 additions & 0 deletions src/Juvix/Compiler/Nockma/StdlibFunction/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ instance Pretty StdlibFunction where
StdlibEncode -> "encode"
StdlibDecode -> "decode"
StdlibVerifyDetached -> "verify-detached"
StdlibSign -> "sign"
StdlibVerify -> "verify"

data StdlibFunction
= StdlibDec
Expand All @@ -31,6 +33,8 @@ data StdlibFunction
| StdlibEncode
| StdlibDecode
| StdlibVerifyDetached
| StdlibSign
| StdlibVerify
deriving stock (Show, Lift, Eq, Bounded, Enum, Generic)

instance Hashable StdlibFunction
Expand Down
Loading

0 comments on commit e30905a

Please sign in to comment.