Skip to content

Commit 1a796f7

Browse files
authored
Silent Payments (#197)
1 parent c2740e2 commit 1a796f7

File tree

6 files changed

+201
-55
lines changed

6 files changed

+201
-55
lines changed

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,33 @@ let tweakedPublicKeyKey = try! privateKey.publicKey.tweak(tweak)
6666
## Elliptic Curve Diffie Hellman
6767

6868
```swift
69-
let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey()
70-
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey()
69+
let privateKey = try! secp256k1.KeyAgreement.PrivateKey()
70+
let publicKey = try! secp256k1.KeyAgreement.PrivateKey().publicKey
71+
72+
// Create a shared secret with a private key from only a public key
73+
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey)
74+
```
75+
76+
## Silent Payment
77+
78+
```swift
79+
let privateSign1 = try! secp256k1.Signing.PrivateKey()
80+
let privateSign2 = try! secp256k1.Signing.PrivateKey()
81+
82+
let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign1.rawRepresentation)
83+
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign2.rawRepresentation)
7184

7285
let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey)
7386
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey)
87+
88+
let sharedSecretSign1 = try! secp256k1.Signing.PrivateKey(rawRepresentation: sharedSecret1.bytes)
89+
let sharedSecretSign2 = try! secp256k1.Signing.PrivateKey(rawRepresentation: sharedSecret2.bytes)
90+
91+
// Payable Silent Payment public key
92+
let xonlyTweak2 = try! sharedSecretSign2.publicKey.xonly.add(privateSign1.publicKey.xonly.bytes)
93+
94+
// Spendable Silent Payment private key
95+
let privateTweak1 = try! sharedSecretSign1.add(xonly: privateSign1.publicKey.xonly.bytes)
7496
```
7597

7698

Sources/implementation/Asymmetric.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public extension secp256k1 {
6969
private let baseKey: PublicKeyImplementation
7070

7171
/// The secp256k1 public key object
72-
var keyBytes: [UInt8] {
72+
var bytes: [UInt8] {
7373
baseKey.bytes
7474
}
7575

@@ -109,8 +109,8 @@ public extension secp256k1 {
109109
/// Generates a secp256k1 public key from a raw representation.
110110
/// - Parameter data: A raw representation of the key.
111111
/// - Throws: An error is thrown when the raw representation does not create a public key.
112-
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, format: secp256k1.Format) {
113-
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, format: format)
112+
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
113+
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
114114
}
115115
}
116116

@@ -124,12 +124,18 @@ public extension secp256k1 {
124124
baseKey.bytes
125125
}
126126

127+
/// A boolean that will be set to true if the point encoded by xonly is the
128+
/// negation of the pubkey and set to false otherwise.
129+
public var parity: Bool {
130+
baseKey.keyParity.boolValue
131+
}
132+
127133
fileprivate init(baseKey: XonlyKeyImplementation) {
128134
self.baseKey = baseKey
129135
}
130136

131-
public init<D: ContiguousBytes>(rawRepresentation data: D) {
132-
self.baseKey = XonlyKeyImplementation(rawRepresentation: data)
137+
public init<D: ContiguousBytes>(rawRepresentation data: D, keyParity: Int32) {
138+
self.baseKey = XonlyKeyImplementation(rawRepresentation: data, keyParity: keyParity)
133139
}
134140
}
135141
}

Sources/implementation/ECDH.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public extension secp256k1 {
2323
/// - data: A raw representation of the public key as a collection of contiguous bytes.
2424
/// - xonly: A raw representation of the xonly key as a collection of contiguous bytes.
2525
/// - format: the format of the public key object
26-
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, format: secp256k1.Format) {
27-
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, format: format)
26+
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, keyParity: Int32, format: secp256k1.Format) {
27+
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, keyParity: keyParity, format: format)
2828
}
2929

3030
/// Initializes a secp256k1 public key for key agreement.

Sources/implementation/Tweak.swift

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,46 @@ public extension secp256k1.Signing.PrivateKey {
1515
/// Create a new `PrivateKey` by adding tweak to the secret key.
1616
/// - Parameter tweak: the 32-byte tweak object
1717
/// - Returns: tweaked `PrivateKey` object
18-
func tweak(_ tweak: [UInt8]) throws -> Self {
18+
func add(_ tweak: [UInt8]) throws -> Self {
19+
var privateBytes = key.bytes
20+
21+
guard secp256k1_ec_seckey_tweak_add(secp256k1.Context.raw, &privateBytes, tweak).boolValue,
22+
secp256k1_ec_seckey_verify(secp256k1.Context.raw, privateBytes).boolValue else {
23+
throw secp256k1Error.underlyingCryptoError
24+
}
25+
26+
return try Self(rawRepresentation: privateBytes)
27+
}
28+
29+
/// Create a new `PrivateKey` by adding tweak to the secret key. When tweaking x-only keys,
30+
/// the implicit negations are handled when odd Y coordinates are reached.
31+
/// [REF](https://github.com/bitcoin-core/secp256k1/issues/1021#issuecomment-983021759)
32+
/// - Parameter tweak: the 32-byte tweak object
33+
/// - Returns: tweaked `PrivateKey` object
34+
func add(xonly tweak: [UInt8]) throws -> Self {
1935
var keypair = secp256k1_keypair()
2036
var privateBytes = [UInt8](repeating: 0, count: secp256k1.ByteDetails.count)
37+
var xonly = secp256k1_xonly_pubkey()
38+
var keyParity = Int32()
2139

2240
guard secp256k1_keypair_create(secp256k1.Context.raw, &keypair, key.bytes).boolValue,
2341
secp256k1_keypair_xonly_tweak_add(secp256k1.Context.raw, &keypair, tweak).boolValue,
24-
secp256k1_keypair_sec(secp256k1.Context.raw, &privateBytes, &keypair).boolValue else {
42+
secp256k1_keypair_sec(secp256k1.Context.raw, &privateBytes, &keypair).boolValue,
43+
secp256k1_keypair_xonly_pub(secp256k1.Context.raw, &xonly, &keyParity, &keypair).boolValue else {
44+
throw secp256k1Error.underlyingCryptoError
45+
}
46+
47+
return try Self(rawRepresentation: privateBytes)
48+
}
49+
50+
/// Create a new `PrivateKey` by multiplying tweak to the secret key.
51+
/// - Parameter tweak: the 32-byte tweak object
52+
/// - Returns: tweaked `PrivateKey` object
53+
func multiply(_ tweak: [UInt8]) throws -> Self {
54+
var privateBytes = key.bytes
55+
56+
guard secp256k1_ec_seckey_tweak_mul(secp256k1.Context.raw, &privateBytes, tweak).boolValue,
57+
secp256k1_ec_seckey_verify(secp256k1.Context.raw, privateBytes).boolValue else {
2558
throw secp256k1Error.underlyingCryptoError
2659
}
2760

@@ -35,23 +68,71 @@ public extension secp256k1.Signing.PublicKey {
3568
/// - tweak: the 32-byte tweak object
3669
/// - format: the format of the tweaked `PublicKey` object
3770
/// - Returns: tweaked `PublicKey` object
38-
func tweak(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
39-
var xonlyPubKey = secp256k1_xonly_pubkey()
71+
func add(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
72+
var pubKey = secp256k1_pubkey()
73+
var pubKeyLen = format.length
74+
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
75+
var xonlyKey = secp256k1_xonly_pubkey()
76+
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
77+
var keyParity = Int32()
78+
79+
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
80+
secp256k1_ec_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, tweak).boolValue,
81+
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
82+
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
83+
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
84+
throw secp256k1Error.underlyingCryptoError
85+
}
86+
87+
return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
88+
}
89+
90+
/// Create a new `PublicKey` by multiplying tweak to the public key.
91+
/// - Parameters:
92+
/// - tweak: the 32-byte tweak object
93+
/// - format: the format of the tweaked `PublicKey` object
94+
/// - Returns: tweaked `PublicKey` object
95+
func multiply(_ tweak: [UInt8], format: secp256k1.Format = .compressed) throws -> Self {
4096
var pubKey = secp256k1_pubkey()
4197
var pubKeyLen = format.length
42-
var pubBytes = [UInt8](repeating: 0, count: pubKeyLen)
43-
var xonlyPubKeyOutput = secp256k1_xonly_pubkey()
98+
var pubKeyBytes = [UInt8](repeating: 0, count: pubKeyLen)
99+
var xonlyKey = secp256k1_xonly_pubkey()
100+
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
101+
var keyParity = Int32()
102+
103+
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &pubKey, bytes, pubKeyLen).boolValue,
104+
secp256k1_ec_pubkey_tweak_mul(secp256k1.Context.raw, &pubKey, tweak).boolValue,
105+
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubKeyBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
106+
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyKey, &keyParity, &pubKey).boolValue,
107+
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyKey).boolValue else {
108+
throw secp256k1Error.underlyingCryptoError
109+
}
110+
111+
return Self(rawRepresentation: pubKeyBytes, xonly: xonlyBytes, keyParity: keyParity, format: format)
112+
}
113+
}
114+
115+
public extension secp256k1.Signing.XonlyKey {
116+
/// Create a new `XonlyKey` by adding tweak to the x-only public key.
117+
/// - Parameters:
118+
/// - tweak: the 32-byte tweak object
119+
/// - format: the format of the tweaked `XonlyKey` object
120+
/// - Returns: tweaked `PublicKey` object
121+
func add(_ tweak: [UInt8]) throws -> Self {
122+
var pubKey = secp256k1_pubkey()
123+
var inXonlyPubKey = secp256k1_xonly_pubkey()
124+
var outXonlyPubKey = secp256k1_xonly_pubkey()
44125
var xonlyBytes = [UInt8](repeating: 0, count: secp256k1.Schnorr.xonlyByteCount)
45126
var keyParity = Int32()
46127

47-
guard secp256k1_xonly_pubkey_parse(secp256k1.Context.raw, &xonlyPubKey, xonly.bytes).boolValue,
48-
secp256k1_xonly_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, &xonlyPubKey, tweak).boolValue,
49-
secp256k1_ec_pubkey_serialize(secp256k1.Context.raw, &pubBytes, &pubKeyLen, &pubKey, format.rawValue).boolValue,
50-
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &xonlyPubKeyOutput, &keyParity, &pubKey).boolValue,
51-
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &xonlyPubKeyOutput).boolValue else {
128+
guard secp256k1_xonly_pubkey_parse(secp256k1.Context.raw, &inXonlyPubKey, bytes).boolValue,
129+
secp256k1_xonly_pubkey_tweak_add(secp256k1.Context.raw, &pubKey, &inXonlyPubKey, tweak).boolValue,
130+
secp256k1_xonly_pubkey_from_pubkey(secp256k1.Context.raw, &outXonlyPubKey, &keyParity, &pubKey).boolValue,
131+
secp256k1_xonly_pubkey_serialize(secp256k1.Context.raw, &xonlyBytes, &outXonlyPubKey).boolValue,
132+
secp256k1_xonly_pubkey_tweak_add_check(secp256k1.Context.raw, &xonlyBytes, keyParity, &inXonlyPubKey, tweak).boolValue else {
52133
throw secp256k1Error.underlyingCryptoError
53134
}
54135

55-
return Self(rawRepresentation: pubBytes, xonly: xonlyBytes, format: format)
136+
return Self(rawRepresentation: xonlyBytes, keyParity: keyParity)
56137
}
57138
}

0 commit comments

Comments
 (0)