Procházet zdrojové kódy

Update the signatory.proto file to match NUT-XXX (#1032)

* Update the signatory.proto file to match NUT-XXX

Source: https://github.com/cashubtc/nuts/pull/250/files

* Add unit tests as requested in https://github.com/cashubtc/cdk/pull/1032#discussion_r2321436860

* Remove unused types from proto file
C před 1 měsícem
rodič
revize
ded165f405

+ 7 - 8
crates/cashu/src/nuts/nut02.rs

@@ -553,13 +553,12 @@ impl MintKeySet {
         secp: &Secp256k1<C>,
         xpriv: Xpriv,
         unit: CurrencyUnit,
-        max_order: u8,
+        amounts: &[u64],
         final_expiry: Option<u64>,
         version: KeySetVersion,
     ) -> Self {
         let mut map = BTreeMap::new();
-        for i in 0..max_order {
-            let amount = Amount::from(2_u64.pow(i as u32));
+        for (i, amount) in amounts.iter().enumerate() {
             let secret_key = xpriv
                 .derive_priv(
                     secp,
@@ -569,7 +568,7 @@ impl MintKeySet {
                 .private_key;
             let public_key = secret_key.public_key(secp);
             map.insert(
-                amount,
+                amount.into(),
                 MintKeyPair {
                     secret_key: secret_key.into(),
                     public_key: public_key.into(),
@@ -594,7 +593,7 @@ impl MintKeySet {
     pub fn generate_from_seed<C: secp256k1::Signing>(
         secp: &Secp256k1<C>,
         seed: &[u8],
-        max_order: u8,
+        amounts: &[u64],
         currency_unit: CurrencyUnit,
         derivation_path: DerivationPath,
         final_expiry: Option<u64>,
@@ -607,7 +606,7 @@ impl MintKeySet {
                 .derive_priv(secp, &derivation_path)
                 .expect("RNG busted"),
             currency_unit,
-            max_order,
+            amounts,
             final_expiry,
             version,
         )
@@ -617,7 +616,7 @@ impl MintKeySet {
     pub fn generate_from_xpriv<C: secp256k1::Signing>(
         secp: &Secp256k1<C>,
         xpriv: Xpriv,
-        max_order: u8,
+        amounts: &[u64],
         currency_unit: CurrencyUnit,
         derivation_path: DerivationPath,
         final_expiry: Option<u64>,
@@ -629,7 +628,7 @@ impl MintKeySet {
                 .derive_priv(secp, &derivation_path)
                 .expect("RNG busted"),
             currency_unit,
-            max_order,
+            amounts,
             final_expiry,
             version,
         )

+ 1 - 1
crates/cashu/src/quote_id.rs

@@ -48,7 +48,7 @@ impl From<Uuid> for QuoteId {
 impl fmt::Display for QuoteId {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            QuoteId::BASE64(s) => write!(f, "{}", s),
+            QuoteId::BASE64(s) => write!(f, "{s}"),
             QuoteId::UUID(u) => write!(f, "{}", u.hyphenated()),
         }
     }

+ 1 - 0
crates/cdk-common/src/database/mint/test.rs

@@ -29,6 +29,7 @@ where
         derivation_path_index: Some(0),
         max_order: 32,
         input_fee_ppk: 0,
+        amounts: vec![],
     };
     let mut writer = db.begin_transaction().await.expect("db.begin()");
     writer.add_keyset_info(keyset_info).await.unwrap();

+ 2 - 0
crates/cdk-common/src/mint.rs

@@ -313,6 +313,8 @@ pub struct MintKeySetInfo {
     pub derivation_path_index: Option<u32>,
     /// Max order of keyset
     pub max_order: u8,
+    /// Supported amounts
+    pub amounts: Vec<u64>,
     /// Input Fee ppk
     #[serde(default = "default_fee")]
     pub input_fee_ppk: u64,

+ 6 - 5
crates/cdk-signatory/src/common.rs

@@ -66,7 +66,7 @@ pub async fn init_keysets(
                     let keyset = MintKeySet::generate_from_xpriv(
                         secp_ctx,
                         xpriv,
-                        highest_index_keyset.max_order,
+                        &highest_index_keyset.amounts,
                         highest_index_keyset.unit.clone(),
                         highest_index_keyset.derivation_path.clone(),
                         highest_index_keyset.final_expiry,
@@ -98,7 +98,7 @@ pub async fn init_keysets(
                         derivation_path,
                         Some(derivation_path_index),
                         unit.clone(),
-                        *max_order,
+                        &highest_index_keyset.amounts,
                         *input_fee_ppk,
                         // TODO: add Mint settings for a final expiry of newly generated keysets
                         None,
@@ -128,7 +128,7 @@ pub fn create_new_keyset<C: secp256k1::Signing>(
     derivation_path: DerivationPath,
     derivation_path_index: Option<u32>,
     unit: CurrencyUnit,
-    max_order: u8,
+    amounts: &[u64],
     input_fee_ppk: u64,
     final_expiry: Option<u64>,
 ) -> (MintKeySet, MintKeySetInfo) {
@@ -138,7 +138,7 @@ pub fn create_new_keyset<C: secp256k1::Signing>(
             .derive_priv(secp, &derivation_path)
             .expect("RNG busted"),
         unit,
-        max_order,
+        amounts,
         final_expiry,
         // TODO: change this to Version01 to generate keysets v2
         cdk_common::nut02::KeySetVersion::Version00,
@@ -151,7 +151,8 @@ pub fn create_new_keyset<C: secp256k1::Signing>(
         final_expiry: keyset.final_expiry,
         derivation_path,
         derivation_path_index,
-        max_order,
+        max_order: 0,
+        amounts: amounts.to_owned(),
         input_fee_ppk,
     };
     (keyset, keyset_info)

+ 9 - 5
crates/cdk-signatory/src/db_signatory.rs

@@ -65,13 +65,17 @@ impl DbSignatory {
                     }
                 };
 
+                let amounts = (0..max_order)
+                    .map(|i| 2_u64.pow(i as u32))
+                    .collect::<Vec<_>>();
+
                 let (keyset, keyset_info) = create_new_keyset(
                     &secp_ctx,
                     xpriv,
                     derivation_path,
                     Some(0),
                     unit.clone(),
-                    max_order,
+                    &amounts,
                     fee,
                     // TODO: add and connect settings for this
                     None,
@@ -132,7 +136,7 @@ impl DbSignatory {
         MintKeySet::generate_from_xpriv(
             &self.secp_ctx,
             self.xpriv,
-            keyset_info.max_order,
+            &keyset_info.amounts,
             keyset_info.unit.clone(),
             keyset_info.derivation_path.clone(),
             keyset_info.final_expiry,
@@ -241,7 +245,7 @@ impl Signatory for DbSignatory {
             derivation_path,
             Some(path_index),
             args.unit.clone(),
-            args.max_order,
+            &args.amounts,
             args.input_fee_ppk,
             // TODO: add and connect settings for this
             None,
@@ -274,7 +278,7 @@ mod test {
         let keyset = MintKeySet::generate_from_seed(
             &Secp256k1::new(),
             seed,
-            2,
+            &[1, 2],
             CurrencyUnit::Sat,
             derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
             None,
@@ -320,7 +324,7 @@ mod test {
         let keyset = MintKeySet::generate_from_xpriv(
             &Secp256k1::new(),
             xpriv,
-            2,
+            &[1, 2],
             CurrencyUnit::Sat,
             derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
             None,

+ 16 - 24
crates/cdk-signatory/src/proto/convert.rs

@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
 
 use cdk_common::secret::Secret;
 use cdk_common::util::hex;
-use cdk_common::{Amount, PublicKey};
+use cdk_common::{Amount, Id, PublicKey};
 use tonic::Status;
 
 use super::*;
@@ -44,7 +44,7 @@ impl TryInto<crate::signatory::SignatoryKeySet> for KeySet {
 
     fn try_into(self) -> Result<crate::signatory::SignatoryKeySet, Self::Error> {
         Ok(crate::signatory::SignatoryKeySet {
-            id: self.id.parse()?,
+            id: Id::from_bytes(&self.id)?,
             unit: self
                 .unit
                 .ok_or(cdk_common::Error::Custom(INTERNAL_ERROR.to_owned()))?
@@ -68,7 +68,7 @@ impl TryInto<crate::signatory::SignatoryKeySet> for KeySet {
 impl From<crate::signatory::SignatoryKeySet> for KeySet {
     fn from(keyset: crate::signatory::SignatoryKeySet) -> Self {
         Self {
-            id: keyset.id.to_string(),
+            id: keyset.id.to_bytes(),
             unit: Some(keyset.unit.into()),
             active: keyset.active,
             input_fee_ppk: keyset.input_fee_ppk,
@@ -80,6 +80,7 @@ impl From<crate::signatory::SignatoryKeySet> for KeySet {
                     .collect(),
             }),
             final_expiry: keyset.final_expiry,
+            version: Default::default(),
         }
     }
 }
@@ -141,7 +142,7 @@ impl From<cdk_common::BlindSignature> for BlindSignature {
         BlindSignature {
             amount: value.amount.into(),
             blinded_secret: value.c.to_bytes().to_vec(),
-            keyset_id: value.keyset_id.to_string(),
+            keyset_id: value.keyset_id.to_bytes(),
             dleq: value.dleq.map(|x| x.into()),
         }
     }
@@ -161,7 +162,7 @@ impl From<cdk_common::Proof> for Proof {
     fn from(value: cdk_common::Proof) -> Self {
         Proof {
             amount: value.amount.into(),
-            keyset_id: value.keyset_id.to_string(),
+            keyset_id: value.keyset_id.to_bytes(),
             secret: value.secret.to_bytes(),
             c: value.c.to_bytes().to_vec(),
         }
@@ -179,9 +180,7 @@ impl TryInto<cdk_common::Proof> for Proof {
 
         Ok(cdk_common::Proof {
             amount: self.amount.into(),
-            keyset_id: self
-                .keyset_id
-                .parse()
+            keyset_id: Id::from_bytes(&self.keyset_id)
                 .map_err(|e| Status::from_error(Box::new(e)))?,
             secret: Secret::new(secret),
             c: cdk_common::PublicKey::from_slice(&self.c)
@@ -199,7 +198,7 @@ impl TryInto<cdk_common::BlindSignature> for BlindSignature {
         Ok(cdk_common::BlindSignature {
             amount: self.amount.into(),
             c: cdk_common::PublicKey::from_slice(&self.blinded_secret)?,
-            keyset_id: self.keyset_id.parse().expect("Invalid keyset id"),
+            keyset_id: Id::from_bytes(&self.keyset_id)?,
             dleq: self.dleq.map(|dleq| dleq.try_into()).transpose()?,
         })
     }
@@ -209,7 +208,7 @@ impl From<cdk_common::BlindedMessage> for BlindedMessage {
     fn from(value: cdk_common::BlindedMessage) -> Self {
         BlindedMessage {
             amount: value.amount.into(),
-            keyset_id: value.keyset_id.to_string(),
+            keyset_id: value.keyset_id.to_bytes(),
             blinded_secret: value.blinded_secret.to_bytes().to_vec(),
         }
     }
@@ -220,9 +219,7 @@ impl TryInto<cdk_common::BlindedMessage> for BlindedMessage {
     fn try_into(self) -> Result<cdk_common::BlindedMessage, Self::Error> {
         Ok(cdk_common::BlindedMessage {
             amount: self.amount.into(),
-            keyset_id: self
-                .keyset_id
-                .parse()
+            keyset_id: Id::from_bytes(&self.keyset_id)
                 .map_err(|e| Status::from_error(Box::new(e)))?,
             blinded_secret: cdk_common::PublicKey::from_slice(&self.blinded_secret)
                 .map_err(|e| Status::from_error(Box::new(e)))?,
@@ -311,10 +308,7 @@ impl TryInto<cdk_common::KeySet> for KeySet {
     type Error = cdk_common::error::Error;
     fn try_into(self) -> Result<cdk_common::KeySet, Self::Error> {
         Ok(cdk_common::KeySet {
-            id: self
-                .id
-                .parse()
-                .map_err(|_| cdk_common::error::Error::Custom("Invalid ID".to_owned()))?,
+            id: Id::from_bytes(&self.id)?,
             unit: self
                 .unit
                 .ok_or(cdk_common::error::Error::Custom(INTERNAL_ERROR.to_owned()))?
@@ -337,7 +331,7 @@ impl From<crate::signatory::RotateKeyArguments> for RotationRequest {
     fn from(value: crate::signatory::RotateKeyArguments) -> Self {
         Self {
             unit: Some(value.unit.into()),
-            max_order: value.max_order.into(),
+            amounts: value.amounts,
             input_fee_ppk: value.input_fee_ppk,
         }
     }
@@ -352,10 +346,7 @@ impl TryInto<crate::signatory::RotateKeyArguments> for RotationRequest {
                 .unit
                 .ok_or(Status::invalid_argument("unit not set"))?
                 .try_into()?,
-            max_order: self
-                .max_order
-                .try_into()
-                .map_err(|_| Status::invalid_argument("Invalid max_order"))?,
+            amounts: self.amounts,
             input_fee_ppk: self.input_fee_ppk,
         })
     }
@@ -364,12 +355,13 @@ impl TryInto<crate::signatory::RotateKeyArguments> for RotationRequest {
 impl From<cdk_common::KeySetInfo> for KeySet {
     fn from(value: cdk_common::KeySetInfo) -> Self {
         Self {
-            id: value.id.into(),
+            id: value.id.to_bytes(),
             unit: Some(value.unit.into()),
             active: value.active,
             input_fee_ppk: value.input_fee_ppk,
             keys: Default::default(),
             final_expiry: value.final_expiry,
+            version: Default::default(),
         }
     }
 }
@@ -379,7 +371,7 @@ impl TryInto<cdk_common::KeySetInfo> for KeySet {
 
     fn try_into(self) -> Result<cdk_common::KeySetInfo, Self::Error> {
         Ok(cdk_common::KeySetInfo {
-            id: self.id.try_into()?,
+            id: Id::from_bytes(&self.id)?,
             unit: self
                 .unit
                 .ok_or(cdk_common::Error::Custom(INTERNAL_ERROR.to_owned()))?

+ 16 - 5
crates/cdk-signatory/src/proto/signatory.proto

@@ -32,7 +32,7 @@ message BlindedMessages {
 // Represents a blinded message
 message BlindedMessage {
   uint64 amount = 1;
-  string keyset_id = 2;
+  bytes keyset_id = 2;
   bytes blinded_secret = 3;
 }
 
@@ -57,12 +57,13 @@ message SignatoryKeysets {
 }
 
 message KeySet {
-  string id = 1;
+  bytes id = 1;
   CurrencyUnit unit = 2;
   bool active = 3;
   uint64 input_fee_ppk = 4;
   Keys keys = 5;
   optional uint64 final_expiry = 6;
+  uint64 version = 7;
 }
 
 message Keys {
@@ -72,7 +73,7 @@ message Keys {
 message RotationRequest {
   CurrencyUnit unit = 1;
   uint64 input_fee_ppk = 2;
-  uint32 max_order = 3;
+  repeated uint64 amounts = 3;
 }
 
 enum CurrencyUnitType {
@@ -99,18 +100,28 @@ message Proofs {
 
 message Proof {
   uint64 amount = 1;
-  string keyset_id = 2;
+  bytes keyset_id = 2;
   bytes secret = 3;
   bytes c = 4;
 }
 
+message ProofDLEQ {
+  bytes e = 1;
+  bytes s = 2;
+  bytes r = 3;
+}
+
+message SigningResponse {
+  Error error = 1;
+  BlindSignatures blind_signatures = 2;
+}
 message BlindSignatures {
   repeated BlindSignature blind_signatures = 1;
 }
 
 message BlindSignature {
   uint64 amount = 1;
-  string keyset_id = 2;
+  bytes keyset_id = 2;
   bytes blinded_secret = 3;
   optional BlindSignatureDLEQ dleq = 4;
 }

+ 2 - 1
crates/cdk-signatory/src/signatory.rs

@@ -43,7 +43,7 @@ pub struct RotateKeyArguments {
     /// Unit
     pub unit: CurrencyUnit,
     /// Max order
-    pub max_order: u8,
+    pub amounts: Vec<u64>,
     /// Input fee
     pub input_fee_ppk: u64,
 }
@@ -110,6 +110,7 @@ impl From<SignatoryKeySet> for MintKeySetInfo {
             derivation_path: Default::default(),
             derivation_path_index: Default::default(),
             max_order: 0,
+            amounts: vec![],
             final_expiry: val.final_expiry,
             valid_from: 0,
         }

+ 2 - 0
crates/cdk-sql-common/src/mint/auth/mod.rs

@@ -284,6 +284,7 @@ where
                 derivation_path,
                 derivation_path_index,
                 max_order,
+                amounts,
                 input_fee_ppk
             FROM
                 keyset
@@ -308,6 +309,7 @@ where
                 derivation_path,
                 derivation_path_index,
                 max_order,
+                amounts,
                 input_fee_ppk
             FROM
                 keyset

+ 2 - 0
crates/cdk-sql-common/src/mint/migrations.rs

@@ -29,4 +29,6 @@ pub static MIGRATIONS: &[(&str, &str, &str)] = &[
     ("sqlite", "20250819200000_remove_request_lookup_kind_constraints.sql", include_str!(r#"./migrations/sqlite/20250819200000_remove_request_lookup_kind_constraints.sql"#)),
     ("sqlite", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/sqlite/20250901090000_add_kv_store.sql"#)),
     ("postgres", "20250901090000_add_kv_store.sql", include_str!(r#"./migrations/postgres/20250901090000_add_kv_store.sql"#)),
+    ("sqlite", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/sqlite/20250903200000_add_signatory_amounts.sql"#)),
+    ("postgres", "20250903200000_add_signatory_amounts.sql", include_str!(r#"./migrations/postgres/20250903200000_add_signatory_amounts.sql"#)),
 ];

+ 1 - 0
crates/cdk-sql-common/src/mint/migrations/postgres/20250903200000_add_signatory_amounts.sql

@@ -0,0 +1 @@
+ALTER TABLE keyset ADD COLUMN amounts TEXT DEFAULT NULL;

+ 33 - 0
crates/cdk-sql-common/src/mint/migrations/sqlite/20250903200000_add_signatory_amounts.sql

@@ -0,0 +1,33 @@
+CREATE TABLE keyset_new (
+  id TEXT PRIMARY KEY,
+  unit TEXT NOT NULL,
+  active BOOL NOT NULL,
+  valid_from INTEGER NOT NULL,
+  valid_to INTEGER,
+  max_order INTEGER NOT NULL,
+  amounts TEXT DEFAULT NULL,
+  input_fee_ppk INTEGER,
+  derivation_path TEXT NOT NULL,
+  derivation_path_index INTEGER
+);
+
+
+INSERT INTO keyset_new SELECT
+    id,
+    unit,
+    active,
+    valid_from,
+    valid_to,
+    max_order,
+    NULL,
+    input_fee_ppk,
+    derivation_path,
+    derivation_path_index
+FROM keyset;
+
+DROP TABLE keyset;
+
+ALTER TABLE keyset_new RENAME TO keyset;
+
+CREATE INDEX unit_index ON keyset(unit);
+CREATE INDEX active_index ON keyset(active);

+ 108 - 3
crates/cdk-sql-common/src/mint/mod.rs

@@ -383,11 +383,11 @@ where
         INSERT INTO
             keyset (
                 id, unit, active, valid_from, valid_to, derivation_path,
-                max_order, input_fee_ppk, derivation_path_index
+                max_order, amounts, input_fee_ppk, derivation_path_index
             )
         VALUES (
             :id, :unit, :active, :valid_from, :valid_to, :derivation_path,
-            :max_order, :input_fee_ppk, :derivation_path_index
+            :max_order, :amounts, :input_fee_ppk, :derivation_path_index
         )
         ON CONFLICT(id) DO UPDATE SET
             unit = excluded.unit,
@@ -396,6 +396,7 @@ where
             valid_to = excluded.valid_to,
             derivation_path = excluded.derivation_path,
             max_order = excluded.max_order,
+            amounts = excluded.amounts,
             input_fee_ppk = excluded.input_fee_ppk,
             derivation_path_index = excluded.derivation_path_index
         "#,
@@ -407,6 +408,7 @@ where
         .bind("valid_to", keyset.final_expiry.map(|v| v as i64))
         .bind("derivation_path", keyset.derivation_path.to_string())
         .bind("max_order", keyset.max_order)
+        .bind("amounts", serde_json::to_string(&keyset.amounts).ok())
         .bind("input_fee_ppk", keyset.input_fee_ppk as i64)
         .bind("derivation_path_index", keyset.derivation_path_index)
         .execute(&self.inner)
@@ -496,6 +498,7 @@ where
                 derivation_path,
                 derivation_path_index,
                 max_order,
+                amounts,
                 input_fee_ppk
             FROM
                 keyset
@@ -520,6 +523,7 @@ where
                 derivation_path,
                 derivation_path_index,
                 max_order,
+                amounts,
                 input_fee_ppk
             FROM
                 keyset
@@ -1837,10 +1841,16 @@ fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {
             derivation_path,
             derivation_path_index,
             max_order,
+            amounts,
             row_keyset_ppk
         ) = row
     );
 
+    let max_order: u8 = column_as_number!(max_order);
+    let amounts = column_as_nullable_string!(amounts)
+        .and_then(|str| serde_json::from_str(&str).ok())
+        .unwrap_or_else(|| (0..max_order).map(|m| 2u64.pow(m.into())).collect());
+
     Ok(MintKeySetInfo {
         id: column_as_string!(id, Id::from_str, Id::from_bytes),
         unit: column_as_string!(unit, CurrencyUnit::from_str),
@@ -1848,7 +1858,8 @@ fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {
         valid_from: column_as_number!(valid_from),
         derivation_path: column_as_string!(derivation_path, DerivationPath::from_str),
         derivation_path_index: column_as_nullable_number!(derivation_path_index),
-        max_order: column_as_number!(max_order),
+        max_order,
+        amounts,
         input_fee_ppk: column_as_number!(row_keyset_ppk),
         final_expiry: column_as_nullable_number!(valid_to),
     })
@@ -2062,3 +2073,97 @@ fn sql_row_to_blind_signature(row: Vec<Column>) -> Result<BlindSignature, Error>
         dleq,
     })
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    mod max_order_to_amounts_migrations {
+        use super::*;
+
+        #[test]
+        fn legacy_payload() {
+            let result = sql_row_to_keyset_info(vec![
+                Column::Text("0083a60439303340".to_owned()),
+                Column::Text("sat".to_owned()),
+                Column::Integer(1),
+                Column::Integer(1749844864),
+                Column::Null,
+                Column::Text("0'/0'/0'".to_owned()),
+                Column::Integer(0),
+                Column::Integer(32),
+                Column::Null,
+                Column::Integer(0),
+            ]);
+            assert!(result.is_ok());
+        }
+
+        #[test]
+        fn migrated_payload() {
+            let legacy = sql_row_to_keyset_info(vec![
+                Column::Text("0083a60439303340".to_owned()),
+                Column::Text("sat".to_owned()),
+                Column::Integer(1),
+                Column::Integer(1749844864),
+                Column::Null,
+                Column::Text("0'/0'/0'".to_owned()),
+                Column::Integer(0),
+                Column::Integer(32),
+                Column::Null,
+                Column::Integer(0),
+            ]);
+            assert!(legacy.is_ok());
+
+            let amounts = (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>();
+            let migrated = sql_row_to_keyset_info(vec![
+                Column::Text("0083a60439303340".to_owned()),
+                Column::Text("sat".to_owned()),
+                Column::Integer(1),
+                Column::Integer(1749844864),
+                Column::Null,
+                Column::Text("0'/0'/0'".to_owned()),
+                Column::Integer(0),
+                Column::Integer(32),
+                Column::Text(serde_json::to_string(&amounts).expect("valid json")),
+                Column::Integer(0),
+            ]);
+            assert!(migrated.is_ok());
+            assert_eq!(legacy.unwrap(), migrated.unwrap());
+        }
+
+        #[test]
+        fn amounts_over_max_order() {
+            let legacy = sql_row_to_keyset_info(vec![
+                Column::Text("0083a60439303340".to_owned()),
+                Column::Text("sat".to_owned()),
+                Column::Integer(1),
+                Column::Integer(1749844864),
+                Column::Null,
+                Column::Text("0'/0'/0'".to_owned()),
+                Column::Integer(0),
+                Column::Integer(32),
+                Column::Null,
+                Column::Integer(0),
+            ]);
+            assert!(legacy.is_ok());
+
+            let amounts = (0..16).map(|x| 2u64.pow(x)).collect::<Vec<_>>();
+            let migrated = sql_row_to_keyset_info(vec![
+                Column::Text("0083a60439303340".to_owned()),
+                Column::Text("sat".to_owned()),
+                Column::Integer(1),
+                Column::Integer(1749844864),
+                Column::Null,
+                Column::Text("0'/0'/0'".to_owned()),
+                Column::Integer(0),
+                Column::Integer(32),
+                Column::Text(serde_json::to_string(&amounts).expect("valid json")),
+                Column::Integer(0),
+            ]);
+            assert!(migrated.is_ok());
+            let migrated = migrated.unwrap();
+            assert_ne!(legacy.unwrap(), migrated);
+            assert_eq!(migrated.amounts.len(), 16);
+        }
+    }
+}

+ 1 - 1
crates/cdk/src/mint/keysets/mod.rs

@@ -82,7 +82,7 @@ impl Mint {
             .signatory
             .rotate_keyset(RotateKeyArguments {
                 unit,
-                max_order,
+                amounts: (0..max_order).map(|n| 2u64.pow(n.into())).collect(),
                 input_fee_ppk,
             })
             .await?;