11//! Primitive cryptographic operations used across various `age` components.
22
3+ use core:: fmt;
4+
5+ use bech32:: primitives:: decode:: CheckedHrpstring ;
36use chacha20poly1305:: {
47 aead:: { self , generic_array:: typenum:: Unsigned , Aead , AeadCore , KeyInit } ,
58 ChaCha20Poly1305 ,
@@ -53,9 +56,73 @@ pub fn hkdf(salt: &[u8], label: &[u8], ikm: &[u8]) -> [u8; 32] {
5356 okm
5457}
5558
59+ /// The bech32 checksum algorithm, defined in [BIP-173].
60+ ///
61+ /// This is identical to [`bech32::Bech32`] except it does not enforce the length
62+ /// restriction, allowing for a reduction in error-correcting properties.
63+ ///
64+ /// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
65+ #[ derive( Copy , Clone , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
66+ enum Bech32Long { }
67+ impl bech32:: Checksum for Bech32Long {
68+ type MidstateRepr = u32 ;
69+ const CODE_LENGTH : usize = usize:: MAX ;
70+ const CHECKSUM_LENGTH : usize = bech32:: Bech32 :: CHECKSUM_LENGTH ;
71+ const GENERATOR_SH : [ u32 ; 5 ] = bech32:: Bech32 :: GENERATOR_SH ;
72+ const TARGET_RESIDUE : u32 = bech32:: Bech32 :: TARGET_RESIDUE ;
73+ }
74+
75+ /// Encodes data as a Bech32-encoded string with the given HRP.
76+ ///
77+ /// This implements Bech32 as defined in [BIP-173], except it does not enforce the length
78+ /// restriction, allowing for a reduction in error-correcting properties.
79+ ///
80+ /// [BIP-173]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
81+ pub fn bech32_encode ( hrp : bech32:: Hrp , data : & [ u8 ] ) -> String {
82+ bech32:: encode_lower :: < Bech32Long > ( hrp, data) . expect ( "we don't enforce the Bech32 length limit" )
83+ }
84+
85+ /// Encodes data to a format writer as a Bech32-encoded string with the given HRP.
86+ ///
87+ /// This implements Bech32 as defined in [BIP-173], except it does not enforce the length
88+ /// restriction, allowing for a reduction in error-correcting properties.
89+ ///
90+ /// [BIP-173]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
91+ pub fn bech32_encode_to_fmt ( f : & mut impl fmt:: Write , hrp : bech32:: Hrp , data : & [ u8 ] ) -> fmt:: Result {
92+ bech32:: encode_lower_to_fmt :: < Bech32Long , _ > ( f, hrp, data) . map_err ( |e| match e {
93+ bech32:: EncodeError :: Fmt ( error) => error,
94+ bech32:: EncodeError :: TooLong ( _) => unreachable ! ( "we don't enforce the Bech32 length limit" ) ,
95+ _ => panic ! ( "Unexpected error: {e}" ) ,
96+ } )
97+ }
98+
99+ /// Decodes a Bech32-encoded string, checks its HRP, and returns its contained data.
100+ ///
101+ /// This implements Bech32 as defined in [BIP-173], except it does not enforce the length
102+ /// restriction, allowing for a reduction in error-correcting properties.
103+ ///
104+ /// [BIP-173]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
105+ pub fn bech32_decode < E , F , G , H , T > (
106+ s : & str ,
107+ parse_err : F ,
108+ hrp_filter : G ,
109+ data_parse : H ,
110+ ) -> Result < T , E >
111+ where
112+ F : FnOnce ( bech32:: primitives:: decode:: CheckedHrpstringError ) -> E ,
113+ G : FnOnce ( bech32:: Hrp ) -> Result < ( ) , E > ,
114+ H : FnOnce ( bech32:: Hrp , bech32:: primitives:: decode:: ByteIter ) -> Result < T , E > ,
115+ {
116+ CheckedHrpstring :: new :: < Bech32Long > ( s)
117+ . map_err ( parse_err)
118+ . and_then ( |parsed| {
119+ hrp_filter ( parsed. hrp ( ) ) . and_then ( |( ) | data_parse ( parsed. hrp ( ) , parsed. byte_iter ( ) ) )
120+ } )
121+ }
122+
56123#[ cfg( test) ]
57124mod tests {
58- use super :: { aead_decrypt, aead_encrypt} ;
125+ use super :: { aead_decrypt, aead_encrypt, bech32_decode , bech32_encode } ;
59126
60127 #[ test]
61128 fn aead_round_trip ( ) {
@@ -65,4 +132,19 @@ mod tests {
65132 let decrypted = aead_decrypt ( & key, plaintext. len ( ) , & encrypted) . unwrap ( ) ;
66133 assert_eq ! ( decrypted, plaintext) ;
67134 }
135+
136+ #[ test]
137+ fn bech32_round_trip ( ) {
138+ let hrp = bech32:: Hrp :: parse_unchecked ( "12345678" ) ;
139+ let data = [ 14 ; 32 ] ;
140+ let encoded = bech32_encode ( hrp, & data) ;
141+ let decoded = bech32_decode (
142+ & encoded,
143+ |_| ( ) ,
144+ |parsed_hrp| ( parsed_hrp == hrp) . then_some ( ( ) ) . ok_or ( ( ) ) ,
145+ |_, bytes| Ok ( bytes. collect :: < Vec < _ > > ( ) ) ,
146+ )
147+ . unwrap ( ) ;
148+ assert_eq ! ( decoded, data) ;
149+ }
68150}
0 commit comments