12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- use std:: io:: Write ;
15
+ use std:: {
16
+ fs:: { File , OpenOptions } ,
17
+ io,
18
+ io:: Write ,
19
+ os:: unix:: io:: { AsRawFd , FromRawFd } ,
20
+ } ;
16
21
17
22
use alloy:: { primitives:: Bytes , sol_types:: SolValue } ;
18
- use anyhow:: { Context , Result } ;
23
+ use anyhow:: { ensure , Context , Result } ;
19
24
use clap:: Parser ;
20
25
use risc0_ethereum_contracts:: encode_seal;
21
26
use risc0_zkvm:: { default_prover, ExecutorEnv , ProverOpts , VerifierContext } ;
@@ -35,7 +40,10 @@ enum Command {
35
40
36
41
/// Run the CLI.
37
42
pub 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 ( ) {
39
47
Command :: Prove {
40
48
guest_binary_path,
41
49
input,
@@ -45,22 +53,19 @@ pub fn main() -> Result<()> {
45
53
) ?,
46
54
} ;
47
55
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
+
48
60
Ok ( ( ) )
49
61
}
50
62
51
63
/// 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 > > {
53
65
let elf = std:: fs:: read ( elf_path) . expect ( "failed to read guest ELF" ) ;
54
66
let ( journal, seal) = prove ( & elf, & input) ?;
55
67
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 ( ) )
64
69
}
65
70
66
71
/// 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>)> {
78
83
let seal = encode_seal ( & receipt) ?;
79
84
Ok ( ( journal, seal) )
80
85
}
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