1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- use std:: io:: Write ;
15+ use std:: {
16+ fs:: { File , OpenOptions } ,
17+ io,
18+ io:: Write ,
19+ os:: unix:: io:: { AsRawFd , FromRawFd } ,
20+ } ;
1621
1722use alloy:: { primitives:: Bytes , sol_types:: SolValue } ;
18- use anyhow:: { Context , Result } ;
23+ use anyhow:: { ensure , Context , Result } ;
1924use clap:: Parser ;
2025use risc0_ethereum_contracts:: encode_seal;
2126use risc0_zkvm:: { default_prover, ExecutorEnv , ProverOpts , VerifierContext } ;
@@ -35,7 +40,10 @@ enum Command {
3540
3641/// Run the CLI.
3742pub fn main ( ) -> Result < ( ) > {
38- match Command :: parse ( ) {
43+ // Take stdout is ensure no extra data is written to it.
44+ let mut stdout = take_stdout ( ) ?;
45+
46+ let output = match Command :: parse ( ) {
3947 Command :: Prove {
4048 guest_binary_path,
4149 input,
@@ -45,22 +53,19 @@ pub fn main() -> Result<()> {
4553 ) ?,
4654 } ;
4755
56+ // Forge test FFI calls expect hex encoded bytes sent to stdout
57+ write ! ( & mut stdout, "{}" , hex:: encode( output) ) . context ( "failed to write to stdout" ) ?;
58+ stdout. flush ( ) . context ( "failed to flush stdout" ) ?;
59+
4860 Ok ( ( ) )
4961}
5062
5163/// Prints on stdio the Ethereum ABI and hex encoded proof.
52- fn prove_ffi ( elf_path : String , input : Vec < u8 > ) -> Result < ( ) > {
64+ fn prove_ffi ( elf_path : String , input : Vec < u8 > ) -> Result < Vec < u8 > > {
5365 let elf = std:: fs:: read ( elf_path) . expect ( "failed to read guest ELF" ) ;
5466 let ( journal, seal) = prove ( & elf, & input) ?;
5567 let calldata = ( Bytes ( journal. into ( ) ) , Bytes ( seal. into ( ) ) ) ;
56- let output = hex:: encode ( calldata. abi_encode ( ) ) ;
57-
58- // Forge test FFI calls expect hex encoded bytes sent to stdout
59- print ! ( "{output}" ) ;
60- std:: io:: stdout ( )
61- . flush ( )
62- . context ( "failed to flush stdout buffer" ) ?;
63- Ok ( ( ) )
68+ Ok ( calldata. abi_encode ( ) )
6469}
6570
6671/// Generates journal and snark seal as a pair (`Vec<u8>`, `Vec<u8>)
@@ -78,3 +83,29 @@ fn prove(elf: &[u8], input: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
7883 let seal = encode_seal ( & receipt) ?;
7984 Ok ( ( journal, seal) )
8085}
86+
87+ /// "Takes" stdout such, returning a handle and ensuring no other code in this process can write to
88+ /// it. This is used to ensure that no additional data (e.g. log lines) is written to stdout, as
89+ /// any extra will cause a decoding failure.
90+ fn take_stdout ( ) -> Result < File > {
91+ let stdout = io:: stdout ( ) ;
92+ let mut handle = stdout. lock ( ) ;
93+ // Ensure all buffered data is written before redirection
94+ handle. flush ( ) ?;
95+
96+ let devnull = OpenOptions :: new ( ) . write ( true ) . open ( "/dev/null" ) ?;
97+
98+ unsafe {
99+ // Create a copy of stdout to use for our output.
100+ let dup_fd = libc:: dup ( handle. as_raw_fd ( ) ) ;
101+ ensure ! ( dup_fd >= 0 , "call to libc::dup failed: {}" , dup_fd) ;
102+ // Redirect stdout to the fd we opened for /dev/null
103+ let dup2_result = libc:: dup2 ( devnull. as_raw_fd ( ) , libc:: STDOUT_FILENO ) ;
104+ ensure ! (
105+ dup2_result >= 0 ,
106+ "call to libc::dup2 failed: {}" ,
107+ dup2_result
108+ ) ;
109+ Ok ( File :: from_raw_fd ( dup_fd) )
110+ }
111+ }
0 commit comments