1use std::path::{Path, PathBuf};
2
3use ant_protocol::pqc::api::{ml_dsa_65, MlDsaPublicKey, MlDsaSignature, MlDsaVariant};
4use futures_util::StreamExt;
5use serde::{Deserialize, Serialize};
6
7use crate::error::{Error, Result};
8use crate::node::binary::{extract_tar_gz, extract_zip, ProgressReporter};
9
10const GITHUB_REPO: &str = "WithAutonomi/ant-client";
11const CLI_BINARY_NAME: &str = "ant";
12const TAG_PREFIX: &str = "ant-cli-v";
13
14const SIGNING_CONTEXT: &[u8] = b"ant-release-v1";
16
17const SIGNATURE_SIZE: usize = 3309;
19
20const RELEASE_SIGNING_KEY: &[u8] = &[
26 0xb4, 0xcf, 0x2a, 0x24, 0x31, 0xd9, 0xb2, 0x3a, 0xab, 0xe9, 0x5e, 0xfc, 0xbc, 0xf3, 0xb1, 0x1f,
27 0x4e, 0x50, 0x0a, 0x46, 0xeb, 0x83, 0xfc, 0x6f, 0x0f, 0x89, 0x41, 0x00, 0x1b, 0x52, 0xde, 0xdc,
28 0xb5, 0xc9, 0x07, 0xed, 0x72, 0x3a, 0xe1, 0xa9, 0x82, 0xe1, 0xfc, 0xff, 0xab, 0xce, 0x9f, 0x7d,
29 0xab, 0xe2, 0x57, 0x92, 0xe0, 0xf2, 0xac, 0xa1, 0x41, 0xd3, 0x74, 0x95, 0x41, 0xd1, 0xac, 0x22,
30 0xb1, 0xbb, 0x5c, 0xf4, 0x02, 0x0b, 0x73, 0x85, 0xfd, 0x56, 0x75, 0x0d, 0x5c, 0x38, 0xe1, 0x2d,
31 0xe0, 0x15, 0x4f, 0xbf, 0x40, 0xeb, 0xf1, 0x0d, 0x8f, 0x39, 0x32, 0xeb, 0x80, 0xa7, 0x2e, 0x77,
32 0x3e, 0x54, 0xe3, 0x3d, 0x62, 0xae, 0xe7, 0x09, 0x5e, 0xfb, 0xdc, 0xaa, 0x07, 0xdc, 0xe1, 0x08,
33 0x96, 0xbb, 0x4b, 0xa0, 0x2e, 0x05, 0x2d, 0xee, 0xeb, 0x9a, 0x1a, 0xae, 0xde, 0xe9, 0x2c, 0xf8,
34 0x2f, 0x43, 0x6c, 0x78, 0x4b, 0xde, 0xef, 0x91, 0x8b, 0x94, 0x9d, 0x4f, 0x81, 0x05, 0xcc, 0xf0,
35 0x98, 0xce, 0xce, 0x67, 0x54, 0xac, 0xde, 0xcd, 0x26, 0x9e, 0x84, 0xf4, 0x88, 0xb2, 0x1a, 0x3e,
36 0x93, 0x2e, 0xff, 0xa8, 0x45, 0x95, 0xd1, 0xd0, 0xb1, 0x6c, 0x3c, 0x1e, 0xef, 0x3d, 0xe3, 0xf2,
37 0x73, 0xe2, 0xf6, 0xb7, 0xf9, 0x3f, 0x10, 0x0d, 0x3c, 0xde, 0x28, 0x94, 0x07, 0xef, 0x24, 0x70,
38 0xc4, 0x5a, 0x0a, 0x67, 0xbb, 0x0f, 0x4f, 0x5c, 0x2b, 0xd8, 0x02, 0x05, 0xa5, 0x98, 0x03, 0x5d,
39 0x8f, 0xc0, 0x4a, 0x84, 0xe9, 0xea, 0xac, 0x13, 0xdf, 0x69, 0xfc, 0x1e, 0xcf, 0xb6, 0x88, 0xba,
40 0x99, 0x30, 0xbc, 0x7a, 0xb8, 0x9d, 0x3d, 0x62, 0x3b, 0x33, 0x19, 0xbb, 0x3a, 0x2c, 0x2b, 0xa0,
41 0x5a, 0xb0, 0x8f, 0x9e, 0x10, 0x81, 0xb9, 0x12, 0x54, 0x81, 0xf8, 0xe2, 0x91, 0xb2, 0xe7, 0xe6,
42 0x9c, 0x11, 0xeb, 0x49, 0x64, 0x3a, 0x25, 0xd6, 0x53, 0x2e, 0xdf, 0xfc, 0x14, 0x32, 0x65, 0xcc,
43 0x87, 0xdb, 0xfd, 0xbb, 0x81, 0xa8, 0x50, 0xcc, 0xb4, 0x31, 0x0c, 0x70, 0xf3, 0xb6, 0x15, 0x8a,
44 0x50, 0x80, 0xad, 0xb1, 0xb0, 0x10, 0x2b, 0x67, 0x33, 0xf5, 0xf6, 0x36, 0x35, 0x5f, 0xa7, 0xd2,
45 0x81, 0xd6, 0x75, 0xa1, 0x18, 0x15, 0xbe, 0x1d, 0x5e, 0x33, 0x8e, 0x98, 0xdd, 0x45, 0x0f, 0x0c,
46 0x0f, 0x0d, 0x8b, 0x3f, 0x97, 0x11, 0x21, 0x2e, 0xa0, 0x5e, 0xfe, 0x70, 0x09, 0xa7, 0x14, 0x30,
47 0xa3, 0x01, 0x2d, 0x18, 0x2b, 0x8f, 0x19, 0x75, 0x54, 0x1f, 0xd8, 0xee, 0x66, 0x06, 0x7b, 0x9d,
48 0x7d, 0xb2, 0xae, 0x14, 0xe6, 0x51, 0x19, 0xc2, 0x45, 0x2e, 0x7e, 0x11, 0xd9, 0x7b, 0x16, 0x8e,
49 0xae, 0x17, 0xdb, 0x1b, 0x24, 0x90, 0xcd, 0xed, 0x94, 0xf9, 0xf7, 0xba, 0x9f, 0x4c, 0x12, 0xae,
50 0x31, 0x7b, 0xd4, 0x7c, 0x04, 0x42, 0x2c, 0x32, 0x16, 0xc1, 0x70, 0x6d, 0x11, 0x6f, 0x3b, 0x44,
51 0x62, 0xba, 0xbd, 0xc5, 0x7a, 0xec, 0x55, 0x1b, 0xcd, 0xdb, 0xb6, 0x55, 0x08, 0x86, 0x13, 0x7f,
52 0x4e, 0xa9, 0x63, 0xe1, 0x87, 0xa0, 0x7e, 0x49, 0xfb, 0xf4, 0xa3, 0x46, 0xcf, 0x1d, 0xec, 0xf5,
53 0xc6, 0x2f, 0xe1, 0x43, 0x02, 0xd0, 0xe4, 0x5f, 0x1b, 0x20, 0x1a, 0xa7, 0x81, 0xbd, 0x31, 0x19,
54 0x6a, 0x74, 0xd7, 0x9a, 0x6d, 0x3d, 0xf8, 0xac, 0x4d, 0xbb, 0x01, 0x63, 0xa4, 0x9d, 0x3c, 0xc9,
55 0x6c, 0x8a, 0x4f, 0x61, 0xd6, 0x98, 0xf5, 0x40, 0x22, 0xa9, 0x5e, 0x93, 0x5e, 0x13, 0xd3, 0xe0,
56 0xdb, 0x54, 0xab, 0x0d, 0xe3, 0x88, 0x85, 0x80, 0x7a, 0x5e, 0x38, 0x64, 0x97, 0xc4, 0xe9, 0xb0,
57 0x5d, 0xf7, 0x40, 0x5f, 0x6e, 0x3f, 0xbe, 0x14, 0x9a, 0x7c, 0xa2, 0x7c, 0x74, 0xa5, 0x32, 0x22,
58 0x61, 0x31, 0xa5, 0x0d, 0xa5, 0xcc, 0x93, 0xe4, 0xfd, 0xed, 0xbc, 0xe7, 0xf2, 0xe5, 0xdb, 0xc6,
59 0x0c, 0xe1, 0xc8, 0x4e, 0xee, 0xe6, 0x76, 0x1c, 0x10, 0x1b, 0xd8, 0x53, 0xd4, 0xe8, 0x07, 0xed,
60 0xea, 0x91, 0xd4, 0x1b, 0x91, 0x5c, 0x28, 0x05, 0xca, 0xe2, 0x9c, 0xd1, 0x99, 0x43, 0xed, 0xd8,
61 0x6a, 0x2b, 0xd2, 0x64, 0x9b, 0xe1, 0x0c, 0x88, 0x6c, 0x0d, 0xb2, 0x6b, 0x73, 0x85, 0x9d, 0xbf,
62 0x79, 0x78, 0xaa, 0x7b, 0x5e, 0xf8, 0xa4, 0x26, 0xdf, 0xb3, 0x9b, 0x24, 0x5a, 0xc8, 0x19, 0x22,
63 0xa5, 0xc6, 0xca, 0x00, 0x59, 0x3b, 0xad, 0x45, 0xdf, 0x71, 0x1d, 0x60, 0x60, 0x24, 0x0d, 0xa4,
64 0x3d, 0x42, 0x23, 0xb8, 0xfe, 0xac, 0x86, 0x94, 0x79, 0x87, 0x05, 0xae, 0xb8, 0x4d, 0x7e, 0x11,
65 0x5b, 0x22, 0x44, 0x15, 0x3d, 0x7f, 0x82, 0x98, 0x65, 0x0a, 0x3c, 0xd3, 0xef, 0x80, 0x0d, 0x75,
66 0x03, 0x92, 0xf6, 0x3a, 0x8b, 0xa4, 0xb0, 0x61, 0x2d, 0x2c, 0xcc, 0x1f, 0x01, 0x8e, 0x7a, 0x46,
67 0x36, 0x2a, 0x83, 0x21, 0x88, 0x98, 0x13, 0x0a, 0xd5, 0xa1, 0x54, 0x4b, 0x63, 0xe0, 0xe3, 0x1c,
68 0x07, 0x5e, 0x32, 0x8c, 0xa4, 0x6b, 0x62, 0xc3, 0x28, 0x95, 0xb8, 0x0a, 0xb9, 0x4f, 0xaf, 0x7f,
69 0x49, 0xeb, 0xff, 0xd7, 0xa1, 0x41, 0x43, 0x9a, 0x92, 0x9a, 0x5f, 0xee, 0xbd, 0xb9, 0xbe, 0xb3,
70 0x4b, 0x9d, 0x0b, 0xcb, 0x9b, 0x2c, 0x26, 0x8f, 0x0f, 0xb1, 0xfa, 0xc0, 0xe3, 0x3a, 0x7f, 0x2b,
71 0x51, 0x79, 0x75, 0x25, 0x5b, 0x23, 0x22, 0xb1, 0x01, 0x27, 0x4e, 0x43, 0xdd, 0x66, 0x7a, 0x33,
72 0x4d, 0x32, 0x96, 0x83, 0x59, 0x52, 0xd7, 0x3c, 0xb0, 0xe3, 0x03, 0xd6, 0xb0, 0xc7, 0x99, 0x68,
73 0xc6, 0xa4, 0x2d, 0x35, 0x3d, 0xa4, 0x6a, 0x17, 0xd9, 0xf4, 0x0c, 0x26, 0x11, 0xe4, 0xbc, 0x03,
74 0x87, 0x25, 0x62, 0xad, 0xa9, 0x7e, 0x96, 0x4d, 0x39, 0x9b, 0x8f, 0x09, 0xdc, 0xd1, 0x28, 0x5e,
75 0xf4, 0xe3, 0x94, 0xfd, 0x94, 0x46, 0x30, 0xe2, 0x24, 0x46, 0x30, 0x7f, 0xf4, 0x4c, 0xaa, 0x51,
76 0x7e, 0x04, 0x5c, 0xa4, 0x8c, 0xba, 0x4a, 0xb8, 0x61, 0x5e, 0x75, 0x1c, 0xa8, 0x0c, 0xbc, 0x7f,
77 0x36, 0x16, 0xa1, 0x72, 0x98, 0x6a, 0x44, 0x39, 0x42, 0x67, 0xb5, 0x4a, 0xac, 0x14, 0x35, 0x8f,
78 0xcd, 0x87, 0x3f, 0x9e, 0x2e, 0xa1, 0x53, 0xf1, 0x45, 0x68, 0x26, 0xcb, 0x35, 0x96, 0x57, 0xd5,
79 0x3a, 0x24, 0x74, 0xe2, 0xff, 0xe0, 0x70, 0xb1, 0xbd, 0xec, 0x0c, 0xd2, 0x97, 0x9a, 0xe5, 0x9f,
80 0xa9, 0xfe, 0x6a, 0x63, 0x17, 0x35, 0xad, 0x64, 0x2f, 0xd9, 0x2e, 0xdb, 0x47, 0xdc, 0x62, 0xdc,
81 0xcc, 0xee, 0x7e, 0x23, 0xa6, 0x67, 0x61, 0x7c, 0xd1, 0x03, 0xbd, 0x78, 0xe9, 0x34, 0x05, 0xed,
82 0x05, 0x87, 0xef, 0x59, 0xf4, 0x16, 0xd6, 0x8d, 0x85, 0x46, 0x65, 0x2a, 0x08, 0xac, 0x4a, 0x5d,
83 0xe6, 0x27, 0x5f, 0x43, 0xdd, 0x51, 0x4e, 0x95, 0x9b, 0xf5, 0x0c, 0x81, 0x24, 0x73, 0x39, 0x77,
84 0xe9, 0xc8, 0x35, 0x4a, 0xe2, 0xb8, 0x35, 0x92, 0xde, 0x5c, 0x31, 0x12, 0x36, 0x5c, 0xc7, 0x69,
85 0xcd, 0x79, 0xa9, 0xf9, 0xcf, 0x13, 0xa9, 0x12, 0x29, 0x25, 0x5c, 0x6a, 0x34, 0xa4, 0xbf, 0xc5,
86 0xb6, 0x2a, 0xc1, 0xba, 0x6a, 0xd3, 0x98, 0x8c, 0x9b, 0x6d, 0x9f, 0xb9, 0x25, 0xa6, 0xd1, 0x97,
87 0x80, 0x38, 0x11, 0xdc, 0x73, 0x5c, 0xe7, 0x3a, 0x1f, 0xd2, 0x16, 0xcd, 0x63, 0xfb, 0x41, 0xb0,
88 0xba, 0xb0, 0x38, 0x67, 0x48, 0xd2, 0x8a, 0x94, 0x2f, 0x11, 0x81, 0xbf, 0x66, 0x38, 0x68, 0xff,
89 0xfe, 0xd1, 0x7c, 0xcd, 0xa3, 0xac, 0xe4, 0xf7, 0x58, 0x19, 0xcd, 0x2a, 0xe3, 0xfa, 0x4d, 0xb0,
90 0xbe, 0xac, 0x05, 0x1c, 0xd9, 0x8d, 0xf7, 0x5c, 0xc0, 0xfc, 0xa6, 0xb5, 0x99, 0xb8, 0x8e, 0x2b,
91 0x72, 0xf8, 0x19, 0xfc, 0x17, 0x11, 0xf6, 0x2b, 0x08, 0xe4, 0x6e, 0xb0, 0x65, 0xab, 0x78, 0x8a,
92 0xfc, 0x7c, 0x09, 0xca, 0x73, 0xcd, 0x35, 0x5d, 0x6c, 0x7a, 0x36, 0xc0, 0x24, 0xba, 0x3f, 0x08,
93 0xea, 0x17, 0x09, 0xe1, 0x9d, 0x5d, 0x18, 0x59, 0x8a, 0xd8, 0x6a, 0x6d, 0x85, 0x6a, 0x9e, 0xa9,
94 0xe5, 0x4b, 0x45, 0xb2, 0x35, 0x6e, 0x62, 0x24, 0x08, 0x00, 0x1c, 0x06, 0x73, 0x27, 0x5d, 0x11,
95 0x4a, 0xc8, 0x51, 0xbd, 0x59, 0xd6, 0x94, 0xce, 0x16, 0x15, 0x17, 0x58, 0x7f, 0x39, 0x9d, 0x4e,
96 0x69, 0x1a, 0x64, 0xbb, 0xd4, 0x51, 0xb9, 0xe4, 0x7d, 0x51, 0x3a, 0xff, 0xe5, 0x1f, 0x29, 0xea,
97 0x7e, 0xa5, 0x62, 0x63, 0xff, 0x10, 0xf7, 0x54, 0x35, 0xd1, 0xf3, 0x73, 0x1e, 0xab, 0xca, 0x52,
98 0x14, 0xc6, 0x7e, 0x51, 0xc2, 0x48, 0x13, 0xcb, 0x30, 0xb2, 0x1a, 0x84, 0x72, 0xe5, 0x44, 0x83,
99 0xc9, 0x90, 0xa5, 0x8c, 0xf9, 0xeb, 0x3c, 0x5c, 0xc6, 0xcc, 0x8a, 0x95, 0x8a, 0xfa, 0xeb, 0x37,
100 0x9c, 0xde, 0xa2, 0xb1, 0x72, 0x4d, 0xd9, 0x3d, 0xab, 0xfd, 0x0e, 0xbd, 0x32, 0x9d, 0x23, 0xe9,
101 0x6f, 0x85, 0x4e, 0xfe, 0xcd, 0x91, 0xfb, 0x82, 0x94, 0xee, 0x8b, 0xdf, 0x6a, 0xd9, 0x01, 0xa1,
102 0xc6, 0x22, 0x18, 0x01, 0x8d, 0x10, 0xd5, 0x87, 0x42, 0xd0, 0xbd, 0x23, 0x75, 0x44, 0x53, 0x46,
103 0xa5, 0xae, 0x00, 0x4c, 0x0e, 0x88, 0x4a, 0xa8, 0x3d, 0x4a, 0x30, 0xe0, 0x1a, 0xa4, 0xe5, 0x40,
104 0xb8, 0xe0, 0x12, 0x9c, 0x44, 0x03, 0xfb, 0x2e, 0x4e, 0xf5, 0x29, 0xdb, 0x09, 0x84, 0x55, 0xc7,
105 0x6c, 0xc6, 0x1f, 0xf9, 0xee, 0x0b, 0xa4, 0x91, 0x7d, 0x79, 0x27, 0x59, 0x75, 0x97, 0xec, 0x6a,
106 0xa8, 0xf8, 0x55, 0xa8, 0x45, 0xd4, 0xd7, 0xa6, 0xc1, 0xc4, 0x27, 0x35, 0xe8, 0x4f, 0x39, 0x89,
107 0x7d, 0x41, 0xf3, 0xf6, 0xd0, 0xb6, 0xf9, 0x91, 0xeb, 0x94, 0xf1, 0xbb, 0x17, 0x46, 0x9c, 0xd5,
108 0x5a, 0x53, 0x04, 0x2d, 0x12, 0x7c, 0x17, 0x6a, 0x36, 0xb5, 0xea, 0xf3, 0x5b, 0x96, 0x1b, 0xee,
109 0xce, 0xc4, 0xc0, 0x11, 0x5a, 0xbc, 0x0c, 0x29, 0xd0, 0x42, 0x1d, 0x16, 0x63, 0xea, 0x1e, 0x04,
110 0x2f, 0xe3, 0x17, 0xed, 0x33, 0xac, 0x56, 0x80, 0x34, 0x41, 0x41, 0x1e, 0x77, 0x80, 0x06, 0x9f,
111 0xbc, 0x2e, 0x78, 0xa1, 0x04, 0x00, 0x06, 0x6f, 0x36, 0x2f, 0xb7, 0xa5, 0x95, 0x37, 0x82, 0x9d,
112 0xef, 0x41, 0x08, 0x85, 0x3d, 0x53, 0xa7, 0xfb, 0xfe, 0xba, 0x8c, 0xb9, 0xae, 0xc7, 0x89, 0x11,
113 0x69, 0x4f, 0x62, 0xe6, 0xb6, 0x08, 0x6b, 0x35, 0x1c, 0x96, 0xb3, 0x7b, 0x40, 0x2d, 0xee, 0x07,
114 0x40, 0x52, 0x4f, 0x68, 0x60, 0xf4, 0xb9, 0xc3, 0x54, 0x9f, 0x22, 0x50, 0x88, 0x48, 0x6a, 0x28,
115 0x93, 0x46, 0x00, 0xe2, 0x4a, 0x85, 0x41, 0x78, 0x0e, 0x87, 0xc5, 0xeb, 0xfc, 0xd3, 0x5f, 0x4d,
116 0x24, 0xe4, 0x9d, 0xeb, 0x1d, 0x00, 0x73, 0x85, 0x25, 0x47, 0x9e, 0x8c, 0x5b, 0x88, 0xf4, 0x3b,
117 0x33, 0xf0, 0x3d, 0x3a, 0xa1, 0x28, 0xd3, 0x06, 0xb4, 0x7a, 0x4e, 0x5d, 0x31, 0x1b, 0xca, 0xf4,
118 0x3f, 0x70, 0x30, 0x49, 0x44, 0x29, 0x24, 0x14, 0x5e, 0x35, 0xc2, 0x6c, 0x92, 0x7e, 0xf8, 0x97,
119 0x0c, 0x51, 0x9d, 0x67, 0xc0, 0x10, 0xa9, 0x35, 0x48, 0x59, 0x6a, 0x33, 0xef, 0x40, 0x4e, 0x53,
120 0x10, 0x14, 0x2a, 0x12, 0x38, 0xe6, 0xc4, 0x63, 0x9c, 0x84, 0x85, 0x06, 0xaf, 0x3d, 0x3a, 0x84,
121 0x06, 0x60, 0x88, 0x32, 0xda, 0x2c, 0xe5, 0xc6, 0x59, 0xf1, 0xe0, 0x10, 0xe2, 0x3c, 0xe6, 0xbf,
122 0x32, 0x7d, 0x32, 0x39, 0x6d, 0xe4, 0xd9, 0xca, 0xe7, 0xf5, 0xf4, 0xa6, 0x5f, 0xb2, 0x33, 0x05,
123 0xb5, 0xad, 0x5f, 0xcb, 0x0b, 0x14, 0xaf, 0xeb, 0xc0, 0xec, 0x87, 0x85, 0x9b, 0x13, 0xb5, 0x8a,
124 0x98, 0xa9, 0x92, 0x13, 0x1b, 0x74, 0xec, 0xfd, 0xe1, 0xc1, 0x22, 0x06, 0x5d, 0x4f, 0x06, 0xc7,
125 0xdd, 0xc6, 0xf0, 0xc4, 0x01, 0x04, 0xad, 0x7f, 0x71, 0xbc, 0x74, 0x4d, 0xfd, 0x18, 0xa3, 0x56,
126 0x2c, 0x45, 0x28, 0x2b, 0x2f, 0xbc, 0x9b, 0xb8, 0x4b, 0xe6, 0x51, 0x75, 0x28, 0x0c, 0x27, 0x0e,
127 0xf7, 0x92, 0x8c, 0xc9, 0xde, 0x33, 0x1a, 0x65, 0x28, 0xc7, 0x01, 0x32, 0xa2, 0x36, 0x88, 0xb6,
128 0x64, 0x10, 0x03, 0xd6, 0xb7, 0x9f, 0x9d, 0x73, 0xe1, 0xa9, 0xc7, 0xdf, 0xe1, 0x0b, 0x39, 0x31,
129 0x77, 0xbc, 0x91, 0xf1, 0x45, 0x9a, 0xc5, 0x97, 0x28, 0xc0, 0x61, 0xc5, 0x23, 0x54, 0xad, 0xe3,
130 0x23, 0x18, 0x69, 0xf7, 0x27, 0xd0, 0x5b, 0xf2, 0x44, 0x62, 0xdc, 0x97, 0xce, 0x4e, 0x40, 0x76,
131 0x00, 0xde, 0xc2, 0xf9, 0x3a, 0x42, 0xfd, 0xd4, 0xd7, 0xe1, 0x85, 0xd8, 0xc9, 0x38, 0x91, 0xc1,
132 0x79, 0x87, 0x58, 0xf1, 0x26, 0x1a, 0x29, 0x02, 0xe3, 0x54, 0xde, 0x58, 0x64, 0x9d, 0xe6, 0x8e,
133 0x33, 0x70, 0x53, 0x43, 0x47, 0x90, 0xee, 0x6e, 0x0f, 0x8c, 0xb3, 0x9e, 0x47, 0x45, 0xfc, 0xa8,
134 0xe3, 0x52, 0x62, 0x74, 0x6d, 0xa2, 0xaf, 0x28, 0x9d, 0xdf, 0x1e, 0x69, 0x1f, 0x56, 0xbc, 0x49,
135 0xc1, 0xe5, 0xd6, 0xc4, 0xb5, 0x5c, 0x4d, 0x39, 0x49, 0x4b, 0xb4, 0xec, 0x56, 0x54, 0x9a, 0x15,
136 0x94, 0x0a, 0xcb, 0xa9, 0x10, 0x46, 0x03, 0x5c, 0x23, 0x2f, 0x29, 0xed, 0x72, 0xa1, 0x57, 0xfa,
137 0x58, 0xef, 0x21, 0x7e, 0xf2, 0x8b, 0xa7, 0x04, 0x51, 0xb4, 0x03, 0x5d, 0xd8, 0x48, 0xc0, 0xe5,
138 0x83, 0xb6, 0x7a, 0x6b, 0xcd, 0xfb, 0xda, 0x47, 0xe8, 0xa1, 0xae, 0x57, 0x74, 0x49, 0xc0, 0xf9,
139 0x4a, 0x6b, 0x3c, 0xb5, 0xd8, 0x27, 0x3d, 0x1d, 0x96, 0x39, 0x09, 0x65, 0x95, 0xdb, 0x01, 0xa3,
140 0x8b, 0x78, 0x8b, 0x07, 0x6d, 0x1c, 0x8b, 0x4b, 0x1d, 0x9d, 0x4a, 0x4f, 0xcb, 0xb8, 0xf6, 0x22,
141 0x73, 0x8a, 0x7b, 0xc8, 0xf2, 0x0a, 0xef, 0x03, 0x1e, 0xb7, 0x4d, 0x8f, 0xc0, 0xdf, 0x87, 0x88,
142 0x05, 0xe1, 0x0a, 0x30, 0xea, 0xde, 0xf3, 0xc2, 0xb6, 0x00, 0x3c, 0xd6, 0xff, 0x3b, 0xb5, 0x01,
143 0xfb, 0xd8, 0xb2, 0x65, 0x26, 0x5d, 0xa0, 0x5a, 0x7c, 0xef, 0x1d, 0x85, 0xbe, 0x51, 0xc2, 0x57,
144 0x0b, 0x27, 0x37, 0x71, 0x99, 0xf5, 0x87, 0x83, 0x68, 0x0b, 0x88, 0xed, 0x66, 0x9e, 0x37, 0x59,
145 0x84, 0x23, 0x72, 0xc3, 0x80, 0xac, 0xfe, 0x45, 0x5f, 0xdf, 0x31, 0xc4, 0x84, 0x07, 0x5a, 0x17,
146 0x28, 0xcd, 0x64, 0xb4, 0xe2, 0xa3, 0x0e, 0x2c, 0x15, 0x60, 0x77, 0xdc, 0x08, 0x45, 0x36, 0x37,
147 0x68, 0x50, 0xba, 0x03, 0x85, 0xb7, 0xed, 0xd0, 0x7b, 0xb2, 0xa1, 0x62, 0xbc, 0x70, 0x00, 0x9e,
148];
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct UpdateCheck {
153 pub current_version: String,
154 pub latest_version: String,
155 pub update_available: bool,
156 pub download_url: Option<String>,
157}
158
159impl UpdateCheck {
160 pub fn force(&mut self) -> Result<()> {
164 self.update_available = true;
165 self.download_url = Some(build_download_url(&self.latest_version)?);
166 Ok(())
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct UpdateResult {
173 pub previous_version: String,
174 pub new_version: String,
175}
176
177pub async fn check_for_update(current_version: &str) -> Result<UpdateCheck> {
181 let latest = fetch_latest_cli_version().await?;
182 let current = parse_version(current_version)?;
183 let latest_parsed = parse_version(&latest)?;
184 let update_available = latest_parsed > current;
185
186 let download_url = if update_available {
187 Some(build_download_url(&latest)?)
188 } else {
189 None
190 };
191
192 Ok(UpdateCheck {
193 current_version: current_version.to_string(),
194 latest_version: latest,
195 update_available,
196 download_url,
197 })
198}
199
200pub async fn perform_update(
206 check: &UpdateCheck,
207 progress: &dyn ProgressReporter,
208) -> Result<UpdateResult> {
209 let download_url = check.download_url.as_deref().ok_or_else(|| {
210 Error::UpdateFailed("no download URL — are you already on the latest version?".to_string())
211 })?;
212
213 let tmp_dir = tempfile::tempdir()
214 .map_err(|e| Error::UpdateFailed(format!("failed to create temp directory: {e}")))?;
215
216 let (archive_path, archive_bytes) =
217 download_archive(download_url, tmp_dir.path(), progress).await?;
218
219 let sig_url = format!("{download_url}.sig");
220 progress.report_started("Downloading signature...");
221 let sig_bytes = download_bytes(&sig_url).await?;
222
223 progress.report_started("Verifying ML-DSA signature...");
224 verify_signature(&archive_bytes, &sig_bytes)?;
225 progress.report_complete("Signature verified");
226
227 progress.report_started("Extracting archive...");
228 let extracted = if download_url.ends_with(".zip") {
229 extract_zip(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
230 } else {
231 extract_tar_gz(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
232 };
233 let binary_path = extracted.binary_path;
234
235 let actual_version = extract_version(&binary_path).await;
237 if let Ok(ref v) = actual_version {
238 if v != &check.latest_version {
239 return Err(Error::UpdateFailed(format!(
240 "version mismatch: expected {}, binary reports {v}",
241 check.latest_version
242 )));
243 }
244 }
245
246 replace_binary(&binary_path)?;
247
248 let _ = std::fs::remove_file(&archive_path);
251
252 Ok(UpdateResult {
253 previous_version: check.current_version.clone(),
254 new_version: check.latest_version.clone(),
255 })
256}
257
258async fn fetch_latest_cli_version() -> Result<String> {
263 let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases?per_page=100");
265 let client = reqwest::Client::new();
266 let resp = client
267 .get(&url)
268 .header("User-Agent", "ant-cli")
269 .header("Accept", "application/vnd.github+json")
270 .send()
271 .await
272 .map_err(|e| Error::UpdateFailed(format!("failed to fetch releases: {e}")))?;
273
274 if !resp.status().is_success() {
275 return Err(Error::UpdateFailed(format!(
276 "GitHub API returned status {} when fetching releases",
277 resp.status()
278 )));
279 }
280
281 let releases: Vec<serde_json::Value> = resp
282 .json()
283 .await
284 .map_err(|e| Error::UpdateFailed(format!("failed to parse releases JSON: {e}")))?;
285
286 let mut best: Option<semver::Version> = None;
288 for release in &releases {
289 if release["draft"].as_bool().unwrap_or(false)
290 || release["prerelease"].as_bool().unwrap_or(false)
291 {
292 continue;
293 }
294 let tag = release["tag_name"].as_str().unwrap_or_default();
295 if let Some(version_str) = tag.strip_prefix(TAG_PREFIX) {
296 if let Ok(v) = semver::Version::parse(version_str) {
297 if best.as_ref().is_none_or(|b| v > *b) {
298 best = Some(v);
299 }
300 }
301 }
302 }
303
304 best.map(|v| v.to_string())
305 .ok_or_else(|| Error::UpdateFailed("no ant-cli release found on GitHub".to_string()))
306}
307
308async fn download_archive(
313 url: &str,
314 tmp_dir: &Path,
315 progress: &dyn ProgressReporter,
316) -> Result<(PathBuf, Vec<u8>)> {
317 progress.report_started(&format!("Downloading {CLI_BINARY_NAME} from {url}"));
318
319 let client = reqwest::Client::new();
320 let resp = client
321 .get(url)
322 .header("User-Agent", "ant-cli")
323 .send()
324 .await
325 .map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
326
327 if !resp.status().is_success() {
328 return Err(Error::UpdateFailed(format!(
329 "download returned status {}",
330 resp.status()
331 )));
332 }
333
334 let total_size = resp.content_length().unwrap_or(0);
335 let mut downloaded: u64 = 0;
336
337 let tmp_archive = tmp_dir.join(".download.tmp");
338 let mut tmp_file = std::fs::File::create(&tmp_archive)
339 .map_err(|e| Error::UpdateFailed(format!("failed to create temp file: {e}")))?;
340
341 let mut stream = resp.bytes_stream();
342 while let Some(chunk) = stream.next().await {
343 let chunk =
344 chunk.map_err(|e| Error::UpdateFailed(format!("download stream error: {e}")))?;
345 downloaded += chunk.len() as u64;
346 std::io::Write::write_all(&mut tmp_file, &chunk)
347 .map_err(|e| Error::UpdateFailed(format!("failed to write temp file: {e}")))?;
348 progress.report_progress(downloaded, total_size);
349 }
350 drop(tmp_file);
351
352 progress.report_complete("Download complete");
353
354 let bytes = std::fs::read(&tmp_archive)
355 .map_err(|e| Error::UpdateFailed(format!("failed to read temp file: {e}")))?;
356
357 Ok((tmp_archive, bytes))
358}
359
360async fn download_bytes(url: &str) -> Result<Vec<u8>> {
362 let client = reqwest::Client::new();
363 let resp = client
364 .get(url)
365 .header("User-Agent", "ant-cli")
366 .send()
367 .await
368 .map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
369
370 if !resp.status().is_success() {
371 return Err(Error::UpdateFailed(format!(
372 "download returned status {} for {url}",
373 resp.status()
374 )));
375 }
376
377 resp.bytes()
378 .await
379 .map(|b| b.to_vec())
380 .map_err(|e| Error::UpdateFailed(format!("failed to read response body: {e}")))
381}
382
383fn verify_signature(archive_bytes: &[u8], signature_bytes: &[u8]) -> Result<()> {
385 if signature_bytes.len() != SIGNATURE_SIZE {
386 return Err(Error::UpdateFailed(format!(
387 "invalid signature size: expected {SIGNATURE_SIZE}, got {}",
388 signature_bytes.len()
389 )));
390 }
391
392 let public_key = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, RELEASE_SIGNING_KEY)
393 .map_err(|e| Error::UpdateFailed(format!("invalid embedded release key: {e}")))?;
394
395 let sig = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, signature_bytes)
396 .map_err(|e| Error::UpdateFailed(format!("invalid signature format: {e}")))?;
397
398 let dsa = ml_dsa_65();
399 let valid = dsa
400 .verify_with_context(&public_key, archive_bytes, &sig, SIGNING_CONTEXT)
401 .map_err(|e| Error::UpdateFailed(format!("signature verification error: {e}")))?;
402
403 if valid {
404 Ok(())
405 } else {
406 Err(Error::UpdateFailed(
407 "signature verification failed: archive may be corrupted or tampered".to_string(),
408 ))
409 }
410}
411
412async fn extract_version(binary_path: &Path) -> Result<String> {
414 let output = tokio::process::Command::new(binary_path)
415 .arg("--version")
416 .output()
417 .await
418 .map_err(|e| {
419 Error::UpdateFailed(format!(
420 "failed to run {} --version: {e}",
421 binary_path.display()
422 ))
423 })?;
424
425 if !output.status.success() {
426 return Err(Error::UpdateFailed(format!(
427 "{} --version exited with status {}",
428 binary_path.display(),
429 output.status
430 )));
431 }
432
433 let stdout = String::from_utf8_lossy(&output.stdout);
434 Ok(parse_version_from_stdout(&stdout).to_string())
435}
436
437fn parse_version_from_stdout(stdout: &str) -> &str {
445 stdout
446 .lines()
447 .next()
448 .unwrap_or_default()
449 .split_whitespace()
450 .last()
451 .unwrap_or("unknown")
452}
453
454fn replace_binary(new_binary: &Path) -> Result<()> {
459 self_replace::self_replace(new_binary)
460 .map_err(|e| Error::UpdateFailed(format!("failed to replace binary: {e}")))?;
461 Ok(())
462}
463
464pub fn build_download_url(version: &str) -> Result<String> {
469 let asset_name = cli_platform_asset_name(version)?;
470 Ok(format!(
471 "https://github.com/{GITHUB_REPO}/releases/download/{TAG_PREFIX}{version}/{asset_name}"
472 ))
473}
474
475fn cli_platform_asset_name(version: &str) -> Result<String> {
479 let target_triple = if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
480 "x86_64-unknown-linux-musl"
481 } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
482 "aarch64-unknown-linux-musl"
483 } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
484 "x86_64-apple-darwin"
485 } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
486 "aarch64-apple-darwin"
487 } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
488 "x86_64-pc-windows-msvc"
489 } else {
490 return Err(Error::UpdateFailed(format!(
491 "unsupported platform: {}-{}",
492 std::env::consts::OS,
493 std::env::consts::ARCH
494 )));
495 };
496
497 let ext = if cfg!(target_os = "windows") {
498 "zip"
499 } else {
500 "tar.gz"
501 };
502
503 Ok(format!("ant-{version}-{target_triple}.{ext}"))
504}
505
506fn parse_version(version: &str) -> Result<semver::Version> {
507 let cleaned = version.strip_prefix('v').unwrap_or(version);
508 semver::Version::parse(cleaned)
509 .map_err(|e| Error::UpdateFailed(format!("invalid version '{version}': {e}")))
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn parse_version_from_stdout_single_line() {
518 assert_eq!(parse_version_from_stdout("ant 0.1.4"), "0.1.4");
519 assert_eq!(parse_version_from_stdout("ant 0.1.4\n"), "0.1.4");
520 }
521
522 #[test]
523 fn parse_version_from_stdout_multi_line_license_trailer() {
524 let stdout = "ant 0.1.4\n\
526 Autonomi network client\n\
527 \n\
528 Repository: https://github.com/WithAutonomi/ant-client\n\
529 License: MIT or Apache-2.0\n";
530 assert_eq!(parse_version_from_stdout(stdout), "0.1.4");
531 }
532
533 #[test]
534 fn parse_version_from_stdout_empty() {
535 assert_eq!(parse_version_from_stdout(""), "unknown");
536 }
537
538 #[test]
539 fn parse_version_from_stdout_blank_first_line() {
540 assert_eq!(parse_version_from_stdout("\nant 0.1.4\n"), "unknown");
541 }
542
543 #[test]
544 fn parse_version_from_stdout_leading_whitespace() {
545 assert_eq!(parse_version_from_stdout(" ant 0.1.4\n"), "0.1.4");
546 }
547
548 #[test]
549 fn parse_version_valid() {
550 assert!(parse_version("1.2.3").is_ok());
551 assert!(parse_version("v1.2.3").is_ok());
552 assert!(parse_version("0.1.0").is_ok());
553 }
554
555 #[test]
556 fn parse_version_invalid() {
557 assert!(parse_version("not-a-version").is_err());
558 assert!(parse_version("").is_err());
559 }
560
561 #[test]
562 fn version_comparison() {
563 let v1 = parse_version("0.1.0").unwrap();
564 let v2 = parse_version("0.2.0").unwrap();
565 assert!(v2 > v1);
566
567 let v3 = parse_version("1.0.0").unwrap();
568 assert!(v3 > v2);
569
570 let same = parse_version("0.1.0").unwrap();
571 assert_eq!(v1, same);
572 }
573
574 #[test]
575 fn check_result_no_update() {
576 let check = UpdateCheck {
577 current_version: "1.0.0".to_string(),
578 latest_version: "1.0.0".to_string(),
579 update_available: false,
580 download_url: None,
581 };
582 assert!(!check.update_available);
583 assert!(check.download_url.is_none());
584 }
585
586 #[test]
587 fn check_result_with_update() {
588 let check = UpdateCheck {
589 current_version: "0.1.0".to_string(),
590 latest_version: "0.2.0".to_string(),
591 update_available: true,
592 download_url: Some("https://example.com/ant.tar.gz".to_string()),
593 };
594 assert!(check.update_available);
595 assert!(check.download_url.is_some());
596 }
597
598 #[test]
599 fn force_populates_download_url() {
600 let mut check = UpdateCheck {
601 current_version: "1.0.0".to_string(),
602 latest_version: "1.0.0".to_string(),
603 update_available: false,
604 download_url: None,
605 };
606 check.force().unwrap();
607 assert!(check.update_available);
608 assert!(check.download_url.is_some());
609 }
610
611 #[test]
612 fn platform_asset_name_format() {
613 let name = cli_platform_asset_name("1.2.3").unwrap();
614 assert!(name.starts_with("ant-1.2.3-"));
615 assert!(
616 name.ends_with(".tar.gz") || name.ends_with(".zip"),
617 "unexpected extension: {name}"
618 );
619 }
620
621 #[test]
622 fn build_download_url_format() {
623 let url = build_download_url("1.2.3").unwrap();
624 assert!(url.starts_with(
625 "https://github.com/WithAutonomi/ant-client/releases/download/ant-cli-v1.2.3/ant-1.2.3-"
626 ));
627 assert!(url.ends_with(".tar.gz") || url.ends_with(".zip"));
628 }
629
630 #[test]
631 fn update_check_serializes() {
632 let check = UpdateCheck {
633 current_version: "0.1.0".to_string(),
634 latest_version: "0.2.0".to_string(),
635 update_available: true,
636 download_url: Some("https://example.com/ant.tar.gz".to_string()),
637 };
638 let json = serde_json::to_string(&check).unwrap();
639 let deserialized: UpdateCheck = serde_json::from_str(&json).unwrap();
640 assert_eq!(deserialized.current_version, "0.1.0");
641 assert!(deserialized.update_available);
642 }
643
644 #[test]
645 fn verify_signature_rejects_wrong_size() {
646 let result = verify_signature(b"some archive data", &[0u8; 100]);
647 assert!(result.is_err());
648 let err = result.unwrap_err().to_string();
649 assert!(err.contains("invalid signature size"), "got: {err}");
650 }
651
652 #[test]
653 fn verify_signature_rejects_invalid_signature() {
654 let invalid_sig = vec![0u8; SIGNATURE_SIZE];
655 let result = verify_signature(b"some archive data", &invalid_sig);
656 assert!(result.is_err());
657 }
658
659 #[test]
660 fn verify_signature_valid_roundtrip() {
661 let dsa = ml_dsa_65();
662 let (public_key, secret_key) = dsa.generate_keypair().unwrap();
663 let archive = b"fake archive content for testing";
664
665 let sig = dsa
666 .sign_with_context(&secret_key, archive, SIGNING_CONTEXT)
667 .unwrap();
668
669 let parsed_sig =
671 MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &sig.to_bytes()).unwrap();
672 let valid = dsa
673 .verify_with_context(&public_key, archive, &parsed_sig, SIGNING_CONTEXT)
674 .unwrap();
675 assert!(valid);
676 }
677}