use secp256k1::{constants::CURVE_ORDER, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey}; fn hex32(b: &[u8; 32]) -> String { b.iter().map(|x| format!("{:02x}", x)).collect() } /// Implements OP_TWEAKADD semantics. /// Returns None if invalid scalar, invalid x, or infinity. fn tweak_add_xonly(pubkey32: [u8; 32], h32: [u8; 32]) -> Option<[u8; 32]> { let secp = Secp256k1::new(); // Reject if t >= n let scalar = secp256k1::Scalar::from_be_bytes(h32).ok()?; // Lift pubkey from x-only let xpk = XOnlyPublicKey::from_slice(&pubkey32).ok()?; let (xonly, _) = xpk.add_tweak(&secp, &scalar).ok()?; Some(xonly.serialize()) } fn case(name: &str, pubkey_hex: &str, t_hex: &str, check_res: Option<&str>) { let pk_bytes = hex::decode(pubkey_hex).unwrap(); let t_bytes = hex::decode(t_hex).unwrap(); let mut pk32 = [0u8; 32]; pk32.copy_from_slice(&pk_bytes); let mut t32 = [0u8; 32]; t32.copy_from_slice(&t_bytes); match tweak_add_xonly(pk32, t32) { Some(out) => { let out_hex = hex32(&out); if let Some(check) = check_res { assert_eq!(out_hex, check); } let script = format!("<{t_hex}> <{pubkey_hex}> OP_TWEAKADD <{out_hex}> OP_EQUAL"); println!("{name}\n```\n pubkey32 = {pubkey_hex}\n h32 = {t_hex}\n expect = {out_hex}\n\n script = {script}\n```") } None => { let script = format!("<{t_hex}> <{pubkey_hex}> OP_TWEAKADD OP_DROP OP_1"); println!("{name}\n```\n pubkey32 = {pubkey_hex}\n h32 = {t_hex}\n expect = fail\n script = {script}\n```") } } } /// Helper: compute x-only for scalar*k*G. fn xonly_of_scalar(k: u8) -> String { let secp = Secp256k1::new(); let mut buf = [0u8; 32]; buf[31] = k; let sk = SecretKey::from_slice(&buf).unwrap(); let pk = PublicKey::from_secret_key(&secp, &sk); let (xonly, _) = pk.x_only_public_key(); hex32(&xonly.serialize()) } fn hash_scalar(k: u8) -> [u8; 32] { bitcoin_hashes::Sha256::hash(&[k]).to_byte_array() } /// Helper: compute x-only for scalar*k*G. fn xonly_of_scalar_hash(k: [u8; 32]) -> String { let secp = Secp256k1::new(); let sk = SecretKey::from_slice(&k).unwrap(); let pk = PublicKey::from_secret_key(&secp, &sk); let (xonly, _) = pk.x_only_public_key(); hex32(&xonly.serialize()) } fn main() { println!("Curve order n = {}", hex::encode(CURVE_ORDER)); println!(); let x_g = xonly_of_scalar(1); let x_2g = xonly_of_scalar(2); let x_3g = xonly_of_scalar(3); let x_5g = xonly_of_scalar(5); let x_6g = xonly_of_scalar(6); let x_7g = xonly_of_scalar(7); let x_16g = xonly_of_scalar(16); let h1 = hash_scalar(1); let h2 = hash_scalar(2); let h7 = hash_scalar(7); let x_h1g = xonly_of_scalar_hash(h1); let x_h2g = xonly_of_scalar_hash(h2); let x_h7g = xonly_of_scalar_hash(h7); println!("\n### Passing cases\n"); case( "1) Identity tweak (t = 0)", &x_g, "0000000000000000000000000000000000000000000000000000000000000000", Some(&x_g), ); case( "2) Increment by 1", &x_g, "0000000000000000000000000000000000000000000000000000000000000001", Some(&x_2g), ); case( "3) Increment by 2", &x_g, "0000000000000000000000000000000000000000000000000000000000000002", Some(&x_3g), ); case( "4) Increment by 5", &x_g, "0000000000000000000000000000000000000000000000000000000000000005", Some(&x_6g), ); case( "5) Input x(2G), t = 3", &x_2g, "0000000000000000000000000000000000000000000000000000000000000003", Some(&x_5g), ); case( "6) Input x(7G), t = 9", &x_7g, "0000000000000000000000000000000000000000000000000000000000000009", Some(&x_16g), ); case( "7) Input x(h(1) G), t = 1", &x_h1g, "0000000000000000000000000000000000000000000000000000000000000001", None, ); case( "8) Input x(h(2) G), t = 1", &x_h2g, "0000000000000000000000000000000000000000000000000000000000000001", None, ); case( "9) Input x(h(7) G), t = 1", &x_h7g, "0000000000000000000000000000000000000000000000000000000000000001", None, ); case("10) Input x(G), t = 1", &x_g, &hex32(&h1), None); case("11) Input x(G), t = h(2)", &x_g, &hex32(&h2), None); case( "12) Input x(G), t = h(7) (Note: differs from 9)", &x_g, &hex32(&h7), None, ); println!("\n### Failing cases\n"); case( "A) Scalar out of range (t = n)", &x_g, "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", None, ); case( "B) Invalid x (x = 0), t = 1", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000001", None, ); case( "C) Infinity result (x(G), t = n-1)", &x_g, "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", None, ); }