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::channel::version_matches_channel;
8use crate::error::{Error, Result};
9use crate::node::binary::{extract_tar_gz, extract_zip, ProgressReporter};
10use crate::node::types::UpgradeChannel;
11
12const GITHUB_REPO: &str = "WithAutonomi/ant-client";
13const CLI_BINARY_NAME: &str = "ant";
14const TAG_PREFIX: &str = "ant-cli-v";
15
16const SIGNING_CONTEXT: &[u8] = b"ant-release-v1";
18
19const SIGNATURE_SIZE: usize = 3309;
21
22const RELEASE_SIGNING_KEY: &[u8] = &[
28 0xb4, 0xcf, 0x2a, 0x24, 0x31, 0xd9, 0xb2, 0x3a, 0xab, 0xe9, 0x5e, 0xfc, 0xbc, 0xf3, 0xb1, 0x1f,
29 0x4e, 0x50, 0x0a, 0x46, 0xeb, 0x83, 0xfc, 0x6f, 0x0f, 0x89, 0x41, 0x00, 0x1b, 0x52, 0xde, 0xdc,
30 0xb5, 0xc9, 0x07, 0xed, 0x72, 0x3a, 0xe1, 0xa9, 0x82, 0xe1, 0xfc, 0xff, 0xab, 0xce, 0x9f, 0x7d,
31 0xab, 0xe2, 0x57, 0x92, 0xe0, 0xf2, 0xac, 0xa1, 0x41, 0xd3, 0x74, 0x95, 0x41, 0xd1, 0xac, 0x22,
32 0xb1, 0xbb, 0x5c, 0xf4, 0x02, 0x0b, 0x73, 0x85, 0xfd, 0x56, 0x75, 0x0d, 0x5c, 0x38, 0xe1, 0x2d,
33 0xe0, 0x15, 0x4f, 0xbf, 0x40, 0xeb, 0xf1, 0x0d, 0x8f, 0x39, 0x32, 0xeb, 0x80, 0xa7, 0x2e, 0x77,
34 0x3e, 0x54, 0xe3, 0x3d, 0x62, 0xae, 0xe7, 0x09, 0x5e, 0xfb, 0xdc, 0xaa, 0x07, 0xdc, 0xe1, 0x08,
35 0x96, 0xbb, 0x4b, 0xa0, 0x2e, 0x05, 0x2d, 0xee, 0xeb, 0x9a, 0x1a, 0xae, 0xde, 0xe9, 0x2c, 0xf8,
36 0x2f, 0x43, 0x6c, 0x78, 0x4b, 0xde, 0xef, 0x91, 0x8b, 0x94, 0x9d, 0x4f, 0x81, 0x05, 0xcc, 0xf0,
37 0x98, 0xce, 0xce, 0x67, 0x54, 0xac, 0xde, 0xcd, 0x26, 0x9e, 0x84, 0xf4, 0x88, 0xb2, 0x1a, 0x3e,
38 0x93, 0x2e, 0xff, 0xa8, 0x45, 0x95, 0xd1, 0xd0, 0xb1, 0x6c, 0x3c, 0x1e, 0xef, 0x3d, 0xe3, 0xf2,
39 0x73, 0xe2, 0xf6, 0xb7, 0xf9, 0x3f, 0x10, 0x0d, 0x3c, 0xde, 0x28, 0x94, 0x07, 0xef, 0x24, 0x70,
40 0xc4, 0x5a, 0x0a, 0x67, 0xbb, 0x0f, 0x4f, 0x5c, 0x2b, 0xd8, 0x02, 0x05, 0xa5, 0x98, 0x03, 0x5d,
41 0x8f, 0xc0, 0x4a, 0x84, 0xe9, 0xea, 0xac, 0x13, 0xdf, 0x69, 0xfc, 0x1e, 0xcf, 0xb6, 0x88, 0xba,
42 0x99, 0x30, 0xbc, 0x7a, 0xb8, 0x9d, 0x3d, 0x62, 0x3b, 0x33, 0x19, 0xbb, 0x3a, 0x2c, 0x2b, 0xa0,
43 0x5a, 0xb0, 0x8f, 0x9e, 0x10, 0x81, 0xb9, 0x12, 0x54, 0x81, 0xf8, 0xe2, 0x91, 0xb2, 0xe7, 0xe6,
44 0x9c, 0x11, 0xeb, 0x49, 0x64, 0x3a, 0x25, 0xd6, 0x53, 0x2e, 0xdf, 0xfc, 0x14, 0x32, 0x65, 0xcc,
45 0x87, 0xdb, 0xfd, 0xbb, 0x81, 0xa8, 0x50, 0xcc, 0xb4, 0x31, 0x0c, 0x70, 0xf3, 0xb6, 0x15, 0x8a,
46 0x50, 0x80, 0xad, 0xb1, 0xb0, 0x10, 0x2b, 0x67, 0x33, 0xf5, 0xf6, 0x36, 0x35, 0x5f, 0xa7, 0xd2,
47 0x81, 0xd6, 0x75, 0xa1, 0x18, 0x15, 0xbe, 0x1d, 0x5e, 0x33, 0x8e, 0x98, 0xdd, 0x45, 0x0f, 0x0c,
48 0x0f, 0x0d, 0x8b, 0x3f, 0x97, 0x11, 0x21, 0x2e, 0xa0, 0x5e, 0xfe, 0x70, 0x09, 0xa7, 0x14, 0x30,
49 0xa3, 0x01, 0x2d, 0x18, 0x2b, 0x8f, 0x19, 0x75, 0x54, 0x1f, 0xd8, 0xee, 0x66, 0x06, 0x7b, 0x9d,
50 0x7d, 0xb2, 0xae, 0x14, 0xe6, 0x51, 0x19, 0xc2, 0x45, 0x2e, 0x7e, 0x11, 0xd9, 0x7b, 0x16, 0x8e,
51 0xae, 0x17, 0xdb, 0x1b, 0x24, 0x90, 0xcd, 0xed, 0x94, 0xf9, 0xf7, 0xba, 0x9f, 0x4c, 0x12, 0xae,
52 0x31, 0x7b, 0xd4, 0x7c, 0x04, 0x42, 0x2c, 0x32, 0x16, 0xc1, 0x70, 0x6d, 0x11, 0x6f, 0x3b, 0x44,
53 0x62, 0xba, 0xbd, 0xc5, 0x7a, 0xec, 0x55, 0x1b, 0xcd, 0xdb, 0xb6, 0x55, 0x08, 0x86, 0x13, 0x7f,
54 0x4e, 0xa9, 0x63, 0xe1, 0x87, 0xa0, 0x7e, 0x49, 0xfb, 0xf4, 0xa3, 0x46, 0xcf, 0x1d, 0xec, 0xf5,
55 0xc6, 0x2f, 0xe1, 0x43, 0x02, 0xd0, 0xe4, 0x5f, 0x1b, 0x20, 0x1a, 0xa7, 0x81, 0xbd, 0x31, 0x19,
56 0x6a, 0x74, 0xd7, 0x9a, 0x6d, 0x3d, 0xf8, 0xac, 0x4d, 0xbb, 0x01, 0x63, 0xa4, 0x9d, 0x3c, 0xc9,
57 0x6c, 0x8a, 0x4f, 0x61, 0xd6, 0x98, 0xf5, 0x40, 0x22, 0xa9, 0x5e, 0x93, 0x5e, 0x13, 0xd3, 0xe0,
58 0xdb, 0x54, 0xab, 0x0d, 0xe3, 0x88, 0x85, 0x80, 0x7a, 0x5e, 0x38, 0x64, 0x97, 0xc4, 0xe9, 0xb0,
59 0x5d, 0xf7, 0x40, 0x5f, 0x6e, 0x3f, 0xbe, 0x14, 0x9a, 0x7c, 0xa2, 0x7c, 0x74, 0xa5, 0x32, 0x22,
60 0x61, 0x31, 0xa5, 0x0d, 0xa5, 0xcc, 0x93, 0xe4, 0xfd, 0xed, 0xbc, 0xe7, 0xf2, 0xe5, 0xdb, 0xc6,
61 0x0c, 0xe1, 0xc8, 0x4e, 0xee, 0xe6, 0x76, 0x1c, 0x10, 0x1b, 0xd8, 0x53, 0xd4, 0xe8, 0x07, 0xed,
62 0xea, 0x91, 0xd4, 0x1b, 0x91, 0x5c, 0x28, 0x05, 0xca, 0xe2, 0x9c, 0xd1, 0x99, 0x43, 0xed, 0xd8,
63 0x6a, 0x2b, 0xd2, 0x64, 0x9b, 0xe1, 0x0c, 0x88, 0x6c, 0x0d, 0xb2, 0x6b, 0x73, 0x85, 0x9d, 0xbf,
64 0x79, 0x78, 0xaa, 0x7b, 0x5e, 0xf8, 0xa4, 0x26, 0xdf, 0xb3, 0x9b, 0x24, 0x5a, 0xc8, 0x19, 0x22,
65 0xa5, 0xc6, 0xca, 0x00, 0x59, 0x3b, 0xad, 0x45, 0xdf, 0x71, 0x1d, 0x60, 0x60, 0x24, 0x0d, 0xa4,
66 0x3d, 0x42, 0x23, 0xb8, 0xfe, 0xac, 0x86, 0x94, 0x79, 0x87, 0x05, 0xae, 0xb8, 0x4d, 0x7e, 0x11,
67 0x5b, 0x22, 0x44, 0x15, 0x3d, 0x7f, 0x82, 0x98, 0x65, 0x0a, 0x3c, 0xd3, 0xef, 0x80, 0x0d, 0x75,
68 0x03, 0x92, 0xf6, 0x3a, 0x8b, 0xa4, 0xb0, 0x61, 0x2d, 0x2c, 0xcc, 0x1f, 0x01, 0x8e, 0x7a, 0x46,
69 0x36, 0x2a, 0x83, 0x21, 0x88, 0x98, 0x13, 0x0a, 0xd5, 0xa1, 0x54, 0x4b, 0x63, 0xe0, 0xe3, 0x1c,
70 0x07, 0x5e, 0x32, 0x8c, 0xa4, 0x6b, 0x62, 0xc3, 0x28, 0x95, 0xb8, 0x0a, 0xb9, 0x4f, 0xaf, 0x7f,
71 0x49, 0xeb, 0xff, 0xd7, 0xa1, 0x41, 0x43, 0x9a, 0x92, 0x9a, 0x5f, 0xee, 0xbd, 0xb9, 0xbe, 0xb3,
72 0x4b, 0x9d, 0x0b, 0xcb, 0x9b, 0x2c, 0x26, 0x8f, 0x0f, 0xb1, 0xfa, 0xc0, 0xe3, 0x3a, 0x7f, 0x2b,
73 0x51, 0x79, 0x75, 0x25, 0x5b, 0x23, 0x22, 0xb1, 0x01, 0x27, 0x4e, 0x43, 0xdd, 0x66, 0x7a, 0x33,
74 0x4d, 0x32, 0x96, 0x83, 0x59, 0x52, 0xd7, 0x3c, 0xb0, 0xe3, 0x03, 0xd6, 0xb0, 0xc7, 0x99, 0x68,
75 0xc6, 0xa4, 0x2d, 0x35, 0x3d, 0xa4, 0x6a, 0x17, 0xd9, 0xf4, 0x0c, 0x26, 0x11, 0xe4, 0xbc, 0x03,
76 0x87, 0x25, 0x62, 0xad, 0xa9, 0x7e, 0x96, 0x4d, 0x39, 0x9b, 0x8f, 0x09, 0xdc, 0xd1, 0x28, 0x5e,
77 0xf4, 0xe3, 0x94, 0xfd, 0x94, 0x46, 0x30, 0xe2, 0x24, 0x46, 0x30, 0x7f, 0xf4, 0x4c, 0xaa, 0x51,
78 0x7e, 0x04, 0x5c, 0xa4, 0x8c, 0xba, 0x4a, 0xb8, 0x61, 0x5e, 0x75, 0x1c, 0xa8, 0x0c, 0xbc, 0x7f,
79 0x36, 0x16, 0xa1, 0x72, 0x98, 0x6a, 0x44, 0x39, 0x42, 0x67, 0xb5, 0x4a, 0xac, 0x14, 0x35, 0x8f,
80 0xcd, 0x87, 0x3f, 0x9e, 0x2e, 0xa1, 0x53, 0xf1, 0x45, 0x68, 0x26, 0xcb, 0x35, 0x96, 0x57, 0xd5,
81 0x3a, 0x24, 0x74, 0xe2, 0xff, 0xe0, 0x70, 0xb1, 0xbd, 0xec, 0x0c, 0xd2, 0x97, 0x9a, 0xe5, 0x9f,
82 0xa9, 0xfe, 0x6a, 0x63, 0x17, 0x35, 0xad, 0x64, 0x2f, 0xd9, 0x2e, 0xdb, 0x47, 0xdc, 0x62, 0xdc,
83 0xcc, 0xee, 0x7e, 0x23, 0xa6, 0x67, 0x61, 0x7c, 0xd1, 0x03, 0xbd, 0x78, 0xe9, 0x34, 0x05, 0xed,
84 0x05, 0x87, 0xef, 0x59, 0xf4, 0x16, 0xd6, 0x8d, 0x85, 0x46, 0x65, 0x2a, 0x08, 0xac, 0x4a, 0x5d,
85 0xe6, 0x27, 0x5f, 0x43, 0xdd, 0x51, 0x4e, 0x95, 0x9b, 0xf5, 0x0c, 0x81, 0x24, 0x73, 0x39, 0x77,
86 0xe9, 0xc8, 0x35, 0x4a, 0xe2, 0xb8, 0x35, 0x92, 0xde, 0x5c, 0x31, 0x12, 0x36, 0x5c, 0xc7, 0x69,
87 0xcd, 0x79, 0xa9, 0xf9, 0xcf, 0x13, 0xa9, 0x12, 0x29, 0x25, 0x5c, 0x6a, 0x34, 0xa4, 0xbf, 0xc5,
88 0xb6, 0x2a, 0xc1, 0xba, 0x6a, 0xd3, 0x98, 0x8c, 0x9b, 0x6d, 0x9f, 0xb9, 0x25, 0xa6, 0xd1, 0x97,
89 0x80, 0x38, 0x11, 0xdc, 0x73, 0x5c, 0xe7, 0x3a, 0x1f, 0xd2, 0x16, 0xcd, 0x63, 0xfb, 0x41, 0xb0,
90 0xba, 0xb0, 0x38, 0x67, 0x48, 0xd2, 0x8a, 0x94, 0x2f, 0x11, 0x81, 0xbf, 0x66, 0x38, 0x68, 0xff,
91 0xfe, 0xd1, 0x7c, 0xcd, 0xa3, 0xac, 0xe4, 0xf7, 0x58, 0x19, 0xcd, 0x2a, 0xe3, 0xfa, 0x4d, 0xb0,
92 0xbe, 0xac, 0x05, 0x1c, 0xd9, 0x8d, 0xf7, 0x5c, 0xc0, 0xfc, 0xa6, 0xb5, 0x99, 0xb8, 0x8e, 0x2b,
93 0x72, 0xf8, 0x19, 0xfc, 0x17, 0x11, 0xf6, 0x2b, 0x08, 0xe4, 0x6e, 0xb0, 0x65, 0xab, 0x78, 0x8a,
94 0xfc, 0x7c, 0x09, 0xca, 0x73, 0xcd, 0x35, 0x5d, 0x6c, 0x7a, 0x36, 0xc0, 0x24, 0xba, 0x3f, 0x08,
95 0xea, 0x17, 0x09, 0xe1, 0x9d, 0x5d, 0x18, 0x59, 0x8a, 0xd8, 0x6a, 0x6d, 0x85, 0x6a, 0x9e, 0xa9,
96 0xe5, 0x4b, 0x45, 0xb2, 0x35, 0x6e, 0x62, 0x24, 0x08, 0x00, 0x1c, 0x06, 0x73, 0x27, 0x5d, 0x11,
97 0x4a, 0xc8, 0x51, 0xbd, 0x59, 0xd6, 0x94, 0xce, 0x16, 0x15, 0x17, 0x58, 0x7f, 0x39, 0x9d, 0x4e,
98 0x69, 0x1a, 0x64, 0xbb, 0xd4, 0x51, 0xb9, 0xe4, 0x7d, 0x51, 0x3a, 0xff, 0xe5, 0x1f, 0x29, 0xea,
99 0x7e, 0xa5, 0x62, 0x63, 0xff, 0x10, 0xf7, 0x54, 0x35, 0xd1, 0xf3, 0x73, 0x1e, 0xab, 0xca, 0x52,
100 0x14, 0xc6, 0x7e, 0x51, 0xc2, 0x48, 0x13, 0xcb, 0x30, 0xb2, 0x1a, 0x84, 0x72, 0xe5, 0x44, 0x83,
101 0xc9, 0x90, 0xa5, 0x8c, 0xf9, 0xeb, 0x3c, 0x5c, 0xc6, 0xcc, 0x8a, 0x95, 0x8a, 0xfa, 0xeb, 0x37,
102 0x9c, 0xde, 0xa2, 0xb1, 0x72, 0x4d, 0xd9, 0x3d, 0xab, 0xfd, 0x0e, 0xbd, 0x32, 0x9d, 0x23, 0xe9,
103 0x6f, 0x85, 0x4e, 0xfe, 0xcd, 0x91, 0xfb, 0x82, 0x94, 0xee, 0x8b, 0xdf, 0x6a, 0xd9, 0x01, 0xa1,
104 0xc6, 0x22, 0x18, 0x01, 0x8d, 0x10, 0xd5, 0x87, 0x42, 0xd0, 0xbd, 0x23, 0x75, 0x44, 0x53, 0x46,
105 0xa5, 0xae, 0x00, 0x4c, 0x0e, 0x88, 0x4a, 0xa8, 0x3d, 0x4a, 0x30, 0xe0, 0x1a, 0xa4, 0xe5, 0x40,
106 0xb8, 0xe0, 0x12, 0x9c, 0x44, 0x03, 0xfb, 0x2e, 0x4e, 0xf5, 0x29, 0xdb, 0x09, 0x84, 0x55, 0xc7,
107 0x6c, 0xc6, 0x1f, 0xf9, 0xee, 0x0b, 0xa4, 0x91, 0x7d, 0x79, 0x27, 0x59, 0x75, 0x97, 0xec, 0x6a,
108 0xa8, 0xf8, 0x55, 0xa8, 0x45, 0xd4, 0xd7, 0xa6, 0xc1, 0xc4, 0x27, 0x35, 0xe8, 0x4f, 0x39, 0x89,
109 0x7d, 0x41, 0xf3, 0xf6, 0xd0, 0xb6, 0xf9, 0x91, 0xeb, 0x94, 0xf1, 0xbb, 0x17, 0x46, 0x9c, 0xd5,
110 0x5a, 0x53, 0x04, 0x2d, 0x12, 0x7c, 0x17, 0x6a, 0x36, 0xb5, 0xea, 0xf3, 0x5b, 0x96, 0x1b, 0xee,
111 0xce, 0xc4, 0xc0, 0x11, 0x5a, 0xbc, 0x0c, 0x29, 0xd0, 0x42, 0x1d, 0x16, 0x63, 0xea, 0x1e, 0x04,
112 0x2f, 0xe3, 0x17, 0xed, 0x33, 0xac, 0x56, 0x80, 0x34, 0x41, 0x41, 0x1e, 0x77, 0x80, 0x06, 0x9f,
113 0xbc, 0x2e, 0x78, 0xa1, 0x04, 0x00, 0x06, 0x6f, 0x36, 0x2f, 0xb7, 0xa5, 0x95, 0x37, 0x82, 0x9d,
114 0xef, 0x41, 0x08, 0x85, 0x3d, 0x53, 0xa7, 0xfb, 0xfe, 0xba, 0x8c, 0xb9, 0xae, 0xc7, 0x89, 0x11,
115 0x69, 0x4f, 0x62, 0xe6, 0xb6, 0x08, 0x6b, 0x35, 0x1c, 0x96, 0xb3, 0x7b, 0x40, 0x2d, 0xee, 0x07,
116 0x40, 0x52, 0x4f, 0x68, 0x60, 0xf4, 0xb9, 0xc3, 0x54, 0x9f, 0x22, 0x50, 0x88, 0x48, 0x6a, 0x28,
117 0x93, 0x46, 0x00, 0xe2, 0x4a, 0x85, 0x41, 0x78, 0x0e, 0x87, 0xc5, 0xeb, 0xfc, 0xd3, 0x5f, 0x4d,
118 0x24, 0xe4, 0x9d, 0xeb, 0x1d, 0x00, 0x73, 0x85, 0x25, 0x47, 0x9e, 0x8c, 0x5b, 0x88, 0xf4, 0x3b,
119 0x33, 0xf0, 0x3d, 0x3a, 0xa1, 0x28, 0xd3, 0x06, 0xb4, 0x7a, 0x4e, 0x5d, 0x31, 0x1b, 0xca, 0xf4,
120 0x3f, 0x70, 0x30, 0x49, 0x44, 0x29, 0x24, 0x14, 0x5e, 0x35, 0xc2, 0x6c, 0x92, 0x7e, 0xf8, 0x97,
121 0x0c, 0x51, 0x9d, 0x67, 0xc0, 0x10, 0xa9, 0x35, 0x48, 0x59, 0x6a, 0x33, 0xef, 0x40, 0x4e, 0x53,
122 0x10, 0x14, 0x2a, 0x12, 0x38, 0xe6, 0xc4, 0x63, 0x9c, 0x84, 0x85, 0x06, 0xaf, 0x3d, 0x3a, 0x84,
123 0x06, 0x60, 0x88, 0x32, 0xda, 0x2c, 0xe5, 0xc6, 0x59, 0xf1, 0xe0, 0x10, 0xe2, 0x3c, 0xe6, 0xbf,
124 0x32, 0x7d, 0x32, 0x39, 0x6d, 0xe4, 0xd9, 0xca, 0xe7, 0xf5, 0xf4, 0xa6, 0x5f, 0xb2, 0x33, 0x05,
125 0xb5, 0xad, 0x5f, 0xcb, 0x0b, 0x14, 0xaf, 0xeb, 0xc0, 0xec, 0x87, 0x85, 0x9b, 0x13, 0xb5, 0x8a,
126 0x98, 0xa9, 0x92, 0x13, 0x1b, 0x74, 0xec, 0xfd, 0xe1, 0xc1, 0x22, 0x06, 0x5d, 0x4f, 0x06, 0xc7,
127 0xdd, 0xc6, 0xf0, 0xc4, 0x01, 0x04, 0xad, 0x7f, 0x71, 0xbc, 0x74, 0x4d, 0xfd, 0x18, 0xa3, 0x56,
128 0x2c, 0x45, 0x28, 0x2b, 0x2f, 0xbc, 0x9b, 0xb8, 0x4b, 0xe6, 0x51, 0x75, 0x28, 0x0c, 0x27, 0x0e,
129 0xf7, 0x92, 0x8c, 0xc9, 0xde, 0x33, 0x1a, 0x65, 0x28, 0xc7, 0x01, 0x32, 0xa2, 0x36, 0x88, 0xb6,
130 0x64, 0x10, 0x03, 0xd6, 0xb7, 0x9f, 0x9d, 0x73, 0xe1, 0xa9, 0xc7, 0xdf, 0xe1, 0x0b, 0x39, 0x31,
131 0x77, 0xbc, 0x91, 0xf1, 0x45, 0x9a, 0xc5, 0x97, 0x28, 0xc0, 0x61, 0xc5, 0x23, 0x54, 0xad, 0xe3,
132 0x23, 0x18, 0x69, 0xf7, 0x27, 0xd0, 0x5b, 0xf2, 0x44, 0x62, 0xdc, 0x97, 0xce, 0x4e, 0x40, 0x76,
133 0x00, 0xde, 0xc2, 0xf9, 0x3a, 0x42, 0xfd, 0xd4, 0xd7, 0xe1, 0x85, 0xd8, 0xc9, 0x38, 0x91, 0xc1,
134 0x79, 0x87, 0x58, 0xf1, 0x26, 0x1a, 0x29, 0x02, 0xe3, 0x54, 0xde, 0x58, 0x64, 0x9d, 0xe6, 0x8e,
135 0x33, 0x70, 0x53, 0x43, 0x47, 0x90, 0xee, 0x6e, 0x0f, 0x8c, 0xb3, 0x9e, 0x47, 0x45, 0xfc, 0xa8,
136 0xe3, 0x52, 0x62, 0x74, 0x6d, 0xa2, 0xaf, 0x28, 0x9d, 0xdf, 0x1e, 0x69, 0x1f, 0x56, 0xbc, 0x49,
137 0xc1, 0xe5, 0xd6, 0xc4, 0xb5, 0x5c, 0x4d, 0x39, 0x49, 0x4b, 0xb4, 0xec, 0x56, 0x54, 0x9a, 0x15,
138 0x94, 0x0a, 0xcb, 0xa9, 0x10, 0x46, 0x03, 0x5c, 0x23, 0x2f, 0x29, 0xed, 0x72, 0xa1, 0x57, 0xfa,
139 0x58, 0xef, 0x21, 0x7e, 0xf2, 0x8b, 0xa7, 0x04, 0x51, 0xb4, 0x03, 0x5d, 0xd8, 0x48, 0xc0, 0xe5,
140 0x83, 0xb6, 0x7a, 0x6b, 0xcd, 0xfb, 0xda, 0x47, 0xe8, 0xa1, 0xae, 0x57, 0x74, 0x49, 0xc0, 0xf9,
141 0x4a, 0x6b, 0x3c, 0xb5, 0xd8, 0x27, 0x3d, 0x1d, 0x96, 0x39, 0x09, 0x65, 0x95, 0xdb, 0x01, 0xa3,
142 0x8b, 0x78, 0x8b, 0x07, 0x6d, 0x1c, 0x8b, 0x4b, 0x1d, 0x9d, 0x4a, 0x4f, 0xcb, 0xb8, 0xf6, 0x22,
143 0x73, 0x8a, 0x7b, 0xc8, 0xf2, 0x0a, 0xef, 0x03, 0x1e, 0xb7, 0x4d, 0x8f, 0xc0, 0xdf, 0x87, 0x88,
144 0x05, 0xe1, 0x0a, 0x30, 0xea, 0xde, 0xf3, 0xc2, 0xb6, 0x00, 0x3c, 0xd6, 0xff, 0x3b, 0xb5, 0x01,
145 0xfb, 0xd8, 0xb2, 0x65, 0x26, 0x5d, 0xa0, 0x5a, 0x7c, 0xef, 0x1d, 0x85, 0xbe, 0x51, 0xc2, 0x57,
146 0x0b, 0x27, 0x37, 0x71, 0x99, 0xf5, 0x87, 0x83, 0x68, 0x0b, 0x88, 0xed, 0x66, 0x9e, 0x37, 0x59,
147 0x84, 0x23, 0x72, 0xc3, 0x80, 0xac, 0xfe, 0x45, 0x5f, 0xdf, 0x31, 0xc4, 0x84, 0x07, 0x5a, 0x17,
148 0x28, 0xcd, 0x64, 0xb4, 0xe2, 0xa3, 0x0e, 0x2c, 0x15, 0x60, 0x77, 0xdc, 0x08, 0x45, 0x36, 0x37,
149 0x68, 0x50, 0xba, 0x03, 0x85, 0xb7, 0xed, 0xd0, 0x7b, 0xb2, 0xa1, 0x62, 0xbc, 0x70, 0x00, 0x9e,
150];
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct UpdateCheck {
155 pub current_version: String,
156 pub latest_version: String,
157 pub update_available: bool,
158 pub download_url: Option<String>,
159 #[serde(default = "default_channel")]
163 pub channel: UpgradeChannel,
164}
165
166fn default_channel() -> UpgradeChannel {
168 UpgradeChannel::Stable
169}
170
171#[must_use]
178pub fn channel_for_version(version: &str) -> UpgradeChannel {
179 match parse_version(version) {
180 Ok(v) if v.pre.as_str().split('.').next() == Some("beta") => UpgradeChannel::Beta,
181 _ => UpgradeChannel::Stable,
182 }
183}
184
185impl UpdateCheck {
186 pub fn force(&mut self) -> Result<()> {
190 self.update_available = true;
191 self.download_url = Some(build_download_url(&self.latest_version)?);
192 Ok(())
193 }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct UpdateResult {
199 pub previous_version: String,
200 pub new_version: String,
201}
202
203pub async fn check_for_update(
209 current_version: &str,
210 channel: UpgradeChannel,
211) -> Result<UpdateCheck> {
212 let latest = fetch_latest_cli_version(channel).await?;
213 let current = parse_version(current_version)?;
214 let latest_parsed = parse_version(&latest)?;
215 let update_available = latest_parsed > current;
216
217 let download_url = if update_available {
218 Some(build_download_url(&latest)?)
219 } else {
220 None
221 };
222
223 Ok(UpdateCheck {
224 current_version: current_version.to_string(),
225 latest_version: latest,
226 update_available,
227 download_url,
228 channel,
229 })
230}
231
232pub async fn perform_update(
238 check: &UpdateCheck,
239 progress: &dyn ProgressReporter,
240) -> Result<UpdateResult> {
241 let download_url = check.download_url.as_deref().ok_or_else(|| {
242 Error::UpdateFailed("no download URL — are you already on the latest version?".to_string())
243 })?;
244
245 let tmp_dir = tempfile::tempdir()
246 .map_err(|e| Error::UpdateFailed(format!("failed to create temp directory: {e}")))?;
247
248 let (archive_path, archive_bytes) =
249 download_archive(download_url, tmp_dir.path(), progress).await?;
250
251 let sig_url = format!("{download_url}.sig");
252 progress.report_started("Downloading signature...");
253 let sig_bytes = download_bytes(&sig_url).await?;
254
255 progress.report_started("Verifying ML-DSA signature...");
256 verify_signature(&archive_bytes, &sig_bytes)?;
257 progress.report_complete("Signature verified");
258
259 progress.report_started("Extracting archive...");
260 let extracted = if download_url.ends_with(".zip") {
261 extract_zip(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
262 } else {
263 extract_tar_gz(&archive_bytes, tmp_dir.path(), CLI_BINARY_NAME)?
264 };
265 let binary_path = extracted.binary_path;
266
267 let actual_version = extract_version(&binary_path).await;
269 if let Ok(ref v) = actual_version {
270 if v != &check.latest_version {
271 return Err(Error::UpdateFailed(format!(
272 "version mismatch: expected {}, binary reports {v}",
273 check.latest_version
274 )));
275 }
276 }
277
278 replace_binary(&binary_path)?;
279
280 let _ = std::fs::remove_file(&archive_path);
283
284 Ok(UpdateResult {
285 previous_version: check.current_version.clone(),
286 new_version: check.latest_version.clone(),
287 })
288}
289
290async fn fetch_latest_cli_version(channel: UpgradeChannel) -> Result<String> {
295 let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases?per_page=100");
297 let client = reqwest::Client::new();
298 let resp = client
299 .get(&url)
300 .header("User-Agent", "ant-cli")
301 .header("Accept", "application/vnd.github+json")
302 .send()
303 .await
304 .map_err(|e| Error::UpdateFailed(format!("failed to fetch releases: {e}")))?;
305
306 if !resp.status().is_success() {
307 return Err(Error::UpdateFailed(format!(
308 "GitHub API returned status {} when fetching releases",
309 resp.status()
310 )));
311 }
312
313 let releases: Vec<serde_json::Value> = resp
314 .json()
315 .await
316 .map_err(|e| Error::UpdateFailed(format!("failed to parse releases JSON: {e}")))?;
317
318 select_channel_version(&releases, channel).ok_or_else(|| {
319 Error::UpdateFailed(format!(
320 "no ant-cli release found on GitHub for the {channel} channel"
321 ))
322 })
323}
324
325fn select_channel_version(
333 releases: &[serde_json::Value],
334 channel: UpgradeChannel,
335) -> Option<String> {
336 let mut best: Option<semver::Version> = None;
337
338 for release in releases {
339 if release["draft"].as_bool().unwrap_or(false) {
340 continue;
341 }
342
343 let tag = release["tag_name"].as_str().unwrap_or_default();
344 let Some(version_str) = tag.strip_prefix(TAG_PREFIX) else {
345 continue;
346 };
347 let Ok(version) = semver::Version::parse(version_str) else {
348 continue;
349 };
350
351 if !version_matches_channel(&version, channel) {
352 continue;
353 }
354
355 if best.as_ref().is_none_or(|b| version > *b) {
356 best = Some(version);
357 }
358 }
359
360 best.map(|v| v.to_string())
361}
362
363async fn download_archive(
368 url: &str,
369 tmp_dir: &Path,
370 progress: &dyn ProgressReporter,
371) -> Result<(PathBuf, Vec<u8>)> {
372 progress.report_started(&format!("Downloading {CLI_BINARY_NAME} from {url}"));
373
374 let client = reqwest::Client::new();
375 let resp = client
376 .get(url)
377 .header("User-Agent", "ant-cli")
378 .send()
379 .await
380 .map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
381
382 if !resp.status().is_success() {
383 return Err(Error::UpdateFailed(format!(
384 "download returned status {}",
385 resp.status()
386 )));
387 }
388
389 let total_size = resp.content_length().unwrap_or(0);
390 let mut downloaded: u64 = 0;
391
392 let tmp_archive = tmp_dir.join(".download.tmp");
393 let mut tmp_file = std::fs::File::create(&tmp_archive)
394 .map_err(|e| Error::UpdateFailed(format!("failed to create temp file: {e}")))?;
395
396 let mut stream = resp.bytes_stream();
397 while let Some(chunk) = stream.next().await {
398 let chunk =
399 chunk.map_err(|e| Error::UpdateFailed(format!("download stream error: {e}")))?;
400 downloaded += chunk.len() as u64;
401 std::io::Write::write_all(&mut tmp_file, &chunk)
402 .map_err(|e| Error::UpdateFailed(format!("failed to write temp file: {e}")))?;
403 progress.report_progress(downloaded, total_size);
404 }
405 drop(tmp_file);
406
407 progress.report_complete("Download complete");
408
409 let bytes = std::fs::read(&tmp_archive)
410 .map_err(|e| Error::UpdateFailed(format!("failed to read temp file: {e}")))?;
411
412 Ok((tmp_archive, bytes))
413}
414
415async fn download_bytes(url: &str) -> Result<Vec<u8>> {
417 let client = reqwest::Client::new();
418 let resp = client
419 .get(url)
420 .header("User-Agent", "ant-cli")
421 .send()
422 .await
423 .map_err(|e| Error::UpdateFailed(format!("download request failed: {e}")))?;
424
425 if !resp.status().is_success() {
426 return Err(Error::UpdateFailed(format!(
427 "download returned status {} for {url}",
428 resp.status()
429 )));
430 }
431
432 resp.bytes()
433 .await
434 .map(|b| b.to_vec())
435 .map_err(|e| Error::UpdateFailed(format!("failed to read response body: {e}")))
436}
437
438fn verify_signature(archive_bytes: &[u8], signature_bytes: &[u8]) -> Result<()> {
440 if signature_bytes.len() != SIGNATURE_SIZE {
441 return Err(Error::UpdateFailed(format!(
442 "invalid signature size: expected {SIGNATURE_SIZE}, got {}",
443 signature_bytes.len()
444 )));
445 }
446
447 let public_key = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, RELEASE_SIGNING_KEY)
448 .map_err(|e| Error::UpdateFailed(format!("invalid embedded release key: {e}")))?;
449
450 let sig = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, signature_bytes)
451 .map_err(|e| Error::UpdateFailed(format!("invalid signature format: {e}")))?;
452
453 let dsa = ml_dsa_65();
454 let valid = dsa
455 .verify_with_context(&public_key, archive_bytes, &sig, SIGNING_CONTEXT)
456 .map_err(|e| Error::UpdateFailed(format!("signature verification error: {e}")))?;
457
458 if valid {
459 Ok(())
460 } else {
461 Err(Error::UpdateFailed(
462 "signature verification failed: archive may be corrupted or tampered".to_string(),
463 ))
464 }
465}
466
467async fn extract_version(binary_path: &Path) -> Result<String> {
469 let mut cmd = tokio::process::Command::new(binary_path);
470 cmd.arg("--version");
471 #[cfg(windows)]
475 {
476 const CREATE_NO_WINDOW: u32 = 0x08000000;
477 cmd.creation_flags(CREATE_NO_WINDOW);
478 }
479 let output = cmd.output().await.map_err(|e| {
480 Error::UpdateFailed(format!(
481 "failed to run {} --version: {e}",
482 binary_path.display()
483 ))
484 })?;
485
486 if !output.status.success() {
487 return Err(Error::UpdateFailed(format!(
488 "{} --version exited with status {}",
489 binary_path.display(),
490 output.status
491 )));
492 }
493
494 let stdout = String::from_utf8_lossy(&output.stdout);
495 Ok(parse_version_from_stdout(&stdout).to_string())
496}
497
498fn parse_version_from_stdout(stdout: &str) -> &str {
506 stdout
507 .lines()
508 .next()
509 .unwrap_or_default()
510 .split_whitespace()
511 .last()
512 .unwrap_or("unknown")
513}
514
515fn replace_binary(new_binary: &Path) -> Result<()> {
520 self_replace::self_replace(new_binary)
521 .map_err(|e| Error::UpdateFailed(format!("failed to replace binary: {e}")))?;
522 Ok(())
523}
524
525pub fn build_download_url(version: &str) -> Result<String> {
530 let asset_name = cli_platform_asset_name(version)?;
531 Ok(format!(
532 "https://github.com/{GITHUB_REPO}/releases/download/{TAG_PREFIX}{version}/{asset_name}"
533 ))
534}
535
536fn cli_platform_asset_name(version: &str) -> Result<String> {
540 let target_triple = if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
541 "x86_64-unknown-linux-musl"
542 } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
543 "aarch64-unknown-linux-musl"
544 } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
545 "x86_64-apple-darwin"
546 } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
547 "aarch64-apple-darwin"
548 } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
549 "x86_64-pc-windows-msvc"
550 } else {
551 return Err(Error::UpdateFailed(format!(
552 "unsupported platform: {}-{}",
553 std::env::consts::OS,
554 std::env::consts::ARCH
555 )));
556 };
557
558 let ext = if cfg!(target_os = "windows") {
559 "zip"
560 } else {
561 "tar.gz"
562 };
563
564 Ok(format!("ant-{version}-{target_triple}.{ext}"))
565}
566
567fn parse_version(version: &str) -> Result<semver::Version> {
568 let cleaned = version.strip_prefix('v').unwrap_or(version);
569 semver::Version::parse(cleaned)
570 .map_err(|e| Error::UpdateFailed(format!("invalid version '{version}': {e}")))
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn parse_version_from_stdout_single_line() {
579 assert_eq!(parse_version_from_stdout("ant 0.1.4"), "0.1.4");
580 assert_eq!(parse_version_from_stdout("ant 0.1.4\n"), "0.1.4");
581 }
582
583 #[test]
584 fn parse_version_from_stdout_multi_line_license_trailer() {
585 let stdout = "ant 0.1.4\n\
587 Autonomi network client\n\
588 \n\
589 Repository: https://github.com/WithAutonomi/ant-client\n\
590 License: MIT or Apache-2.0\n";
591 assert_eq!(parse_version_from_stdout(stdout), "0.1.4");
592 }
593
594 #[test]
595 fn parse_version_from_stdout_empty() {
596 assert_eq!(parse_version_from_stdout(""), "unknown");
597 }
598
599 #[test]
600 fn parse_version_from_stdout_blank_first_line() {
601 assert_eq!(parse_version_from_stdout("\nant 0.1.4\n"), "unknown");
602 }
603
604 #[test]
605 fn parse_version_from_stdout_leading_whitespace() {
606 assert_eq!(parse_version_from_stdout(" ant 0.1.4\n"), "0.1.4");
607 }
608
609 #[test]
610 fn parse_version_valid() {
611 assert!(parse_version("1.2.3").is_ok());
612 assert!(parse_version("v1.2.3").is_ok());
613 assert!(parse_version("0.1.0").is_ok());
614 }
615
616 #[test]
617 fn parse_version_invalid() {
618 assert!(parse_version("not-a-version").is_err());
619 assert!(parse_version("").is_err());
620 }
621
622 #[test]
623 fn version_comparison() {
624 let v1 = parse_version("0.1.0").unwrap();
625 let v2 = parse_version("0.2.0").unwrap();
626 assert!(v2 > v1);
627
628 let v3 = parse_version("1.0.0").unwrap();
629 assert!(v3 > v2);
630
631 let same = parse_version("0.1.0").unwrap();
632 assert_eq!(v1, same);
633 }
634
635 #[test]
636 fn check_result_no_update() {
637 let check = UpdateCheck {
638 current_version: "1.0.0".to_string(),
639 latest_version: "1.0.0".to_string(),
640 update_available: false,
641 download_url: None,
642 channel: UpgradeChannel::Stable,
643 };
644 assert!(!check.update_available);
645 assert!(check.download_url.is_none());
646 }
647
648 #[test]
649 fn check_result_with_update() {
650 let check = UpdateCheck {
651 current_version: "0.1.0".to_string(),
652 latest_version: "0.2.0".to_string(),
653 update_available: true,
654 download_url: Some("https://example.com/ant.tar.gz".to_string()),
655 channel: UpgradeChannel::Stable,
656 };
657 assert!(check.update_available);
658 assert!(check.download_url.is_some());
659 }
660
661 #[test]
662 fn force_populates_download_url() {
663 let mut check = UpdateCheck {
664 current_version: "1.0.0".to_string(),
665 latest_version: "1.0.0".to_string(),
666 update_available: false,
667 download_url: None,
668 channel: UpgradeChannel::Stable,
669 };
670 check.force().unwrap();
671 assert!(check.update_available);
672 assert!(check.download_url.is_some());
673 }
674
675 #[test]
676 fn platform_asset_name_format() {
677 let name = cli_platform_asset_name("1.2.3").unwrap();
678 assert!(name.starts_with("ant-1.2.3-"));
679 assert!(
680 name.ends_with(".tar.gz") || name.ends_with(".zip"),
681 "unexpected extension: {name}"
682 );
683 }
684
685 #[test]
686 fn build_download_url_format() {
687 let url = build_download_url("1.2.3").unwrap();
688 assert!(url.starts_with(
689 "https://github.com/WithAutonomi/ant-client/releases/download/ant-cli-v1.2.3/ant-1.2.3-"
690 ));
691 assert!(url.ends_with(".tar.gz") || url.ends_with(".zip"));
692 }
693
694 #[test]
695 fn update_check_serializes() {
696 let check = UpdateCheck {
697 current_version: "0.1.0".to_string(),
698 latest_version: "0.2.0".to_string(),
699 update_available: true,
700 download_url: Some("https://example.com/ant.tar.gz".to_string()),
701 channel: UpgradeChannel::Stable,
702 };
703 let json = serde_json::to_string(&check).unwrap();
704 let deserialized: UpdateCheck = serde_json::from_str(&json).unwrap();
705 assert_eq!(deserialized.current_version, "0.1.0");
706 assert!(deserialized.update_available);
707 }
708
709 #[test]
710 fn verify_signature_rejects_wrong_size() {
711 let result = verify_signature(b"some archive data", &[0u8; 100]);
712 assert!(result.is_err());
713 let err = result.unwrap_err().to_string();
714 assert!(err.contains("invalid signature size"), "got: {err}");
715 }
716
717 #[test]
718 fn verify_signature_rejects_invalid_signature() {
719 let invalid_sig = vec![0u8; SIGNATURE_SIZE];
720 let result = verify_signature(b"some archive data", &invalid_sig);
721 assert!(result.is_err());
722 }
723
724 #[test]
725 fn verify_signature_valid_roundtrip() {
726 let dsa = ml_dsa_65();
727 let (public_key, secret_key) = dsa.generate_keypair().unwrap();
728 let archive = b"fake archive content for testing";
729
730 let sig = dsa
731 .sign_with_context(&secret_key, archive, SIGNING_CONTEXT)
732 .unwrap();
733
734 let parsed_sig =
736 MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &sig.to_bytes()).unwrap();
737 let valid = dsa
738 .verify_with_context(&public_key, archive, &parsed_sig, SIGNING_CONTEXT)
739 .unwrap();
740 assert!(valid);
741 }
742
743 fn release(tag: &str) -> serde_json::Value {
744 serde_json::json!({ "tag_name": tag, "draft": false })
745 }
746
747 #[test]
748 fn stable_selects_the_highest_final_release() {
749 let releases = [
750 release("ant-cli-v0.3.3"),
751 release("ant-cli-v0.3.4-beta.1"),
752 release("ant-cli-v0.3.4-rc.1"),
753 ];
754
755 assert_eq!(
756 select_channel_version(&releases, UpgradeChannel::Stable),
757 Some("0.3.3".to_string())
758 );
759 }
760
761 #[test]
762 fn beta_selects_the_beta_over_the_rc() {
763 let releases = [
764 release("ant-cli-v0.3.3"),
765 release("ant-cli-v0.3.4-beta.1"),
766 release("ant-cli-v0.3.4-rc.1"),
767 ];
768
769 assert_eq!(
770 select_channel_version(&releases, UpgradeChannel::Beta),
771 Some("0.3.4-beta.1".to_string())
772 );
773 }
774
775 #[test]
778 fn the_github_prerelease_flag_does_not_hide_a_beta() {
779 let mut beta = release("ant-cli-v0.3.4-beta.1");
780 beta["prerelease"] = serde_json::Value::Bool(true);
781 let releases = [release("ant-cli-v0.3.3"), beta];
782
783 assert_eq!(
784 select_channel_version(&releases, UpgradeChannel::Beta),
785 Some("0.3.4-beta.1".to_string())
786 );
787 }
788
789 #[test]
790 fn drafts_and_foreign_tags_are_skipped() {
791 let mut draft = release("ant-cli-v0.4.0");
792 draft["draft"] = serde_json::Value::Bool(true);
793 let releases = [
794 release("ant-core-v0.9.0"),
795 release("v0.5.0"),
796 draft,
797 release("ant-cli-v0.3.3"),
798 ];
799
800 assert_eq!(
801 select_channel_version(&releases, UpgradeChannel::Stable),
802 Some("0.3.3".to_string())
803 );
804 }
805
806 #[test]
807 fn no_eligible_release_yields_none() {
808 let releases = [release("ant-cli-v0.3.4-rc.1")];
809
810 assert_eq!(
811 select_channel_version(&releases, UpgradeChannel::Beta),
812 None
813 );
814 }
815
816 #[test]
817 fn channel_is_inferred_from_the_running_version() {
818 assert_eq!(channel_for_version("0.3.4-beta.1"), UpgradeChannel::Beta);
819 assert_eq!(channel_for_version("0.3.4-beta"), UpgradeChannel::Beta);
820 assert_eq!(channel_for_version("0.3.3"), UpgradeChannel::Stable);
821 assert_eq!(channel_for_version("0.3.4-rc.1"), UpgradeChannel::Stable);
822 assert_eq!(channel_for_version("0.3.4-alpha.1"), UpgradeChannel::Stable);
823 assert_eq!(
825 channel_for_version("0.3.4-betamax.1"),
826 UpgradeChannel::Stable
827 );
828 assert_eq!(channel_for_version("not-a-version"), UpgradeChannel::Stable);
829 }
830
831 #[test]
833 fn update_check_deserializes_without_a_channel_field() {
834 let json = r#"{"current_version":"0.1.0","latest_version":"0.2.0",
835 "update_available":true,"download_url":null}"#;
836 let check: UpdateCheck = serde_json::from_str(json).unwrap();
837 assert_eq!(check.channel, UpgradeChannel::Stable);
838 }
839}