commit 2f8aa29e92f9c5077936bf61b64a12d09ec0bd30
parent e3c04465fc4e3801a95baa71d6487c4e2b8df19c
Author: William Casarin <jb55@jb55.com>
Date: Tue, 25 Jul 2023 08:58:06 -0700
ndb: make NostrEvents immutable
Since we can't mutate NdbNotes, let's update the existing codebase to
generate and sign ids on NostrEvent constructions. This will allow us to
match NdbNote's constructor
Diffstat:
47 files changed, 2213 insertions(+), 464 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -644,6 +644,11 @@
4C75EFB628049D990006080F /* RelayPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPool.swift; sourceTree = "<group>"; };
4C75EFB82804A2740006080F /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; };
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
+ 4C78EFD62A7078C5007E8197 /* random.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = random.h; sourceTree = "<group>"; };
+ 4C78EFD72A707C4D007E8197 /* secp256k1_schnorrsig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = secp256k1_schnorrsig.h; sourceTree = "<group>"; };
+ 4C78EFD82A707C4D007E8197 /* secp256k1_ecdh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = secp256k1_ecdh.h; sourceTree = "<group>"; };
+ 4C78EFD92A707C4D007E8197 /* secp256k1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = secp256k1.h; sourceTree = "<group>"; };
+ 4C78EFDA2A707C67007E8197 /* secp256k1_extrakeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = secp256k1_extrakeys.h; sourceTree = "<group>"; };
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardVisible.swift; sourceTree = "<group>"; };
4C7D095C2A098C5D00943473 /* ConnectWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectWalletView.swift; sourceTree = "<group>"; };
4C7D095D2A098C5D00943473 /* WalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletView.swift; sourceTree = "<group>"; };
@@ -1327,7 +1332,12 @@
4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */,
4CE9FBB82A6B3B26007E485C /* nostrdb.c */,
4CE9FBB92A6B3B26007E485C /* nostrdb.h */,
+ 4C78EFD62A7078C5007E8197 /* random.h */,
4CDD1AE72A6B3611001CD4DF /* jsmn.h */,
+ 4C78EFD82A707C4D007E8197 /* secp256k1_ecdh.h */,
+ 4C78EFD72A707C4D007E8197 /* secp256k1_schnorrsig.h */,
+ 4C78EFDA2A707C67007E8197 /* secp256k1_extrakeys.h */,
+ 4C78EFD92A707C4D007E8197 /* secp256k1.h */,
);
path = nostrdb;
sourceTree = "<group>";
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -202,8 +202,8 @@ struct ContentView: View {
func MaybeReportView(target: ReportTarget) -> some View {
Group {
if let damus_state {
- if let sec = damus_state.keypair.privkey {
- ReportView(postbox: damus_state.postbox, target: target, privkey: sec)
+ if let keypair = damus_state.keypair.to_full() {
+ ReportView(postbox: damus_state.postbox, target: target, keypair: keypair)
} else {
EmptyView()
}
@@ -377,7 +377,9 @@ struct ContentView: View {
}
profile.lud16 = lud16
- let ev = make_metadata_event(keypair: keypair, metadata: profile)
+ guard let ev = make_metadata_event(keypair: keypair, metadata: profile) else {
+ return
+ }
ds.postbox.send(ev)
}
.onReceive(handle_notify(.broadcast_event)) { obj in
@@ -505,7 +507,9 @@ struct ContentView: View {
}
profile.reactions = !hide
- let profile_ev = make_metadata_event(keypair: keypair, metadata: profile)
+ guard let profile_ev = make_metadata_event(keypair: keypair, metadata: profile) else {
+ return
+ }
damus_state.postbox.send(profile_ev)
}
.alert(NSLocalizedString("User muted", comment: "Alert message to indicate the user has been muted"), isPresented: $user_muted_confirm, actions: {
@@ -947,7 +951,9 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev
//let post = tup.0
//let to_relays = tup.1
print("post \(post.content)")
- let new_ev = post_to_event(post: post, privkey: keypair.privkey, pubkey: keypair.pubkey)
+ guard let new_ev = post_to_event(post: post, keypair: keypair) else {
+ return false
+ }
postbox.send(new_ev)
for eref in new_ev.referenced_ids.prefix(3) {
// also broadcast at most 3 referenced events
diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift
@@ -40,15 +40,15 @@ class Contacts {
for d in diff {
if new.contains(d) {
- new_mutes.append(d)
+ new_mutes.append(d.string())
} else {
- new_unmutes.append(d)
+ new_unmutes.append(d.string())
}
}
-
+
// TODO: set local mutelist here
- self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
-
+ self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id.string() }))
+
if new_mutes.count > 0 {
notify(.new_mutes, new_mutes)
}
@@ -72,11 +72,7 @@ class Contacts {
func get_followed_hashtags() -> Set<String> {
guard let ev = self.event else { return Set() }
- return ev.tags.reduce(into: Set<String>(), { htags, tag in
- if tag.count >= 2 && tag[0] == "t" && tag[1] != "" {
- htags.insert(tag[1])
- }
- })
+ return Set(ev.referenced_hashtags.map({ $0.ref_id.string() }))
}
func add_friend_pubkey(_ pubkey: String) {
@@ -85,18 +81,17 @@ class Contacts {
func add_friend_contact(_ contact: NostrEvent) {
friends.insert(contact.pubkey)
- for tag in contact.tags {
- if tag.count >= 2 && tag[0] == "p" {
- friend_of_friends.insert(tag[1])
-
- // Exclude themself and us.
- if contact.pubkey != our_pubkey && contact.pubkey != tag[1] {
- if pubkey_to_our_friends[tag[1]] == nil {
- pubkey_to_our_friends[tag[1]] = Set<String>()
- }
-
- pubkey_to_our_friends[tag[1]]?.insert(contact.pubkey)
+ for tag in contact.referenced_pubkeys {
+ let pk = tag.id.string()
+ friend_of_friends.insert(pk)
+
+ // Exclude themself and us.
+ if contact.pubkey != our_pubkey && contact.pubkey != pk {
+ if pubkey_to_our_friends[pk] == nil {
+ pubkey_to_our_friends[pk] = Set<String>()
}
+
+ pubkey_to_our_friends[pk]?.insert(contact.pubkey)
}
}
}
@@ -128,13 +123,10 @@ class Contacts {
}
func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? {
- guard let ev = follow_user_event(our_contacts: our_contacts, our_pubkey: keypair.pubkey, follow: follow) else {
+ guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
return nil
}
- ev.calculate_id()
- ev.sign(privkey: keypair.privkey)
-
box.send(ev)
return ev
@@ -145,28 +137,35 @@ func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: Fu
return nil
}
- let ev = unfollow_reference_event(our_contacts: cs, our_pubkey: keypair.pubkey, unfollow: unfollow)
- ev.calculate_id()
- ev.sign(privkey: keypair.privkey)
+ guard let ev = unfollow_reference_event(our_contacts: cs, keypair: keypair, unfollow: unfollow) else {
+ return nil
+ }
postbox.send(ev)
return ev
}
-func unfollow_reference_event(our_contacts: NostrEvent, our_pubkey: String, unfollow: ReferencedId) -> NostrEvent {
+func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: ReferencedId) -> NostrEvent? {
let tags = our_contacts.tags.filter { tag in
- if tag.count >= 2 && tag[0] == unfollow.key && tag[1] == unfollow.ref_id {
+ if let key = tag[safe: 0],
+ let ref = tag[safe: 1],
+ let fst = unfollow.key.first,
+ let afst = AsciiCharacter(fst),
+ key.matches_char(afst),
+ ref.string() == unfollow.ref_id
+ {
return false
}
return true
}
let kind = NostrKind.contacts.rawValue
- return NostrEvent(content: our_contacts.content, pubkey: our_pubkey, kind: kind, tags: tags)
+
+ return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
}
-func follow_user_event(our_contacts: NostrEvent?, our_pubkey: String, follow: ReferencedId) -> NostrEvent? {
+func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? {
guard let cs = our_contacts else {
// don't create contacts for now so we don't nuke our contact list due to connectivity issues
// we should only create contacts during profile creation
@@ -174,7 +173,7 @@ func follow_user_event(our_contacts: NostrEvent?, our_pubkey: String, follow: Re
return nil
}
- guard let ev = follow_with_existing_contacts(our_pubkey: our_pubkey, our_contacts: cs, follow: follow) else {
+ guard let ev = follow_with_existing_contacts(keypair: keypair, our_contacts: cs, follow: follow) else {
return nil
}
@@ -186,23 +185,18 @@ func decode_json_relays(_ content: String) -> [String: RelayInfo]? {
return decode_json(content)
}
-func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], privkey: String, relay: String) -> NostrEvent? {
+func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], keypair: FullKeypair, relay: String) -> NostrEvent?{
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
-
relays.removeValue(forKey: relay)
- print("remove_relay \(relays)")
guard let content = encode_json(relays) else {
return nil
}
- let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags)
- new_ev.calculate_id()
- new_ev.sign(privkey: privkey)
- return new_ev
+ return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags)
}
-func add_relay(ev: NostrEvent, privkey: String, current_relays: [RelayDescriptor], relay: String, info: RelayInfo) -> NostrEvent? {
+func add_relay(ev: NostrEvent, keypair: FullKeypair, current_relays: [RelayDescriptor], relay: String, info: RelayInfo) -> NostrEvent? {
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
guard relays.index(forKey: relay) == nil else {
@@ -215,10 +209,7 @@ func add_relay(ev: NostrEvent, privkey: String, current_relays: [RelayDescriptor
return nil
}
- let new_ev = NostrEvent(content: content, pubkey: ev.pubkey, kind: 3, tags: ev.tags)
- new_ev.calculate_id()
- new_ev.sign(privkey: privkey)
- return new_ev
+ return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags)
}
func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: RelayInfo] {
@@ -229,19 +220,22 @@ func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: R
}
func is_already_following(contacts: NostrEvent, follow: ReferencedId) -> Bool {
- return contacts.references(id: follow.ref_id, key: follow.key)
+ guard let key = follow.key.first_char() else { return false }
+ return contacts.references(id: follow.ref_id, key: key)
}
-func follow_with_existing_contacts(our_pubkey: String, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? {
+func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? {
// don't update if we're already following
if is_already_following(contacts: our_contacts, follow: follow) {
return nil
}
let kind = NostrKind.contacts.rawValue
- var tags = our_contacts.tags
+
+ var tags = our_contacts.tags.strings()
tags.append(refid_to_tag(follow))
- return NostrEvent(content: our_contacts.content, pubkey: our_pubkey, kind: kind, tags: tags)
+
+ return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
}
func make_contact_relays(_ relays: [RelayDescriptor]) -> [String: RelayInfo] {
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -821,10 +821,8 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
}
}
+// TODO: remove this, let nostrdb handle all validation
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
- guard ev.id == hex_encode(calculate_event_id(ev: ev)) else {
- return
- }
let validated = events.is_event_valid(ev.id)
switch validated {
diff --git a/damus/Models/Mentions.swift b/damus/Models/Mentions.swift
@@ -473,14 +473,11 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
return PostTags(blocks: post_blocks, tags: new_tags)
}
-func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent {
+func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
let tags = post.references.map(refid_to_tag) + post.tags
let post_blocks = parse_post_blocks(content: post.content)
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
let content = render_blocks(blocks: post_tags.blocks)
- let new_ev = NostrEvent(content: content, pubkey: pubkey, kind: post.kind.rawValue, tags: post_tags.tags)
- new_ev.calculate_id()
- new_ev.sign(privkey: privkey)
- return new_ev
+ return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags)
}
diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift
@@ -35,7 +35,7 @@ class ProfileModel: ObservableObject, Equatable {
}
for tag in contacts.tags {
- guard tag.count >= 2 && tag[0] == "p" else {
+ guard tag.count >= 2 && tag[0].matches_char("p") else {
continue
}
diff --git a/damus/Models/Report.swift b/damus/Models/Report.swift
@@ -55,17 +55,8 @@ func create_report_tags(target: ReportTarget, type: ReportType) -> [[String]] {
}
}
-func create_report_event(privkey: String, report: Report) -> NostrEvent? {
- guard let pubkey = privkey_to_pubkey(privkey: privkey) else {
- return nil
- }
-
- let kind = 1984
+func create_report_event(keypair: FullKeypair, report: Report) -> NostrEvent? {
+ let kind: UInt32 = 1984
let tags = create_report_tags(target: report.target, type: report.type)
- let ev = NostrEvent(content: report.message, pubkey: pubkey, kind: kind, tags: tags)
-
- ev.id = hex_encode(calculate_event_id(ev: ev))
- ev.sig = sign_event(privkey: privkey, ev: ev)
-
- return ev
+ return NostrEvent(content: report.message, keypair: keypair.to_keypair(), kind: kind, tags: tags)
}
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -23,6 +23,8 @@ enum ValidationResult: Decodable {
//typealias NostrEvent = NdbNote
typealias NostrEvent = NostrEventOld
+let MAX_NOTE_SIZE: Int = 2 << 18
+
class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
// TODO: memory mapped db events
/*
@@ -49,10 +51,10 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
var tags: TagIterator
*/
- var id: String
- var content: String
- var sig: String
- var tags: [[String]]
+ let id: String
+ let content: String
+ let sig: String
+ let tags: [[String]]
//var boosted_by: String?
@@ -60,7 +62,7 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
//var pow: Int?
// custom flags for internal use
- var flags: Int = 0
+ //var flags: Int = 0
let pubkey: String
let created_at: UInt32
@@ -86,16 +88,21 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
hasher.combine(id)
}
- init(id: String = "", content: String, pubkey: String, kind: Int = 1, tags: [[String]] = [], createdAt: Int64 = Int64(Date().timeIntervalSince1970)) {
-
- self.id = id
- self.sig = ""
+ init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
self.content = content
- self.pubkey = pubkey
+ self.pubkey = keypair.pubkey
self.kind = kind
self.tags = tags
self.created_at = createdAt
+
+ if let privkey = keypair.privkey {
+ self.id = hex_encode(calculate_event_id(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content))
+ self.sig = sign_id(privkey: privkey, id: self.id)
+ } else {
+ self.id = ""
+ self.sig = ""
+ }
}
}
@@ -112,10 +119,6 @@ extension NostrEventOld {
return !too_big
}
- var is_valid_id: Bool {
- return hex_encode(calculate_event_id(ev: self)) == self.id
- }
-
func blocks(_ privkey: String?) -> Blocks {
if let bs = _blocks {
return bs
@@ -136,7 +139,7 @@ extension NostrEventOld {
}
if self.content == "", let ref = self.referenced_ids.first {
- return cache.lookup(ref.ref_id)
+ return cache.lookup(ref.ref_id.string())
}
return self.inner_event
@@ -240,9 +243,9 @@ extension NostrEventOld {
return tag_to_refid(tags[last])
}
- public func references(id: String, key: String) -> Bool {
+ public func references(id: String, key: AsciiCharacter) -> Bool {
for tag in tags {
- if tag.count >= 2 && tag[0] == key {
+ if tag.count >= 2 && tag[0].matches_char(key) {
if tag[1] == id {
return true
}
@@ -283,16 +286,8 @@ extension NostrEventOld {
return get_referenced_ids(key: "p")
}
- public var is_local: Bool {
- return (self.flags & 1) != 0
- }
-
- func calculate_id() {
- self.id = hex_encode(calculate_event_id(ev: self))
- }
-
- func sign(privkey: String) {
- self.sig = sign_event(privkey: privkey, ev: self)
+ public var referenced_hashtags: [ReferencedId] {
+ return get_referenced_ids(key: "t")
}
var age: TimeInterval {
@@ -301,14 +296,14 @@ extension NostrEventOld {
}
}
-func sign_event(privkey: String, ev: NostrEvent) -> String {
+func sign_id(privkey: String, id: String) -> String {
let priv_key_bytes = try! privkey.bytes
let key = try! secp256k1.Signing.PrivateKey(rawRepresentation: priv_key_bytes)
// Extra params for custom signing
var aux_rand = random_bytes(count: 64)
- var digest = try! ev.id.bytes
+ var digest = try! id.bytes
// API allows for signing variable length messages
let signature = try! key.schnorr.signature(message: &digest, auxiliaryRand: &aux_rand)
@@ -370,32 +365,28 @@ func decode_data<T: Decodable>(_ data: Data) -> T? {
return nil
}
-func event_commitment(ev: NostrEvent, tags: String) -> String {
+func event_commitment(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> String {
let encoder = JSONEncoder()
encoder.outputFormatting = .withoutEscapingSlashes
- let str_data = try! encoder.encode(ev.content)
+ let str_data = try! encoder.encode(content)
let content = String(decoding: str_data, as: UTF8.self)
- let commit = "[0,\"\(ev.pubkey)\",\(ev.created_at),\(ev.kind),\(tags),\(content)]"
- //print("COMMIT", commit)
- return commit
-}
-
-func calculate_event_commitment(ev: NostrEvent) -> Data {
+
let tags_encoder = JSONEncoder()
tags_encoder.outputFormatting = .withoutEscapingSlashes
- let tags_data = try! tags_encoder.encode(ev.tags)
+ let tags_data = try! tags_encoder.encode(tags)
let tags = String(decoding: tags_data, as: UTF8.self)
- let target = event_commitment(ev: ev, tags: tags)
- let target_data = target.data(using: .utf8)!
- return target_data
+ return "[0,\"\(pubkey)\",\(created_at),\(kind),\(tags),\(content)]"
}
-func calculate_event_id(ev: NostrEvent) -> Data {
- let commitment = calculate_event_commitment(ev: ev)
- let hash = sha256(commitment)
+func calculate_event_commitment(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data {
+ let target = event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content)
+ return target.data(using: .utf8)!
+}
- return hash
+func calculate_event_id(pubkey: String, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data {
+ let commitment = calculate_event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content)
+ return sha256(commitment)
}
@@ -480,10 +471,6 @@ func get_referenced_ids(tags: [[String]], key: String) -> [ReferencedId] {
}
func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
- guard let privkey = keypair.privkey else {
- return nil
- }
-
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
let rw_relay_info = RelayInfo(read: true, write: true)
var relays: [String: RelayInfo] = [:]
@@ -498,48 +485,31 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
["p", damus_pubkey],
["p", keypair.pubkey] // you're a friend of yourself!
]
- let ev = NostrEvent(content: relay_json,
- pubkey: keypair.pubkey,
- kind: NostrKind.contacts.rawValue,
- tags: tags)
- ev.calculate_id()
- ev.sign(privkey: privkey)
- return ev
+ return NostrEvent(content: relay_json, keypair: keypair, kind: NostrKind.contacts.rawValue, tags: tags)
}
-func make_metadata_event(keypair: FullKeypair, metadata: Profile) -> NostrEvent {
- let metadata_json = encode_json(metadata)!
- let ev = NostrEvent(content: metadata_json,
- pubkey: keypair.pubkey,
- kind: NostrKind.metadata.rawValue,
- tags: [])
+func make_metadata_event(keypair: FullKeypair, metadata: Profile) -> NostrEvent? {
+ guard let metadata_json = encode_json(metadata) else {
+ return nil
+ }
+ return NostrEvent(content: metadata_json, keypair: keypair.to_keypair(), kind: NostrKind.metadata.rawValue, tags: [])
- ev.calculate_id()
- ev.sign(privkey: keypair.privkey)
- return ev
}
-func make_boost_event(pubkey: String, privkey: String, boosted: NostrEvent) -> NostrEvent {
+func make_boost_event(keypair: FullKeypair, boosted: NostrEvent) -> NostrEvent? {
var tags: [[String]] = boosted.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
tags.append(["e", boosted.id, "", "root"])
tags.append(["p", boosted.pubkey])
- let ev = NostrEvent(content: event_to_json(ev: boosted), pubkey: pubkey, kind: 6, tags: tags)
- ev.calculate_id()
- ev.sign(privkey: privkey)
- return ev
+ return NostrEvent(content: event_to_json(ev: boosted), keypair: keypair.to_keypair(), kind: 6, tags: tags)
}
-func make_like_event(pubkey: String, privkey: String, liked: NostrEvent, content: String = "🤙") -> NostrEvent {
+func make_like_event(keypair: FullKeypair, liked: NostrEvent, content: String = "🤙") -> NostrEvent? {
var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
tags.append(["e", liked.id])
tags.append(["p", liked.pubkey])
- let ev = NostrEvent(content: content, pubkey: pubkey, kind: 7, tags: tags)
- ev.calculate_id()
- ev.sign(privkey: privkey)
-
- return ev
+ return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 7, tags: tags)
}
func zap_target_to_tags(_ target: ZapTarget) -> [[String]] {
@@ -560,11 +530,8 @@ func make_private_zap_request_event(identity: FullKeypair, enc_key: FullKeypair,
// target tags must be the same as zap request target tags
let tags = zap_target_to_tags(target)
- let note = NostrEvent(content: message, pubkey: identity.pubkey, kind: 9733, tags: tags)
- note.id = hex_encode(calculate_event_id(ev: note))
- note.sig = sign_event(privkey: identity.privkey, ev: note)
-
- guard let note_json = encode_json(note),
+ guard let note = NostrEvent(content: message, keypair: identity.to_keypair(), kind: 9733, tags: tags),
+ let note_json = encode_json(note),
let enc = encrypt_message(message: note_json, privkey: enc_key.privkey, to_pk: target.pubkey, encoding: .bech32)
else {
return nil
@@ -584,7 +551,7 @@ func decrypt_private_zap(our_privkey: String, zapreq: NostrEvent, target: ZapTar
// check to see if the private note was from us
if note == nil {
- guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: target.id, created_at: zapreq.created_at) else{
+ guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: target.id, created_at: zapreq.created_at) else {
return nil
}
// use our private keypair and their pubkey to get the shared secret
@@ -620,7 +587,7 @@ func decrypt_private_zap(our_privkey: String, zapreq: NostrEvent, target: ZapTar
return note
}
-func generate_private_keypair(our_privkey: String, id: String, created_at: Int64) -> FullKeypair? {
+func generate_private_keypair(our_privkey: String, id: String, created_at: UInt32) -> FullKeypair? {
let to_hash = our_privkey + id + String(created_at)
guard let dat = to_hash.data(using: .utf8) else {
return nil
@@ -691,9 +658,9 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
privzap_req = privreq
}
- let ev = NostrEvent(content: message, pubkey: kp.pubkey, kind: 9734, tags: tags, createdAt: now)
- ev.id = hex_encode(calculate_event_id(ev: ev))
- ev.sig = sign_event(privkey: kp.privkey, ev: ev)
+ guard let ev = NostrEvent(content: message, keypair: kp.to_keypair(), kind: 9734, tags: tags, createdAt: now) else {
+ return nil
+ }
let zapreq = ZapRequest(ev: ev)
if let privzap_req {
return .priv(zapreq, privzap_req)
@@ -942,7 +909,7 @@ func aes_operation(operation: CCOperation, data: [UInt8], iv: [UInt8], shared_se
func validate_event(ev: NostrEvent) -> ValidationResult {
- let raw_id = sha256(calculate_event_commitment(ev: ev))
+ let raw_id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags, content: ev.content)
let id = hex_encode(raw_id)
if id != ev.id {
diff --git a/damus/Nostr/ReferencedId.swift b/damus/Nostr/ReferencedId.swift
@@ -19,6 +19,10 @@ struct Reference {
}
}
+func tagref_should_be_id(_ tag: NdbTagElem) -> Bool {
+ return !tag.matches_char("t")
+}
+
struct References: Sequence, IteratorProtocol {
let tags: TagsSequence
var tags_iter: TagsIterator
@@ -26,7 +30,7 @@ struct References: Sequence, IteratorProtocol {
mutating func next() -> Reference? {
while let tag = tags_iter.next() {
guard let key = tag[0], key.count == 1,
- let id = tag[1], id.is_id
+ let id = tag[1], tagref_should_be_id(key)
else { continue }
for c in key {
@@ -49,12 +53,37 @@ struct References: Sequence, IteratorProtocol {
.filter() { ref in ref.key == "p" }
}
+ static func hashtags(tags: TagsSequence) -> LazyFilterSequence<References> {
+ References(tags: tags).lazy
+ .filter() { ref in ref.key == "t" }
+ }
+
init(tags: TagsSequence) {
self.tags = tags
self.tags_iter = tags.makeIterator()
}
}
+extension [[String]] {
+ func strings() -> [[String]] {
+ return self
+ }
+}
+
+extension String {
+ func string() -> String {
+ return self
+ }
+
+ func first_char() -> AsciiCharacter? {
+ self.first.flatMap { chr in AsciiCharacter(chr) }
+ }
+
+ func matches_char(_ c: AsciiCharacter) -> Bool {
+ return self.first == c.character
+ }
+}
+
struct ReferencedId: Identifiable, Hashable, Equatable {
let ref_id: String
let relay_id: String?
diff --git a/damus/TestData.swift b/damus/TestData.swift
@@ -7,23 +7,44 @@
import Foundation
+let test_seckey = "8e33316b227de8215d36f4787573beaaf532229bb00398430a0ae963b658e656"
+let test_pubkey = "a9952fe066ced622167acb8069a0dfd1d44d9493ef2a4c28cf93e2877248b41a"
+let test_keypair = Keypair(pubkey: test_pubkey, privkey: test_seckey)
+let test_keypair_full = test_keypair.to_full()!
let test_event_holder = EventHolder(events: [], incoming: [test_event])
let test_event =
NostrEvent(
content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jpg cool",
- pubkey: "pk",
- createdAt: Int64(Date().timeIntervalSince1970 - 100)
- )
+ keypair: test_keypair,
+ createdAt: UInt32(Date().timeIntervalSince1970 - 100)
+ )!
+
+let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
+
+let test_repost_1 = NostrEvent(content: test_encoded_post, keypair: test_keypair, kind: 6, tags: [], createdAt: 1)!
+let test_repost_2 = NostrEvent(content: test_encoded_post, keypair: test_keypair, kind: 6, tags: [], createdAt: 1)!
+
+let test_reposts = [test_repost_1, test_repost_2]
+let test_event_group = EventGroup(events: test_reposts)
+
+let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
+let test_zap_request_ev = NostrEvent(content: "hi", keypair: test_keypair, kind: 9734)!
+let test_zap_request = ZapRequest(ev: test_zap_request_ev)
+let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil)
+
+let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event))
+
+let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
+
func test_damus_state() -> DamusState {
- let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState.empty
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_event)
- damus.profiles.add(id: pubkey, profile: tsprof)
+ damus.profiles.add(id: test_pubkey, profile: tsprof)
return damus
}
diff --git a/damus/Util/AccountDeletion.swift b/damus/Util/AccountDeletion.swift
@@ -8,15 +8,14 @@
import Foundation
-func created_deleted_account_profile(keypair: FullKeypair) -> NostrEvent {
+func created_deleted_account_profile(keypair: FullKeypair) -> NostrEvent? {
let profile = Profile()
profile.deleted = true
profile.about = "account deleted"
profile.name = "nobody"
- let content = encode_json(profile)!
- let ev = NostrEvent(content: content, pubkey: keypair.pubkey, kind: 0)
- ev.id = hex_encode(calculate_event_id(ev: ev))
- ev.sig = sign_event(privkey: keypair.privkey, ev: ev)
- return ev
+ guard let content = encode_json(profile) else {
+ return nil
+ }
+ return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 0)
}
diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift
@@ -14,6 +14,10 @@ let ANON_PUBKEY = "anon"
struct FullKeypair: Equatable {
let pubkey: String
let privkey: String
+
+ func to_keypair() -> Keypair {
+ return Keypair(pubkey: pubkey, privkey: privkey)
+ }
}
struct Keypair {
diff --git a/damus/Util/Lists.swift b/damus/Util/Lists.swift
@@ -16,22 +16,15 @@ func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: Str
}
func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: String, list_name: String, list_type: String) -> NostrEvent? {
- let pubkey = keypair.pubkey
-
- if let prev = mprev {
- if let okprev = ensure_list_name(list: prev, name: list_name), prev.pubkey == keypair.pubkey {
- return add_to_list_event(keypair: keypair, prev: okprev, to_add: to_add, tag_type: list_type)
- }
+ if let prev = mprev,
+ prev.pubkey == keypair.pubkey,
+ matches_list_name(tags: prev.tags, name: list_name)
+ {
+ return add_to_list_event(keypair: keypair, prev: prev, to_add: to_add, tag_type: list_type)
}
let tags = [["d", list_name], [list_type, to_add]]
- let ev = NostrEvent(content: "", pubkey: pubkey, kind: 30000, tags: tags)
-
- ev.tags = tags
- ev.id = hex_encode(calculate_event_id(ev: ev))
- ev.sig = sign_event(privkey: keypair.privkey, ev: ev)
-
- return ev
+ return NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 30000, tags: tags)
}
func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: String, tag_type: String) -> NostrEvent? {
@@ -51,11 +44,7 @@ func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: S
!(tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove)
}
- let ev = NostrEvent(content: prev.content, pubkey: keypair.pubkey, kind: 30000, tags: new_tags)
- ev.id = hex_encode(calculate_event_id(ev: ev))
- ev.sig = sign_event(privkey: keypair.privkey, ev: ev)
-
- return ev
+ return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: new_tags)
}
func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: String, tag_type: String) -> NostrEvent? {
@@ -65,27 +54,19 @@ func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: String, t
return nil
}
}
-
- let new = NostrEvent(content: prev.content, pubkey: keypair.pubkey, kind: 30000, tags: prev.tags)
- new.tags.append([tag_type, to_add])
- new.id = hex_encode(calculate_event_id(ev: new))
- new.sig = sign_event(privkey: keypair.privkey, ev: new)
-
- return new
+
+ var tags = Array(prev.tags)
+ tags.append([tag_type, to_add])
+
+ return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: tags)
}
-func ensure_list_name(list: NostrEvent, name: String) -> NostrEvent? {
- for tag in list.tags {
+func matches_list_name(tags: [[String]], name: String) -> Bool {
+ for tag in tags {
if tag.count >= 2 && tag[0] == "d" {
- if tag[1] != name {
- return nil
- } else {
- return list
- }
+ return tag[1] == name
}
}
-
- list.tags.insert(["d", name], at: 0)
-
- return list
+
+ return false
}
diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift
@@ -140,12 +140,11 @@ struct EventActionBar: View {
}
func send_like() {
- guard let privkey = damus_state.keypair.privkey else {
+ guard let keypair = damus_state.keypair.to_full(),
+ let like_ev = make_like_event(keypair: keypair, liked: event) else {
return
}
-
- let like_ev = make_like_event(pubkey: damus_state.pubkey, privkey: privkey, liked: event)
-
+
self.bar.our_like = like_ev
generator.impactOccurred()
@@ -222,10 +221,9 @@ struct LikeButton: View {
struct EventActionBar_Previews: PreviewProvider {
static var previews: some View {
- let pk = "pubkey"
let ds = test_damus_state()
- let ev = NostrEvent(content: "hi", pubkey: pk)
-
+ let ev = NostrEvent(content: "hi", keypair: test_keypair)!
+
let bar = ActionBarModel.empty()
let likedbar = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: nil, our_boost: nil, our_zap: nil, our_reply: nil)
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, replies: 0, our_like: test_event, our_boost: nil, our_zap: nil, our_reply: nil)
diff --git a/damus/Views/ActionBar/RepostAction.swift b/damus/Views/ActionBar/RepostAction.swift
@@ -20,12 +20,11 @@ struct RepostAction: View {
Button {
dismiss()
- guard let privkey = self.damus_state.keypair.privkey else {
+ guard let keypair = self.damus_state.keypair.to_full(),
+ let boost = make_boost_event(keypair: keypair, boosted: self.event) else {
return
}
-
- let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
-
+
damus_state.postbox.send(boost)
} label: {
Label(NSLocalizedString("Repost", comment: "Button to repost a note"), image: "repost")
diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift
@@ -116,15 +116,11 @@ struct ConfigView: View {
confirm_delete_account = false
}
Button(NSLocalizedString("Delete", comment: "Button for deleting the users account."), role: .destructive) {
- guard let full_kp = state.keypair.to_full() else {
+ guard let keypair = state.keypair.to_full(),
+ delete_text == DELETE_KEYWORD,
+ let ev = created_deleted_account_profile(keypair: keypair) else {
return
}
-
- guard delete_text == DELETE_KEYWORD else {
- return
- }
-
- let ev = created_deleted_account_profile(keypair: full_kp)
state.postbox.send(ev)
notify(.logout, ())
}
diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift
@@ -177,9 +177,9 @@ struct DMChatView: View, KeyboardReadable {
struct DMChatView_Previews: PreviewProvider {
static var previews: some View {
- let ev = NostrEvent(content: "hi", pubkey: "pubkey", kind: 1, tags: [])
+ let ev = NostrEvent(content: "hi", keypair: test_keypair, kind: 1, tags: [])!
- let model = DirectMessageModel(events: [ev], our_pubkey: "pubkey", pubkey: "the_pk")
+ let model = DirectMessageModel(events: [ev], our_pubkey: test_pubkey, pubkey: test_pubkey)
DMChatView(damus_state: test_damus_state(), dms: model)
}
@@ -216,11 +216,7 @@ func create_encrypted_event(_ message: String, to_pk: String, tags: [[String]],
return nil
}
- let ev = NostrEvent(content: enc_content, pubkey: keypair.pubkey, kind: kind, tags: tags, createdAt: created_at)
-
- ev.calculate_id()
- ev.sign(privkey: privkey)
- return ev
+ return NostrEvent(content: enc_content, keypair: keypair.to_keypair(), kind: kind, tags: tags, createdAt: created_at)
}
func create_dm(_ message: String, to_pk: String, tags: [[String]], keypair: Keypair, created_at: UInt32? = nil) -> NostrEvent?
diff --git a/damus/Views/DMView.swift b/damus/Views/DMView.swift
@@ -73,7 +73,7 @@ struct DMView: View {
struct DMView_Previews: PreviewProvider {
static var previews: some View {
- let ev = NostrEvent(content: "Hey there *buddy*, want to grab some drinks later? 🍻", pubkey: "pubkey", kind: 1, tags: [])
+ let ev = NostrEvent(content: "Hey there *buddy*, want to grab some drinks later? 🍻", keypair: test_keypair, kind: 1, tags: [])!
DMView(event: ev, damus_state: test_damus_state())
}
}
diff --git a/damus/Views/Events/Longform/LongformView.swift b/damus/Views/Events/Longform/LongformView.swift
@@ -59,19 +59,18 @@ struct LongformView: View {
}
}
-let test_longform_event = LongformEvent.parse(from:
- .init(id: "longform_id",
- content: longform_long_test_data,
- pubkey: "pk",
- kind: NostrKind.longform.rawValue,
- tags: [
- ["title", "What is WASTOIDS?"],
- ["summary", "WASTOIDS is an audio/visual feed, created by Sam Means..."],
- ["published_at", "1685638715"],
- ["t", "coffee"],
- ["t", "coffeechain"],
- ["image", "https://cdn.jb55.com/s/038fe8f558153b52.jpg"],
- ])
+let test_longform_event = LongformEvent.parse(from: NostrEvent(
+ content: longform_long_test_data,
+ keypair: test_keypair,
+ kind: NostrKind.longform.rawValue,
+ tags: [
+ ["title", "What is WASTOIDS?"],
+ ["summary", "WASTOIDS is an audio/visual feed, created by Sam Means..."],
+ ["published_at", "1685638715"],
+ ["t", "coffee"],
+ ["t", "coffeechain"],
+ ["image", "https://cdn.jb55.com/s/038fe8f558153b52.jpg"],
+ ])!
)
struct LongformView_Previews: PreviewProvider {
diff --git a/damus/Views/Events/ZapEvent.swift b/damus/Views/Events/ZapEvent.swift
@@ -48,15 +48,6 @@ struct ZapEvent: View {
}
-let test_zap_invoice = ZapInvoice(description: .description("description"), amount: 10000, string: "lnbc1", expiry: 1000000, payment_hash: Data(), created_at: 1000000)
-let test_zap_request_ev = NostrEvent(content: "hi", pubkey: "pk", kind: 9734)
-let test_zap_request = ZapRequest(ev: test_zap_request_ev)
-let test_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: nil)
-
-let test_private_zap = Zap(event: test_event, invoice: test_zap_invoice, zapper: "zapper", target: .profile("pk"), raw_request: test_zap_request, is_anon: false, private_request: .init(ev: test_event))
-
-let test_pending_zap = PendingZap(amount_msat: 10000, target: .note(id: "id", author: "pk"), request: .normal(test_zap_request), type: .pub, state: .external(.init(state: .fetching_invoice)))
-
struct ZapEvent_Previews: PreviewProvider {
static var previews: some View {
VStack {
diff --git a/damus/Views/Notifications/EventGroupView.swift b/damus/Views/Notifications/EventGroupView.swift
@@ -258,12 +258,6 @@ struct EventGroupView: View {
}
}
-let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
-let test_repost_1 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk1", kind: 6, tags: [], createdAt: 1)
-let test_repost_2 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk2", kind: 6, tags: [], createdAt: 1)
-let test_reposts = [test_repost_1, test_repost_2]
-let test_event_group = EventGroup(events: test_reposts)
-
struct EventGroupView_Previews: PreviewProvider {
static var previews: some View {
VStack {
diff --git a/damus/Views/Profile/EditMetadataView.swift b/damus/Views/Profile/EditMetadataView.swift
@@ -62,10 +62,12 @@ struct EditMetadataView: View {
func save() {
let profile = to_profile()
- guard let keypair = damus_state.keypair.to_full() else {
+ guard let keypair = damus_state.keypair.to_full(),
+ let metadata_ev = make_metadata_event(keypair: keypair, metadata: profile)
+ else {
return
}
- let metadata_ev = make_metadata_event(keypair: keypair, metadata: profile)
+
damus_state.postbox.send(metadata_ev)
}
diff --git a/damus/Views/Reactions/ReactionView.swift b/damus/Views/Reactions/ReactionView.swift
@@ -28,6 +28,6 @@ struct ReactionView: View {
struct ReactionView_Previews: PreviewProvider {
static var previews: some View {
- ReactionView(damus_state: test_damus_state(), reaction: NostrEvent(id: "", content: "🤙🏼", pubkey: ""))
+ ReactionView(damus_state: test_damus_state(), reaction: NostrEvent(content: "🤙🏼", keypair: test_keypair)!)
}
}
diff --git a/damus/Views/Relays/RecommendedRelayView.swift b/damus/Views/Relays/RecommendedRelayView.swift
@@ -24,9 +24,9 @@ struct RecommendedRelayView: View {
var body: some View {
ZStack {
HStack {
- if let privkey = damus.keypair.privkey {
+ if let keypair = damus.keypair.to_full() {
if showActionButtons && add_button {
- AddButton(privkey: privkey, showText: false)
+ AddButton(keypair: keypair, showText: false)
}
}
@@ -59,8 +59,8 @@ struct RecommendedRelayView: View {
}
.swipeActions {
if add_button {
- if let privkey = damus.keypair.privkey {
- AddButton(privkey: privkey, showText: false)
+ if let keypair = damus.keypair.to_full() {
+ AddButton(keypair: keypair, showText: false)
.tint(.accentColor)
}
}
@@ -68,8 +68,8 @@ struct RecommendedRelayView: View {
.contextMenu {
CopyAction(relay: relay)
- if let privkey = damus.keypair.privkey {
- AddButton(privkey: privkey, showText: true)
+ if let keypair = damus.keypair.to_full() {
+ AddButton(keypair: keypair, showText: true)
}
}
}
@@ -82,9 +82,9 @@ struct RecommendedRelayView: View {
}
}
- func AddButton(privkey: String, showText: Bool) -> some View {
+ func AddButton(keypair: FullKeypair, showText: Bool) -> some View {
Button(action: {
- add_action(privkey: privkey)
+ add_action(keypair: keypair)
}) {
if showText {
Text(NSLocalizedString("Connect", comment: "Button to connect to recommended relay server."))
@@ -97,11 +97,11 @@ struct RecommendedRelayView: View {
}
}
- func add_action(privkey: String) {
+ func add_action(keypair: FullKeypair) {
guard let ev_before_add = damus.contacts.event else {
return
}
- guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.our_descriptors, relay: relay, info: .rw) else {
+ guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: damus.pool.our_descriptors, relay: relay, info: .rw) else {
return
}
process_contact_event(state: damus, ev: ev_after_add)
diff --git a/damus/Views/Relays/RelayConfigView.swift b/damus/Views/Relays/RelayConfigView.swift
@@ -54,7 +54,7 @@ struct RelayConfigView: View {
VStack {
HStack {
Spacer()
- if(!new_relay.isEmpty) {
+ if !new_relay.isEmpty {
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
new_relay = ""
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
@@ -76,15 +76,9 @@ struct RelayConfigView: View {
new_relay.removeLast();
}
- guard let url = RelayURL(new_relay) else {
- return
- }
-
- guard let ev = state.contacts.event else {
- return
- }
-
- guard let privkey = state.keypair.privkey else {
+ guard let url = RelayURL(new_relay),
+ let ev = state.contacts.event,
+ let keypair = state.keypair.to_full() else {
return
}
@@ -103,7 +97,7 @@ struct RelayConfigView: View {
state.pool.connect(to: [new_relay])
- guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.our_descriptors, relay: new_relay, info: info) else {
+ guard let new_ev = add_relay(ev: ev, keypair: keypair, current_relays: state.pool.our_descriptors, relay: new_relay, info: info) else {
return
}
diff --git a/damus/Views/Relays/RelayDetailView.swift b/damus/Views/Relays/RelayDetailView.swift
@@ -40,35 +40,38 @@ struct RelayDetailView: View {
return Text("No data available", comment: "Text indicating that there is no data available to show for specific metadata about a relay server.")
}
}
-
+
+ func RemoveRelayButton(_ keypair: FullKeypair) -> some View {
+ Button(action: {
+ guard let ev = state.contacts.event else {
+ return
+ }
+
+ let descriptors = state.pool.our_descriptors
+ guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else {
+ return
+ }
+
+ process_contact_event(state: state, ev: new_ev)
+ state.postbox.send(new_ev)
+ dismiss()
+ }) {
+ Text("Disconnect From Relay", comment: "Button to disconnect from the relay.")
+ }
+ }
+
var body: some View {
Group {
Form {
-
- if let privkey = state.keypair.privkey {
+ if let keypair = state.keypair.to_full() {
if check_connection() {
- Button(action: {
- guard let ev = state.contacts.event else {
- return
- }
-
- let descriptors = state.pool.our_descriptors
- guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, privkey: privkey, relay: relay) else {
- return
- }
-
- process_contact_event(state: state, ev: new_ev)
- state.postbox.send(new_ev)
- dismiss()
- }) {
- Text("Disconnect From Relay", comment: "Button to disconnect from the relay.")
- }
+ RemoveRelayButton(keypair)
} else {
Button(action: {
guard let ev_before_add = state.contacts.event else {
return
}
- guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
+ guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
return
}
process_contact_event(state: state, ev: ev_after_add)
diff --git a/damus/Views/Relays/RelayView.swift b/damus/Views/Relays/RelayView.swift
@@ -95,7 +95,8 @@ struct RelayView: View {
}
let descriptors = state.pool.our_descriptors
- guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, privkey: privkey, relay: relay) else {
+ guard let keypair = state.keypair.to_full(),
+ let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else {
return
}
diff --git a/damus/Views/ReportView.swift b/damus/Views/ReportView.swift
@@ -10,8 +10,8 @@ import SwiftUI
struct ReportView: View {
let postbox: PostBox
let target: ReportTarget
- let privkey: String
-
+ let keypair: FullKeypair
+
@State var report_sent: Bool = false
@State var report_id: String = ""
@State var report_message: String = ""
@@ -46,7 +46,8 @@ struct ReportView: View {
}
func do_send_report() {
- guard let selected_report_type, let ev = send_report(privkey: privkey, postbox: postbox, target: target, type: selected_report_type, message: report_message) else {
+ guard let selected_report_type,
+ let ev = send_report(keypair: keypair, postbox: postbox, target: target, type: selected_report_type, message: report_message) else {
return
}
@@ -116,9 +117,9 @@ struct ReportView: View {
}
}
-func send_report(privkey: String, postbox: PostBox, target: ReportTarget, type: ReportType, message: String) -> NostrEvent? {
+func send_report(keypair: FullKeypair, postbox: PostBox, target: ReportTarget, type: ReportType, message: String) -> NostrEvent? {
let report = Report(type: type, target: target, message: message)
- guard let ev = create_report_event(privkey: privkey, report: report) else {
+ guard let ev = create_report_event(keypair: keypair, report: report) else {
return nil
}
postbox.send(ev)
@@ -130,10 +131,10 @@ struct ReportView_Previews: PreviewProvider {
let ds = test_damus_state()
VStack {
- ReportView(postbox: ds.postbox, target: ReportTarget.user(""), privkey: "")
-
- ReportView(postbox: ds.postbox, target: ReportTarget.user(""), privkey: "", report_sent: true, report_id: "report_id")
-
+ ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!)
+
+ ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!, report_sent: true, report_id: "report_id")
+
}
}
}
diff --git a/damus/Views/Reposts/RepostView.swift b/damus/Views/Reposts/RepostView.swift
@@ -18,7 +18,7 @@ struct RepostView: View {
struct RepostView_Previews: PreviewProvider {
static var previews: some View {
- RepostView(damus_state: test_damus_state(), repost: NostrEvent(id: "", content: "", pubkey: ""))
+ RepostView(damus_state: test_damus_state(), repost: NostrEvent(content: "", keypair: test_keypair)!)
}
}
diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift
@@ -130,8 +130,8 @@ struct SaveKeysView: View {
let metadata = create_account_to_metadata(account)
let contacts_ev = make_first_contact_event(keypair: account.keypair)
- if let keypair = account.keypair.to_full() {
- let metadata_ev = make_metadata_event(keypair: keypair, metadata: metadata)
+ if let keypair = account.keypair.to_full(),
+ let metadata_ev = make_metadata_event(keypair: keypair, metadata: metadata) {
self.pool.send(.event(metadata_ev))
}
diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift
@@ -181,7 +181,9 @@ struct WalletView: View {
}
profile.damus_donation = settings.donation_percent
- let meta = make_metadata_event(keypair: keypair, metadata: profile)
+ guard let meta = make_metadata_event(keypair: keypair, metadata: profile) else {
+ return
+ }
let tsprofile = TimestampedProfile(profile: profile, timestamp: meta.created_at, event: meta)
damus_state.profiles.add(id: damus_state.pubkey, profile: tsprofile)
damus_state.postbox.send(meta)
diff --git a/damusTests/EventGroupViewTests.swift b/damusTests/EventGroupViewTests.swift
@@ -29,14 +29,17 @@ final class EventGroupViewTests: XCTestCase {
let damusState = test_damus_state()
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
- let repost1 = NostrEvent(id: "", content: encodedPost, pubkey: "pk1", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
- let repost2 = NostrEvent(id: "", content: encodedPost, pubkey: "pk2", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
- let repost3 = NostrEvent(id: "", content: encodedPost, pubkey: "pk3", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+ let pk1 = Keypair(pubkey: "pk1", privkey: nil)
+ let pk2 = Keypair(pubkey: "pk2", privkey: nil)
+ let pk3 = Keypair(pubkey: "pk3", privkey: nil)
+ let repost1 = NostrEvent(content: encodedPost, keypair: pk1, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
+ let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
+ let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: []))), [])
- XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1]))), ["pk1"])
- XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2]))), ["pk1", "pk2"])
- XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), ["pk1", "pk2", "pk3"])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1]))), [pk1.pubkey])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2]))), [pk1.pubkey, pk2.pubkey])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), [pk1.pubkey, pk2.pubkey, pk3.pubkey])
}
func testReactingToText() throws {
@@ -44,20 +47,25 @@ final class EventGroupViewTests: XCTestCase {
let damusState = test_damus_state()
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
- let repost1 = NostrEvent(id: "", content: encodedPost, pubkey: "pk1", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
- let repost2 = NostrEvent(id: "", content: encodedPost, pubkey: "pk2", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
- let repost3 = NostrEvent(id: "", content: encodedPost, pubkey: "pk3", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+
+ let pk1 = Keypair(pubkey: "pk1", privkey: nil)
+ let pk2 = Keypair(pubkey: "pk2", privkey: nil)
+ let pk3 = Keypair(pubkey: "pk3", privkey: nil)
+
+ let repost1 = NostrEvent(content: encodedPost, keypair: pk1, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
+ let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
+ let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, pubkeys: [], locale: enUsLocale), "??")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: ["pk1"], locale: enUsLocale), "pk1:pk1 reposted a note you were tagged in")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: ["pk1", "pk2"], locale: enUsLocale), "pk1:pk1 and pk2:pk2 reposted a note you were tagged in")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, pubkeys: ["pk1", "pk2", "pk3"], locale: enUsLocale), "pk1:pk1 and 2 others reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: [pk1.pubkey], locale: enUsLocale), "pk1:pk1 reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey], locale: enUsLocale), "pk1:pk1 and pk2:pk2 reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: enUsLocale), "pk1:pk1 and 2 others reposted a note you were tagged in")
Bundle.main.localizations.map { Locale(identifier: $0) }.forEach {
XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, pubkeys: [], locale: $0), "??")
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: ["pk1"], locale: $0))
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: ["pk1", "pk2"], locale: $0))
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost3])), ev: test_event, pubkeys: ["pk1", "pk2", "pk3"], locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, pubkeys: [pk1.pubkey], locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey], locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost3])), ev: test_event, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: $0))
}
}
diff --git a/damusTests/LikeTests.swift b/damusTests/LikeTests.swift
@@ -19,31 +19,25 @@ class LikeTests: XCTestCase {
}
func testLikeHasNotification() throws {
- let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe"
- let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2"
- let liked = NostrEvent(content: "awesome #[0] post", pubkey: "orig_pk", tags: [["p", "cindy"], ["e", "bob"]])
- liked.calculate_id()
+ let liked = NostrEvent(content: "awesome #[0] post", keypair: test_keypair, tags: [["p", "cindy"], ["e", "bob"]])!
let id = liked.id
- let like_ev = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked)
-
- XCTAssertTrue(like_ev.references(id: "orig_pk", key: "p"))
+ let like_ev = make_like_event(keypair: test_keypair_full, liked: liked)!
+
+ XCTAssertTrue(like_ev.references(id: test_keypair.pubkey, key: "p"))
XCTAssertTrue(like_ev.references(id: "cindy", key: "p"))
XCTAssertTrue(like_ev.references(id: "bob", key: "e"))
XCTAssertEqual(like_ev.last_refid()!.ref_id, id)
}
func testToReactionEmoji() {
- let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe"
- let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2"
- let liked = NostrEvent(content: "awesome #[0] post", pubkey: "orig_pk", tags: [["p", "cindy"], ["e", "bob"]])
- liked.calculate_id()
-
- let emptyReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "")
- let plusReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "+")
- let minusReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "-")
- let heartReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "❤️")
- let thumbsUpReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "👍")
- let shakaReaction = make_like_event(pubkey: pubkey, privkey: privkey, liked: liked, content: "🤙")
+ let liked = NostrEvent(content: "awesome #[0] post", keypair: test_keypair, tags: [["p", "cindy"], ["e", "bob"]])!
+
+ let emptyReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "")!
+ let plusReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "+")!
+ let minusReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "-")!
+ let heartReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "❤️")!
+ let thumbsUpReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "👍")!
+ let shakaReaction = make_like_event(keypair: test_keypair_full, liked: liked, content: "🤙")!
XCTAssertEqual(to_reaction_emoji(ev: emptyReaction), "❤️")
XCTAssertEqual(to_reaction_emoji(ev: plusReaction), "❤️")
diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift
@@ -331,8 +331,8 @@ class ReplyTests: XCTestCase {
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
let blocks = parse_post_blocks(content: content)
let post = NostrPost(content: content, references: [reply_ref])
- let ev = post_to_event(post: post, privkey: evid, pubkey: pk)
-
+ let ev = post_to_event(post: post, keypair: test_keypair_full)!
+
XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
@@ -347,8 +347,8 @@ class ReplyTests: XCTestCase {
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
let blocks = parse_post_blocks(content: content)
let post = NostrPost(content: content, references: [reply_ref])
- let ev = post_to_event(post: post, privkey: evid, pubkey: pk)
-
+ let ev = post_to_event(post: post, keypair: test_keypair_full)!
+
XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
@@ -367,7 +367,7 @@ class ReplyTests: XCTestCase {
]
let post = NostrPost(content: "this is a (@\(npub)) mention", references: refs)
- let ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
+ let ev = post_to_event(post: post, keypair: test_keypair_full)!
XCTAssertEqual(ev.content, "this is a (nostr:\(npub)) mention")
XCTAssertEqual(ev.tags[2][1], pubkey)
diff --git a/damusTests/UserSearchCacheTests.swift b/damusTests/UserSearchCacheTests.swift
@@ -116,7 +116,6 @@ final class UserSearchCacheTests: XCTestCase {
private func createContactsEventWithPetnames(pubkeysToPetnames: [String: String]) throws -> NostrEvent {
let keypair = try XCTUnwrap(keypair)
- let privkey = try XCTUnwrap(keypair.privkey)
let bootstrapRelays = load_bootstrap_relays(pubkey: keypair.pubkey)
let relayInfo = RelayInfo(read: true, write: true)
@@ -132,13 +131,7 @@ final class UserSearchCacheTests: XCTestCase {
["p", $0.element.key, "", $0.element.value]
}
- let ev = NostrEvent(content: relayJson,
- pubkey: keypair.pubkey,
- kind: NostrKind.contacts.rawValue,
- tags: tags)
- ev.calculate_id()
- ev.sign(privkey: privkey)
- return ev
+ return NostrEvent(content: relayJson, keypair: keypair, kind: NostrKind.contacts.rawValue, tags: tags)!
}
}
diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift
@@ -180,11 +180,9 @@ class damusTests: XCTestCase {
}
func testMakeHashtagPost() {
- let privkey = "d05f5fcceef3e4529703f62a29222d6ee2d1b7bf1f24729b5e01df7c633cec8a"
- let pubkey = "6e59d3b78b1c1490a6489c94405873b57d8ef398a830ae5e39608f4107e9a790"
let post = NostrPost(content: "#damus some content #bitcoin derp", references: [])
- let ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
-
+ let ev = post_to_event(post: post, keypair: test_keypair_full)!
+
XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(ev.content, "#damus some content #bitcoin derp")
XCTAssertEqual(ev.tags[0][0], "t")
diff --git a/nostrdb/NdbNote.swift b/nostrdb/NdbNote.swift
@@ -34,7 +34,7 @@ enum NdbData {
}
}
-class NdbNote: Equatable {
+class NdbNote: Equatable, Hashable {
// we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
private let owned: Bool
let count: Int
@@ -66,14 +66,14 @@ class NdbNote: Equatable {
ndb_note_content_length(note)
}
- /// These are unsafe if working on owned data and if this outlives the NdbNote
- var id: Data {
- Data(buffer: UnsafeBufferPointer(start: ndb_note_id(note), count: 32))
+ /// NDBTODO: make this into data
+ var id: String {
+ hex_encode(Data(buffer: UnsafeBufferPointer(start: ndb_note_id(note), count: 32)))
}
- /// Make a copy if this outlives the note and the note has owned data!
- var pubkey: Data {
- Data(buffer: UnsafeBufferPointer(start: ndb_note_pubkey(note), count: 32))
+ /// NDBTODO: make this into data
+ var pubkey: String {
+ hex_encode(Data(buffer: UnsafeBufferPointer(start: ndb_note_pubkey(note), count: 32)))
}
var created_at: UInt32 {
@@ -98,6 +98,64 @@ class NdbNote: Equatable {
return lhs.id == rhs.id
}
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(id)
+ }
+
+ static let max_note_size: Int = 2 << 18
+
+ init?(content: String, keypair: Keypair, kind: Int = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
+
+ var builder = ndb_builder()
+ let buflen = MAX_NOTE_SIZE
+ let buf = malloc(buflen)
+ let idbuf = malloc(buflen)
+
+ ndb_builder_init(&builder, buf, Int32(buflen))
+
+ guard var pk_raw = hex_decode(keypair.pubkey) else { return nil }
+
+ ndb_builder_set_pubkey(&builder, &pk_raw)
+ ndb_builder_set_kind(&builder, UInt32(kind))
+ ndb_builder_set_created_at(&builder, createdAt)
+
+ for tag in tags {
+ ndb_builder_new_tag(&builder);
+ for elem in tag {
+ _ = elem.withCString { eptr in
+ ndb_builder_push_tag_str(&builder, eptr, Int32(elem.utf8.count))
+ }
+ }
+ }
+
+ _ = content.withCString { cptr in
+ ndb_builder_set_content(&builder, content, Int32(content.utf8.count));
+ }
+
+ var n = UnsafeMutablePointer<ndb_note>?(nil)
+
+ let keypair = keypair.privkey.map { sec in
+ var kp = ndb_keypair()
+ return sec.withCString { secptr in
+ ndb_decode_key(secptr, &kp)
+ return kp
+ }
+ }
+
+ var len: Int32 = 0
+ if var keypair {
+ len = ndb_builder_finalize(&builder, &n, &keypair)
+ } else {
+ len = ndb_builder_finalize(&builder, &n, nil)
+ }
+
+ free(idbuf)
+
+ self.owned = true
+ self.count = Int(len)
+ self.note = realloc(n, Int(len)).assumingMemoryBound(to: ndb_note.self)
+ }
+
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? {
return json.withCString { cstr in
return NdbNote.owned_from_json_cstr(
@@ -135,7 +193,7 @@ extension NdbNote {
}
var known_kind: NostrKind? {
- return NostrKind.init(rawValue: Int(kind))
+ return NostrKind.init(rawValue: kind)
}
var too_big: Bool {
@@ -180,6 +238,10 @@ extension NdbNote {
References.pubkeys(tags: self.tags)
}
+ public var referenced_hashtags: LazyFilterSequence<References> {
+ References.hashtags(tags: self.tags)
+ }
+
func event_refs(_ privkey: String?) -> [EventRef] {
if let rs = _event_refs {
return rs
@@ -220,7 +282,7 @@ extension NdbNote {
}
// NDBTODO: don't hex encode
- var pubkey = hex_encode(self.pubkey)
+ var pubkey = self.pubkey
// This is our DM, we need to use the pubkey of the person we're talking to instead
if our_pubkey == pubkey {
@@ -266,7 +328,7 @@ extension NdbNote {
}
}
- return hex_encode(self.id)
+ return self.id
}
public func last_refid() -> ReferencedId? {
diff --git a/nostrdb/NdbTagElem.swift b/nostrdb/NdbTagElem.swift
@@ -31,32 +31,60 @@ struct NdbStrIter: IteratorProtocol {
}
}
-struct NdbTagElem: Sequence {
+struct NdbTagElem: Sequence, Hashable {
+
let note: NdbNote
let tag: UnsafeMutablePointer<ndb_tag>
let index: Int32
+ let str: ndb_str
+
+ func hash(into hasher: inout Hasher) {
+ if str.flag == NDB_PACKED_ID {
+ hasher.combine(bytes: UnsafeRawBufferPointer(start: str.id, count: 32))
+ } else {
+ hasher.combine(bytes: UnsafeRawBufferPointer(start: str.str, count: strlen(str.str)))
+ }
+ }
+
+ static func == (lhs: NdbTagElem, rhs: NdbTagElem) -> Bool {
+ if lhs.str.flag == NDB_PACKED_ID && rhs.str.flag == NDB_PACKED_ID {
+ return memcmp(lhs.str.id, rhs.str.id, 32) == 0
+ } else if lhs.str.flag == NDB_PACKED_ID || rhs.str.flag == NDB_PACKED_ID {
+ return false
+ }
+
+ let l = strlen(lhs.str.str)
+ let r = strlen(rhs.str.str)
+ if l != r { return false }
+
+ return memcmp(lhs.str.str, rhs.str.str, r) == 0
+ }
init(note: NdbNote, tag: UnsafeMutablePointer<ndb_tag>, index: Int32) {
self.note = note
self.tag = tag
self.index = index
+ self.str = ndb_tag_str(note.note, tag, index)
}
var is_id: Bool {
- return ndb_tag_str(note.note, tag, index).flag == NDB_PACKED_ID
+ return str.flag == NDB_PACKED_ID
}
var count: Int {
- let r = ndb_tag_str(note.note, tag, index)
- if r.flag == NDB_PACKED_ID {
+ if str.flag == NDB_PACKED_ID {
return 32
} else {
- return strlen(r.str)
+ return strlen(str.str)
}
}
func matches_char(_ c: AsciiCharacter) -> Bool {
- return ndb_tag_matches_char(note.note, tag, index, c.cchar) == 1
+ return str.str[0] == c.cchar && str.str[1] == 0
+ }
+
+ var ndbstr: ndb_str {
+ return ndb_tag_str(note.note, tag, index)
}
func data() -> NdbData {
diff --git a/nostrdb/NdbTagsIterator.swift b/nostrdb/NdbTagsIterator.swift
@@ -42,6 +42,12 @@ struct TagsSequence: Sequence {
note.note.pointee.tags.count
}
+ func strings() -> [[String]] {
+ return self.map { tag in
+ tag.map { t in t.string() }
+ }
+ }
+
// no O(1) indexing on top-level tag lists unfortunately :(
// bit it's very fast to iterate over each tag since the number of tags
// are stored and the elements are fixed size.
diff --git a/nostrdb/Test/NdbTests.swift b/nostrdb/Test/NdbTests.swift
@@ -25,8 +25,8 @@ final class NdbTests: XCTestCase {
let id = "20d0ff27d6fcb13de8366328c5b1a7af26bcac07f2e558fbebd5e9242e608c09"
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
- XCTAssertEqual(hex_encode(note.id), id)
- XCTAssertEqual(hex_encode(note.pubkey), pubkey)
+ XCTAssertEqual(note.id, id)
+ XCTAssertEqual(note.pubkey, pubkey)
XCTAssertEqual(note.count, 34322)
XCTAssertEqual(note.kind, 3)
@@ -126,8 +126,8 @@ final class NdbTests: XCTestCase {
guard let event else { return }
XCTAssertEqual(note.content_len, UInt32(event.content.utf8.count))
- XCTAssertEqual(hex_encode(note.pubkey), event.pubkey)
- XCTAssertEqual(hex_encode(note.id), event.id)
+ XCTAssertEqual(note.pubkey, event.pubkey)
+ XCTAssertEqual(note.id, event.id)
let ev_blocks = event.blocks(nil)
let note_blocks = note.blocks(nil)
diff --git a/nostrdb/nostrdb.c b/nostrdb/nostrdb.c
@@ -3,9 +3,15 @@
#include "jsmn.h"
#include "hex.h"
#include "cursor.h"
+#include "random.h"
+#include "sha256.h"
#include <stdlib.h>
#include <limits.h>
+#include "secp256k1.h"
+#include "secp256k1_ecdh.h"
+#include "secp256k1_schnorrsig.h"
+
struct ndb_json_parser {
const char *json;
int json_len;
@@ -20,11 +26,10 @@ static inline int cursor_push_tag(struct cursor *cur, struct ndb_tag *tag)
return cursor_push_u16(cur, tag->count);
}
-int ndb_builder_new(struct ndb_builder *builder, unsigned char *buf,
- int bufsize)
+int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf,
+ int bufsize)
{
struct ndb_note *note;
- struct cursor mem;
int half, size, str_indices_size;
// come on bruh
@@ -38,20 +43,21 @@ int ndb_builder_new(struct ndb_builder *builder, unsigned char *buf,
//debug("size %d half %d str_indices %d\n", size, half, str_indices_size);
// make a safe cursor of our available memory
- make_cursor(buf, buf + bufsize, &mem);
+ make_cursor(buf, buf + bufsize, &builder->mem);
note = builder->note = (struct ndb_note *)buf;
// take slices of the memory into subcursors
- if (!(cursor_slice(&mem, &builder->note_cur, half) &&
- cursor_slice(&mem, &builder->strings, half) &&
- cursor_slice(&mem, &builder->str_indices, str_indices_size))) {
+ if (!(cursor_slice(&builder->mem, &builder->note_cur, half) &&
+ cursor_slice(&builder->mem, &builder->strings, half) &&
+ cursor_slice(&builder->mem, &builder->str_indices, str_indices_size))) {
return 0;
}
memset(note, 0, sizeof(*note));
builder->note_cur.p += sizeof(*note);
+ note->strings = builder->strings.start - buf;
note->version = 1;
return 1;
@@ -82,7 +88,7 @@ static inline int ndb_json_parser_init(struct ndb_json_parser *p,
// the more important stuff gets a larger chunk and then it spirals
// downward into smaller chunks. Thanks for coming to my TED talk.
- if (!ndb_builder_new(&p->builder, buf, half))
+ if (!ndb_builder_init(&p->builder, buf, half))
return 0;
jsmn_init(&p->json_parser);
@@ -99,11 +105,234 @@ static inline int ndb_json_parser_parse(struct ndb_json_parser *p)
return p->num_tokens;
}
-int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note)
+static int cursor_push_unescaped_char(struct cursor *cur, char c1, char c2)
+{
+ switch (c2) {
+ case 't': return cursor_push_byte(cur, '\t');
+ case 'n': return cursor_push_byte(cur, '\n');
+ case 'r': return cursor_push_byte(cur, '\r');
+ case 'b': return cursor_push_byte(cur, '\b');
+ case 'f': return cursor_push_byte(cur, '\f');
+ case '\\': return cursor_push_byte(cur, '\\');
+ case '"': return cursor_push_byte(cur, '"');
+ case 'u':
+ // these aren't handled yet
+ return 0;
+ default:
+ return cursor_push_byte(cur, c1) && cursor_push_byte(cur, c2);
+ }
+}
+
+static int cursor_push_escaped_char(struct cursor *cur, char c)
+{
+ switch (c) {
+ case '"': return cursor_push_str(cur, "\\\"");
+ case '\\': return cursor_push_str(cur, "\\\\");
+ case '\b': return cursor_push_str(cur, "\\b");
+ case '\f': return cursor_push_str(cur, "\\f");
+ case '\n': return cursor_push_str(cur, "\\n");
+ case '\r': return cursor_push_str(cur, "\\r");
+ case '\t': return cursor_push_str(cur, "\\t");
+ // TODO: \u hex hex hex hex
+ }
+ return cursor_push_byte(cur, c);
+}
+
+static int cursor_push_hex_str(struct cursor *cur, unsigned char *buf, int len)
+{
+ int i;
+
+ if (len % 2 != 0)
+ return 0;
+
+ if (!cursor_push_byte(cur, '"'))
+ return 0;
+
+ for (i = 0; i < len; i++) {
+ unsigned int c = ((const unsigned char *)buf)[i];
+ if (!cursor_push_byte(cur, hexchar(c >> 4)))
+ return 0;
+ if (!cursor_push_byte(cur, hexchar(c & 0xF)))
+ return 0;
+ }
+
+ if (!cursor_push_byte(cur, '"'))
+ return 0;
+
+ return 1;
+}
+
+static int cursor_push_jsonstr(struct cursor *cur, const char *str)
+{
+ int i;
+ int len;
+
+ len = strlen(str);
+
+ if (!cursor_push_byte(cur, '"'))
+ return 0;
+
+ for (i = 0; i < len; i++) {
+ if (!cursor_push_escaped_char(cur, str[i]))
+ return 0;
+ }
+
+ if (!cursor_push_byte(cur, '"'))
+ return 0;
+
+ return 1;
+}
+
+
+static inline int cursor_push_json_tag_str(struct cursor *cur, struct ndb_str str)
+{
+ if (str.flag == NDB_PACKED_ID)
+ return cursor_push_hex_str(cur, str.id, 32);
+
+ return cursor_push_jsonstr(cur, str.str);
+}
+
+static int cursor_push_json_tag(struct cursor *cur, struct ndb_note *note,
+ struct ndb_tag *tag)
+{
+ int i;
+
+ if (!cursor_push_byte(cur, '['))
+ return 0;
+
+ for (i = 0; i < tag->count; i++) {
+ if (!cursor_push_json_tag_str(cur, ndb_note_str(note, &tag->strs[i])))
+ return 0;
+ if (i != tag->count-1 && !cursor_push_byte(cur, ','))
+ return 0;
+ }
+
+ return cursor_push_byte(cur, ']');
+}
+
+static int cursor_push_json_tags(struct cursor *cur, struct ndb_note *note)
+{
+ int i;
+ struct ndb_iterator iter, *it = &iter;
+ ndb_tags_iterate_start(note, it);
+
+ if (!cursor_push_byte(cur, '['))
+ return 0;
+
+ i = 0;
+ while (ndb_tags_iterate_next(it)) {
+ if (!cursor_push_json_tag(cur, note, it->tag))
+ return 0;
+ if (i != note->tags.count-1 && !cursor_push_str(cur, ","))
+ return 0;
+ i++;
+ }
+
+ if (!cursor_push_byte(cur, ']'))
+ return 0;
+
+ return 1;
+}
+
+static int ndb_event_commitment(struct ndb_note *ev, unsigned char *buf, int buflen)
+{
+ char timebuf[16] = {0};
+ char kindbuf[16] = {0};
+ char pubkey[65];
+ struct cursor cur;
+ int ok;
+
+ if (!hex_encode(ev->pubkey, sizeof(ev->pubkey), pubkey, 32))
+ return 0;
+
+ make_cursor(buf, buf + buflen, &cur);
+
+ snprintf(timebuf, sizeof(timebuf), "%d", ev->created_at);
+ snprintf(kindbuf, sizeof(kindbuf), "%d", ev->kind);
+
+ ok =
+ cursor_push_str(&cur, "[0,\"") &&
+ cursor_push_str(&cur, pubkey) &&
+ cursor_push_str(&cur, "\",") &&
+ cursor_push_str(&cur, timebuf) &&
+ cursor_push_str(&cur, ",") &&
+ cursor_push_str(&cur, kindbuf) &&
+ cursor_push_str(&cur, ",") &&
+ cursor_push_json_tags(&cur, ev) &&
+ cursor_push_str(&cur, ",") &&
+ cursor_push_jsonstr(&cur, ndb_note_str(ev, &ev->content).str) &&
+ cursor_push_str(&cur, "]");
+
+ if (!ok)
+ return 0;
+
+ return cur.p - cur.start;
+}
+
+int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen) {
+ int len;
+
+ if (!(len = ndb_event_commitment(note, buf, buflen)))
+ return 0;
+
+ //fprintf(stderr, "%.*s\n", len, buf);
+
+ sha256((struct sha256*)note->id, buf, len);
+
+ return 1;
+}
+
+int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32],
+ unsigned char sig[64])
+{
+ unsigned char aux[32];
+ secp256k1_keypair *pair = (secp256k1_keypair*) keypair->pair;
+
+ if (!fill_random(aux, sizeof(aux)))
+ return 0;
+
+ secp256k1_context *ctx =
+ secp256k1_context_create(SECP256K1_CONTEXT_NONE);
+
+ return secp256k1_schnorrsig_sign32(ctx, sig, id, pair, aux);
+}
+
+int ndb_create_keypair(struct ndb_keypair *kp)
+{
+ secp256k1_keypair *keypair = (secp256k1_keypair*)kp->pair;
+ secp256k1_xonly_pubkey pubkey;
+
+ secp256k1_context *ctx =
+ secp256k1_context_create(SECP256K1_CONTEXT_NONE);;
+
+ /* Try to create a keypair with a valid context, it should only
+ * fail if the secret key is zero or out of range. */
+ if (!secp256k1_keypair_create(ctx, keypair, kp->secret))
+ return 0;
+
+ if (!secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, keypair))
+ return 0;
+
+ /* Serialize the public key. Should always return 1 for a valid public key. */
+ return secp256k1_xonly_pubkey_serialize(ctx, kp->pubkey, &pubkey);
+}
+
+int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair)
+{
+ if (!hex_decode(secstr, strlen(secstr), keypair->secret, 32)) {
+ fprintf(stderr, "could not hex decode secret key\n");
+ return 0;
+ }
+
+ return ndb_create_keypair(keypair);
+}
+
+int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note,
+ struct ndb_keypair *keypair)
{
int strings_len = builder->strings.p - builder->strings.start;
- unsigned char *end = builder->note_cur.p + strings_len;
- int total_size = end - builder->note_cur.start;
+ unsigned char *note_end = builder->note_cur.p + strings_len;
+ int total_size = note_end - builder->note_cur.start;
// move the strings buffer next to the end of our ndb_note
memmove(builder->note_cur.p, builder->strings.start, strings_len);
@@ -116,6 +345,19 @@ int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note)
*note = builder->note;
+ // generate id and sign if we're building this manually
+ if (keypair) {
+ // use the remaining memory for building our id buffer
+ unsigned char *end = builder->mem.end;
+ unsigned char *start = (unsigned char*)(*note) + total_size;
+
+ if (!ndb_calculate_id(*note, start, end - start))
+ return 0;
+
+ if (!ndb_sign_id(keypair, (*note)->id, (*note)->sig))
+ return 0;
+ }
+
return total_size;
}
@@ -297,44 +539,8 @@ static int ndb_builder_make_json_str(struct ndb_builder *builder,
return 0;
}
- switch (*(p+1)) {
- case 't':
- if (!cursor_push_byte(&builder->strings, '\t'))
- return 0;
- break;
- case 'n':
- if (!cursor_push_byte(&builder->strings, '\n'))
- return 0;
- break;
- case 'r':
- if (!cursor_push_byte(&builder->strings, '\r'))
- return 0;
- break;
- case 'b':
- if (!cursor_push_byte(&builder->strings, '\b'))
- return 0;
- break;
- case 'f':
- if (!cursor_push_byte(&builder->strings, '\f'))
- return 0;
- break;
- case '\\':
- if (!cursor_push_byte(&builder->strings, '\\'))
- return 0;
- break;
- case '"':
- if (!cursor_push_byte(&builder->strings, '"'))
- return 0;
- break;
- case 'u':
- // these aren't handled yet
+ if (!cursor_push_unescaped_char(&builder->strings, *p, *(p+1)))
return 0;
- default:
- if (!cursor_push_byte(&builder->strings, *p) ||
- !cursor_push_byte(&builder->strings, *(p+1)))
- return 0;
- break;
- }
p++; // Skip the character following the backslash
start = p + 1; // Update the start pointer to the next character
@@ -482,7 +688,7 @@ int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
// sig
tok = &parser.toks[i+1];
hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
- ndb_builder_set_signature(&parser.builder, hexbuf);
+ ndb_builder_set_sig(&parser.builder, hexbuf);
} else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) {
// kind
tok = &parser.toks[i+1];
@@ -524,7 +730,7 @@ int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
}
}
- return ndb_builder_finalize(&parser.builder, note);
+ return ndb_builder_finalize(&parser.builder, note, NULL);
}
void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey)
@@ -537,10 +743,9 @@ void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id)
memcpy(builder->note->id, id, 32);
}
-void ndb_builder_set_signature(struct ndb_builder *builder,
- unsigned char *signature)
+void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig)
{
- memcpy(builder->note->signature, signature, 64);
+ memcpy(builder->note->sig, sig, 64);
}
void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind)
@@ -548,6 +753,11 @@ void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind)
builder->note->kind = kind;
}
+void ndb_builder_set_created_at(struct ndb_builder *builder, uint32_t created_at)
+{
+ builder->note->created_at = created_at;
+}
+
int ndb_builder_new_tag(struct ndb_builder *builder)
{
builder->note->tags.count++;
diff --git a/nostrdb/nostrdb.h b/nostrdb/nostrdb.h
@@ -12,6 +12,12 @@ struct ndb_str {
};
};
+struct ndb_keypair {
+ unsigned char pubkey[32];
+ unsigned char secret[32];
+ unsigned char pair[96];
+};
+
// these must be byte-aligned, they are directly accessing the serialized data
// representation
#pragma pack(push, 1)
@@ -47,7 +53,7 @@ struct ndb_note {
unsigned char padding[3]; // keep things aligned
unsigned char id[32];
unsigned char pubkey[32];
- unsigned char signature[64];
+ unsigned char sig[64];
uint32_t created_at;
uint32_t kind;
@@ -62,6 +68,7 @@ struct ndb_note {
#pragma pack(pop)
struct ndb_builder {
+ struct cursor mem;
struct cursor note_cur;
struct cursor strings;
struct cursor str_indices;
@@ -77,18 +84,24 @@ struct ndb_iterator {
int index;
};
-// HI BUILDER
+// HELPERS
+int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen);
+int ndb_sign_id(struct ndb_keypair *keypair, unsigned char id[32], unsigned char sig[64]);
+int ndb_create_keypair(struct ndb_keypair *key);
+int ndb_decode_key(const char *secstr, struct ndb_keypair *keypair);
+
+// BUILDER
int ndb_note_from_json(const char *json, int len, struct ndb_note **, unsigned char *buf, int buflen);
-int ndb_builder_new(struct ndb_builder *builder, unsigned char *buf, int bufsize);
-int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note);
+int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf, int bufsize);
+int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note, struct ndb_keypair *privkey);
int ndb_builder_set_content(struct ndb_builder *builder, const char *content, int len);
-void ndb_builder_set_signature(struct ndb_builder *builder, unsigned char *signature);
+void ndb_builder_set_created_at(struct ndb_builder *builder, uint32_t created_at);
+void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig);
void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey);
void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id);
void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind);
int ndb_builder_new_tag(struct ndb_builder *builder);
int ndb_builder_push_tag_str(struct ndb_builder *builder, const char *str, int len);
-// BYE BUILDER
static inline struct ndb_str ndb_note_str(struct ndb_note *note,
union ndb_packed_str *pstr)
@@ -111,17 +124,6 @@ static inline struct ndb_str ndb_tag_str(struct ndb_note *note,
return ndb_note_str(note, &tag->strs[ind]);
}
-static inline int ndb_tag_matches_char(struct ndb_note *note,
- struct ndb_tag *tag, int ind, char c)
-{
- struct ndb_str str = ndb_tag_str(note, tag, ind);
- if (str.str[0] == '\0')
- return 0;
- else if (str.str[0] == c)
- return 1;
- return 0;
-}
-
static inline struct ndb_str ndb_iter_tag_str(struct ndb_iterator *iter,
int ind)
{
@@ -138,9 +140,9 @@ static inline unsigned char * ndb_note_pubkey(struct ndb_note *note)
return note->pubkey;
}
-static inline unsigned char * ndb_note_signature(struct ndb_note *note)
+static inline unsigned char * ndb_note_sig(struct ndb_note *note)
{
- return note->signature;
+ return note->sig;
}
static inline uint32_t ndb_note_created_at(struct ndb_note *note)
diff --git a/nostrdb/random.h b/nostrdb/random.h
@@ -0,0 +1,66 @@
+/*************************************************************************
+ * Copyright (c) 2020-2021 Elichai Turkel *
+ * Distributed under the CC0 software license, see the accompanying file *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+/*
+ * This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems.
+ * It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below.
+ *
+ * Platform randomness sources:
+ * Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom
+ * macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html
+ * FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4
+ * OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom
+ * Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
+ */
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <ntstatus.h>
+#include <bcrypt.h>
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
+//#include <sys/random.h>
+#include <Security/SecRandom.h>
+#elif defined(__OpenBSD__)
+#include <unistd.h>
+#else
+#error "Couldn't identify the OS"
+#endif
+
+#include <stddef.h>
+#include <limits.h>
+#include <stdio.h>
+
+
+/* Returns 1 on success, and 0 on failure. */
+static int fill_random(unsigned char* data, size_t size) {
+#if defined(_WIN32)
+ NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ if (res != STATUS_SUCCESS || size > ULONG_MAX) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__linux__) || defined(__FreeBSD__)
+ /* If `getrandom(2)` is not available you should fallback to /dev/urandom */
+ ssize_t res = getrandom(data, size, 0);
+ if (res < 0 || (size_t)res != size ) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__APPLE__) || defined(__OpenBSD__)
+ /* If `getentropy(2)` is not available you should fallback to either
+ * `SecRandomCopyBytes` or /dev/urandom */
+ int res = SecRandomCopyBytes(kSecRandomDefault, size, data);
+ if (res == 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+#endif
+ return 0;
+}
+
diff --git a/nostrdb/secp256k1.h b/nostrdb/secp256k1.h
@@ -0,0 +1,909 @@
+#ifndef SECP256K1_H
+#define SECP256K1_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+/** Unless explicitly stated all pointer arguments must not be NULL.
+ *
+ * The following rules specify the order of arguments in API calls:
+ *
+ * 1. Context pointers go first, followed by output arguments, combined
+ * output/input arguments, and finally input-only arguments.
+ * 2. Array lengths always immediately follow the argument whose length
+ * they describe, even if this violates rule 1.
+ * 3. Within the OUT/OUTIN/IN groups, pointers to data that is typically generated
+ * later go first. This means: signatures, public nonces, secret nonces,
+ * messages, public keys, secret keys, tweaks.
+ * 4. Arguments that are not data pointers go last, from more complex to less
+ * complex: function pointers, algorithm names, messages, void pointers,
+ * counts, flags, booleans.
+ * 5. Opaque data pointers follow the function pointer they are to be passed to.
+ */
+
+/** Opaque data structure that holds context information
+ *
+ * The primary purpose of context objects is to store randomization data for
+ * enhanced protection against side-channel leakage. This protection is only
+ * effective if the context is randomized after its creation. See
+ * secp256k1_context_create for creation of contexts and
+ * secp256k1_context_randomize for randomization.
+ *
+ * A secondary purpose of context objects is to store pointers to callback
+ * functions that the library will call when certain error states arise. See
+ * secp256k1_context_set_error_callback as well as
+ * secp256k1_context_set_illegal_callback for details. Future library versions
+ * may use context objects for additional purposes.
+ *
+ * A constructed context can safely be used from multiple threads
+ * simultaneously, but API calls that take a non-const pointer to a context
+ * need exclusive access to it. In particular this is the case for
+ * secp256k1_context_destroy, secp256k1_context_preallocated_destroy,
+ * and secp256k1_context_randomize.
+ *
+ * Regarding randomization, either do it once at creation time (in which case
+ * you do not need any locking for the other calls), or use a read-write lock.
+ */
+typedef struct secp256k1_context_struct secp256k1_context;
+
+/** Opaque data structure that holds rewritable "scratch space"
+ *
+ * The purpose of this structure is to replace dynamic memory allocations,
+ * because we target architectures where this may not be available. It is
+ * essentially a resizable (within specified parameters) block of bytes,
+ * which is initially created either by memory allocation or TODO as a pointer
+ * into some fixed rewritable space.
+ *
+ * Unlike the context object, this cannot safely be shared between threads
+ * without additional synchronization logic.
+ */
+typedef struct secp256k1_scratch_space_struct secp256k1_scratch_space;
+
+/** Opaque data structure that holds a parsed and valid public key.
+ *
+ * The exact representation of data inside is implementation defined and not
+ * guaranteed to be portable between different platforms or versions. It is
+ * however guaranteed to be 64 bytes in size, and can be safely copied/moved.
+ * If you need to convert to a format suitable for storage or transmission,
+ * use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse. To
+ * compare keys, use secp256k1_ec_pubkey_cmp.
+ */
+typedef struct {
+ unsigned char data[64];
+} secp256k1_pubkey;
+
+/** Opaque data structured that holds a parsed ECDSA signature.
+ *
+ * The exact representation of data inside is implementation defined and not
+ * guaranteed to be portable between different platforms or versions. It is
+ * however guaranteed to be 64 bytes in size, and can be safely copied/moved.
+ * If you need to convert to a format suitable for storage, transmission, or
+ * comparison, use the secp256k1_ecdsa_signature_serialize_* and
+ * secp256k1_ecdsa_signature_parse_* functions.
+ */
+typedef struct {
+ unsigned char data[64];
+} secp256k1_ecdsa_signature;
+
+/** A pointer to a function to deterministically generate a nonce.
+ *
+ * Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
+ * Out: nonce32: pointer to a 32-byte array to be filled by the function.
+ * In: msg32: the 32-byte message hash being verified (will not be NULL)
+ * key32: pointer to a 32-byte secret key (will not be NULL)
+ * algo16: pointer to a 16-byte array describing the signature
+ * algorithm (will be NULL for ECDSA for compatibility).
+ * data: Arbitrary data pointer that is passed through.
+ * attempt: how many iterations we have tried to find a nonce.
+ * This will almost always be 0, but different attempt values
+ * are required to result in a different nonce.
+ *
+ * Except for test cases, this function should compute some cryptographic hash of
+ * the message, the algorithm, the key and the attempt.
+ */
+typedef int (*secp256k1_nonce_function)(
+ unsigned char *nonce32,
+ const unsigned char *msg32,
+ const unsigned char *key32,
+ const unsigned char *algo16,
+ void *data,
+ unsigned int attempt
+);
+
+# if !defined(SECP256K1_GNUC_PREREQ)
+# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
+# define SECP256K1_GNUC_PREREQ(_maj,_min) \
+ ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min))
+# else
+# define SECP256K1_GNUC_PREREQ(_maj,_min) 0
+# endif
+# endif
+
+/* When this header is used at build-time the SECP256K1_BUILD define needs to be set
+ * to correctly setup export attributes and nullness checks. This is normally done
+ * by secp256k1.c but to guard against this header being included before secp256k1.c
+ * has had a chance to set the define (e.g. via test harnesses that just includes
+ * secp256k1.c) we set SECP256K1_NO_BUILD when this header is processed without the
+ * BUILD define so this condition can be caught.
+ */
+#ifndef SECP256K1_BUILD
+# define SECP256K1_NO_BUILD
+#endif
+
+/* Symbol visibility. */
+#if defined(_WIN32)
+ /* GCC for Windows (e.g., MinGW) accepts the __declspec syntax
+ * for MSVC compatibility. A __declspec declaration implies (but is not
+ * exactly equivalent to) __attribute__ ((visibility("default"))), and so we
+ * actually want __declspec even on GCC, see "Microsoft Windows Function
+ * Attributes" in the GCC manual and the recommendations in
+ * https://gcc.gnu.org/wiki/Visibility. */
+# if defined(SECP256K1_BUILD)
+# if defined(DLL_EXPORT) || defined(SECP256K1_DLL_EXPORT)
+ /* Building libsecp256k1 as a DLL.
+ * 1. If using Libtool, it defines DLL_EXPORT automatically.
+ * 2. In other cases, SECP256K1_DLL_EXPORT must be defined. */
+# define SECP256K1_API extern __declspec (dllexport)
+# endif
+ /* The user must define SECP256K1_STATIC when consuming libsecp256k1 as a static
+ * library on Windows. */
+# elif !defined(SECP256K1_STATIC)
+ /* Consuming libsecp256k1 as a DLL. */
+# define SECP256K1_API extern __declspec (dllimport)
+# endif
+#endif
+#ifndef SECP256K1_API
+# if defined(__GNUC__) && (__GNUC__ >= 4) && defined(SECP256K1_BUILD)
+ /* Building libsecp256k1 on non-Windows using GCC or compatible. */
+# define SECP256K1_API extern __attribute__ ((visibility ("default")))
+# else
+ /* All cases not captured above. */
+# define SECP256K1_API extern
+# endif
+#endif
+
+/* Warning attributes
+ * NONNULL is not used if SECP256K1_BUILD is set to avoid the compiler optimizing out
+ * some paranoid null checks. */
+# if defined(__GNUC__) && SECP256K1_GNUC_PREREQ(3, 4)
+# define SECP256K1_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__))
+# else
+# define SECP256K1_WARN_UNUSED_RESULT
+# endif
+# if !defined(SECP256K1_BUILD) && defined(__GNUC__) && SECP256K1_GNUC_PREREQ(3, 4)
+# define SECP256K1_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x)))
+# else
+# define SECP256K1_ARG_NONNULL(_x)
+# endif
+
+/* Attribute for marking functions, types, and variables as deprecated */
+#if !defined(SECP256K1_BUILD) && defined(__has_attribute)
+# if __has_attribute(__deprecated__)
+# define SECP256K1_DEPRECATED(_msg) __attribute__ ((__deprecated__(_msg)))
+# else
+# define SECP256K1_DEPRECATED(_msg)
+# endif
+#else
+# define SECP256K1_DEPRECATED(_msg)
+#endif
+
+/* All flags' lower 8 bits indicate what they're for. Do not use directly. */
+#define SECP256K1_FLAGS_TYPE_MASK ((1 << 8) - 1)
+#define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0)
+#define SECP256K1_FLAGS_TYPE_COMPRESSION (1 << 1)
+/* The higher bits contain the actual data. Do not use directly. */
+#define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY (1 << 8)
+#define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9)
+#define SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY (1 << 10)
+#define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8)
+
+/** Context flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
+ * secp256k1_context_preallocated_create. */
+#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT)
+
+/** Deprecated context flags. These flags are treated equivalent to SECP256K1_CONTEXT_NONE. */
+#define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
+#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
+
+/* Testing flag. Do not use. */
+#define SECP256K1_CONTEXT_DECLASSIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY)
+
+/** Flag to pass to secp256k1_ec_pubkey_serialize. */
+#define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
+#define SECP256K1_EC_UNCOMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION)
+
+/** Prefix byte used to tag various encoded curvepoints for specific purposes */
+#define SECP256K1_TAG_PUBKEY_EVEN 0x02
+#define SECP256K1_TAG_PUBKEY_ODD 0x03
+#define SECP256K1_TAG_PUBKEY_UNCOMPRESSED 0x04
+#define SECP256K1_TAG_PUBKEY_HYBRID_EVEN 0x06
+#define SECP256K1_TAG_PUBKEY_HYBRID_ODD 0x07
+
+/** A built-in constant secp256k1 context object with static storage duration, to be
+ * used in conjunction with secp256k1_selftest.
+ *
+ * This context object offers *only limited functionality* , i.e., it cannot be used
+ * for API functions that perform computations involving secret keys, e.g., signing
+ * and public key generation. If this restriction applies to a specific API function,
+ * it is mentioned in its documentation. See secp256k1_context_create if you need a
+ * full context object that supports all functionality offered by the library.
+ *
+ * It is highly recommended to call secp256k1_selftest before using this context.
+ */
+SECP256K1_API const secp256k1_context *secp256k1_context_static;
+
+/** Deprecated alias for secp256k1_context_static. */
+SECP256K1_API const secp256k1_context *secp256k1_context_no_precomp
+SECP256K1_DEPRECATED("Use secp256k1_context_static instead");
+
+/** Perform basic self tests (to be used in conjunction with secp256k1_context_static)
+ *
+ * This function performs self tests that detect some serious usage errors and
+ * similar conditions, e.g., when the library is compiled for the wrong endianness.
+ * This is a last resort measure to be used in production. The performed tests are
+ * very rudimentary and are not intended as a replacement for running the test
+ * binaries.
+ *
+ * It is highly recommended to call this before using secp256k1_context_static.
+ * It is not necessary to call this function before using a context created with
+ * secp256k1_context_create (or secp256k1_context_preallocated_create), which will
+ * take care of performing the self tests.
+ *
+ * If the tests fail, this function will call the default error handler to abort the
+ * program (see secp256k1_context_set_error_callback).
+ */
+SECP256K1_API void secp256k1_selftest(void);
+
+
+/** Create a secp256k1 context object (in dynamically allocated memory).
+ *
+ * This function uses malloc to allocate memory. It is guaranteed that malloc is
+ * called at most once for every call of this function. If you need to avoid dynamic
+ * memory allocation entirely, see secp256k1_context_static and the functions in
+ * secp256k1_preallocated.h.
+ *
+ * Returns: a newly created context object.
+ * In: flags: Always set to SECP256K1_CONTEXT_NONE (see below).
+ *
+ * The only valid non-deprecated flag in recent library versions is
+ * SECP256K1_CONTEXT_NONE, which will create a context sufficient for all functionality
+ * offered by the library. All other (deprecated) flags will be treated as equivalent
+ * to the SECP256K1_CONTEXT_NONE flag. Though the flags parameter primarily exists for
+ * historical reasons, future versions of the library may introduce new flags.
+ *
+ * If the context is intended to be used for API functions that perform computations
+ * involving secret keys, e.g., signing and public key generation, then it is highly
+ * recommended to call secp256k1_context_randomize on the context before calling
+ * those API functions. This will provide enhanced protection against side-channel
+ * leakage, see secp256k1_context_randomize for details.
+ *
+ * Do not create a new context object for each operation, as construction and
+ * randomization can take non-negligible time.
+ */
+SECP256K1_API secp256k1_context *secp256k1_context_create(
+ unsigned int flags
+) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Copy a secp256k1 context object (into dynamically allocated memory).
+ *
+ * This function uses malloc to allocate memory. It is guaranteed that malloc is
+ * called at most once for every call of this function. If you need to avoid dynamic
+ * memory allocation entirely, see the functions in secp256k1_preallocated.h.
+ *
+ * Cloning secp256k1_context_static is not possible, and should not be emulated by
+ * the caller (e.g., using memcpy). Create a new context instead.
+ *
+ * Returns: a newly created context object.
+ * Args: ctx: an existing context to copy (not secp256k1_context_static)
+ */
+SECP256K1_API secp256k1_context *secp256k1_context_clone(
+ const secp256k1_context *ctx
+) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
+
+/** Destroy a secp256k1 context object (created in dynamically allocated memory).
+ *
+ * The context pointer may not be used afterwards.
+ *
+ * The context to destroy must have been created using secp256k1_context_create
+ * or secp256k1_context_clone. If the context has instead been created using
+ * secp256k1_context_preallocated_create or secp256k1_context_preallocated_clone, the
+ * behaviour is undefined. In that case, secp256k1_context_preallocated_destroy must
+ * be used instead.
+ *
+ * Args: ctx: an existing context to destroy, constructed using
+ * secp256k1_context_create or secp256k1_context_clone
+ * (i.e., not secp256k1_context_static).
+ */
+SECP256K1_API void secp256k1_context_destroy(
+ secp256k1_context *ctx
+) SECP256K1_ARG_NONNULL(1);
+
+/** Set a callback function to be called when an illegal argument is passed to
+ * an API call. It will only trigger for violations that are mentioned
+ * explicitly in the header.
+ *
+ * The philosophy is that these shouldn't be dealt with through a
+ * specific return value, as calling code should not have branches to deal with
+ * the case that this code itself is broken.
+ *
+ * On the other hand, during debug stage, one would want to be informed about
+ * such mistakes, and the default (crashing) may be inadvisable.
+ * When this callback is triggered, the API function called is guaranteed not
+ * to cause a crash, though its return value and output arguments are
+ * undefined.
+ *
+ * When this function has not been called (or called with fn==NULL), then the
+ * default handler will be used. The library provides a default handler which
+ * writes the message to stderr and calls abort. This default handler can be
+ * replaced at link time if the preprocessor macro
+ * USE_EXTERNAL_DEFAULT_CALLBACKS is defined, which is the case if the build
+ * has been configured with --enable-external-default-callbacks. Then the
+ * following two symbols must be provided to link against:
+ * - void secp256k1_default_illegal_callback_fn(const char *message, void *data);
+ * - void secp256k1_default_error_callback_fn(const char *message, void *data);
+ * The library can call these default handlers even before a proper callback data
+ * pointer could have been set using secp256k1_context_set_illegal_callback or
+ * secp256k1_context_set_error_callback, e.g., when the creation of a context
+ * fails. In this case, the corresponding default handler will be called with
+ * the data pointer argument set to NULL.
+ *
+ * Args: ctx: an existing context object.
+ * In: fun: a pointer to a function to call when an illegal argument is
+ * passed to the API, taking a message and an opaque pointer.
+ * (NULL restores the default handler.)
+ * data: the opaque pointer to pass to fun above, must be NULL for the default handler.
+ *
+ * See also secp256k1_context_set_error_callback.
+ */
+SECP256K1_API void secp256k1_context_set_illegal_callback(
+ secp256k1_context *ctx,
+ void (*fun)(const char *message, void *data),
+ const void *data
+) SECP256K1_ARG_NONNULL(1);
+
+/** Set a callback function to be called when an internal consistency check
+ * fails.
+ *
+ * The default callback writes an error message to stderr and calls abort
+ * to abort the program.
+ *
+ * This can only trigger in case of a hardware failure, miscompilation,
+ * memory corruption, serious bug in the library, or other error would can
+ * otherwise result in undefined behaviour. It will not trigger due to mere
+ * incorrect usage of the API (see secp256k1_context_set_illegal_callback
+ * for that). After this callback returns, anything may happen, including
+ * crashing.
+ *
+ * Args: ctx: an existing context object.
+ * In: fun: a pointer to a function to call when an internal error occurs,
+ * taking a message and an opaque pointer (NULL restores the
+ * default handler, see secp256k1_context_set_illegal_callback
+ * for details).
+ * data: the opaque pointer to pass to fun above, must be NULL for the default handler.
+ *
+ * See also secp256k1_context_set_illegal_callback.
+ */
+SECP256K1_API void secp256k1_context_set_error_callback(
+ secp256k1_context *ctx,
+ void (*fun)(const char *message, void *data),
+ const void *data
+) SECP256K1_ARG_NONNULL(1);
+
+/** Create a secp256k1 scratch space object.
+ *
+ * Returns: a newly created scratch space.
+ * Args: ctx: an existing context object.
+ * In: size: amount of memory to be available as scratch space. Some extra
+ * (<100 bytes) will be allocated for extra accounting.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_scratch_space *secp256k1_scratch_space_create(
+ const secp256k1_context *ctx,
+ size_t size
+) SECP256K1_ARG_NONNULL(1);
+
+/** Destroy a secp256k1 scratch space.
+ *
+ * The pointer may not be used afterwards.
+ * Args: ctx: a secp256k1 context object.
+ * scratch: space to destroy
+ */
+SECP256K1_API void secp256k1_scratch_space_destroy(
+ const secp256k1_context *ctx,
+ secp256k1_scratch_space *scratch
+) SECP256K1_ARG_NONNULL(1);
+
+/** Parse a variable-length public key into the pubkey object.
+ *
+ * Returns: 1 if the public key was fully valid.
+ * 0 if the public key could not be parsed or is invalid.
+ * Args: ctx: a secp256k1 context object.
+ * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
+ * parsed version of input. If not, its value is undefined.
+ * In: input: pointer to a serialized public key
+ * inputlen: length of the array pointed to by input
+ *
+ * This function supports parsing compressed (33 bytes, header byte 0x02 or
+ * 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header
+ * byte 0x06 or 0x07) format public keys.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_parse(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey,
+ const unsigned char *input,
+ size_t inputlen
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Serialize a pubkey object into a serialized byte sequence.
+ *
+ * Returns: 1 always.
+ * Args: ctx: a secp256k1 context object.
+ * Out: output: a pointer to a 65-byte (if compressed==0) or 33-byte (if
+ * compressed==1) byte array to place the serialized key
+ * in.
+ * In/Out: outputlen: a pointer to an integer which is initially set to the
+ * size of output, and is overwritten with the written
+ * size.
+ * In: pubkey: a pointer to a secp256k1_pubkey containing an
+ * initialized public key.
+ * flags: SECP256K1_EC_COMPRESSED if serialization should be in
+ * compressed format, otherwise SECP256K1_EC_UNCOMPRESSED.
+ */
+SECP256K1_API int secp256k1_ec_pubkey_serialize(
+ const secp256k1_context *ctx,
+ unsigned char *output,
+ size_t *outputlen,
+ const secp256k1_pubkey *pubkey,
+ unsigned int flags
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Compare two public keys using lexicographic (of compressed serialization) order
+ *
+ * Returns: <0 if the first public key is less than the second
+ * >0 if the first public key is greater than the second
+ * 0 if the two public keys are equal
+ * Args: ctx: a secp256k1 context object.
+ * In: pubkey1: first public key to compare
+ * pubkey2: second public key to compare
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_cmp(
+ const secp256k1_context *ctx,
+ const secp256k1_pubkey *pubkey1,
+ const secp256k1_pubkey *pubkey2
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Parse an ECDSA signature in compact (64 bytes) format.
+ *
+ * Returns: 1 when the signature could be parsed, 0 otherwise.
+ * Args: ctx: a secp256k1 context object
+ * Out: sig: a pointer to a signature object
+ * In: input64: a pointer to the 64-byte array to parse
+ *
+ * The signature must consist of a 32-byte big endian R value, followed by a
+ * 32-byte big endian S value. If R or S fall outside of [0..order-1], the
+ * encoding is invalid. R and S with value 0 are allowed in the encoding.
+ *
+ * After the call, sig will always be initialized. If parsing failed or R or
+ * S are zero, the resulting sig value is guaranteed to fail verification for
+ * any message and public key.
+ */
+SECP256K1_API int secp256k1_ecdsa_signature_parse_compact(
+ const secp256k1_context *ctx,
+ secp256k1_ecdsa_signature *sig,
+ const unsigned char *input64
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Parse a DER ECDSA signature.
+ *
+ * Returns: 1 when the signature could be parsed, 0 otherwise.
+ * Args: ctx: a secp256k1 context object
+ * Out: sig: a pointer to a signature object
+ * In: input: a pointer to the signature to be parsed
+ * inputlen: the length of the array pointed to be input
+ *
+ * This function will accept any valid DER encoded signature, even if the
+ * encoded numbers are out of range.
+ *
+ * After the call, sig will always be initialized. If parsing failed or the
+ * encoded numbers are out of range, signature verification with it is
+ * guaranteed to fail for every message and public key.
+ */
+SECP256K1_API int secp256k1_ecdsa_signature_parse_der(
+ const secp256k1_context *ctx,
+ secp256k1_ecdsa_signature *sig,
+ const unsigned char *input,
+ size_t inputlen
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Serialize an ECDSA signature in DER format.
+ *
+ * Returns: 1 if enough space was available to serialize, 0 otherwise
+ * Args: ctx: a secp256k1 context object
+ * Out: output: a pointer to an array to store the DER serialization
+ * In/Out: outputlen: a pointer to a length integer. Initially, this integer
+ * should be set to the length of output. After the call
+ * it will be set to the length of the serialization (even
+ * if 0 was returned).
+ * In: sig: a pointer to an initialized signature object
+ */
+SECP256K1_API int secp256k1_ecdsa_signature_serialize_der(
+ const secp256k1_context *ctx,
+ unsigned char *output,
+ size_t *outputlen,
+ const secp256k1_ecdsa_signature *sig
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Serialize an ECDSA signature in compact (64 byte) format.
+ *
+ * Returns: 1
+ * Args: ctx: a secp256k1 context object
+ * Out: output64: a pointer to a 64-byte array to store the compact serialization
+ * In: sig: a pointer to an initialized signature object
+ *
+ * See secp256k1_ecdsa_signature_parse_compact for details about the encoding.
+ */
+SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
+ const secp256k1_context *ctx,
+ unsigned char *output64,
+ const secp256k1_ecdsa_signature *sig
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Verify an ECDSA signature.
+ *
+ * Returns: 1: correct signature
+ * 0: incorrect or unparseable signature
+ * Args: ctx: a secp256k1 context object.
+ * In: sig: the signature being verified.
+ * msghash32: the 32-byte message hash being verified.
+ * The verifier must make sure to apply a cryptographic
+ * hash function to the message by itself and not accept an
+ * msghash32 value directly. Otherwise, it would be easy to
+ * create a "valid" signature without knowledge of the
+ * secret key. See also
+ * https://bitcoin.stackexchange.com/a/81116/35586 for more
+ * background on this topic.
+ * pubkey: pointer to an initialized public key to verify with.
+ *
+ * To avoid accepting malleable signatures, only ECDSA signatures in lower-S
+ * form are accepted.
+ *
+ * If you need to accept ECDSA signatures from sources that do not obey this
+ * rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to
+ * verification, but be aware that doing so results in malleable signatures.
+ *
+ * For details, see the comments for that function.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_verify(
+ const secp256k1_context *ctx,
+ const secp256k1_ecdsa_signature *sig,
+ const unsigned char *msghash32,
+ const secp256k1_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Convert a signature to a normalized lower-S form.
+ *
+ * Returns: 1 if sigin was not normalized, 0 if it already was.
+ * Args: ctx: a secp256k1 context object
+ * Out: sigout: a pointer to a signature to fill with the normalized form,
+ * or copy if the input was already normalized. (can be NULL if
+ * you're only interested in whether the input was already
+ * normalized).
+ * In: sigin: a pointer to a signature to check/normalize (can be identical to sigout)
+ *
+ * With ECDSA a third-party can forge a second distinct signature of the same
+ * message, given a single initial signature, but without knowing the key. This
+ * is done by negating the S value modulo the order of the curve, 'flipping'
+ * the sign of the random point R which is not included in the signature.
+ *
+ * Forgery of the same message isn't universally problematic, but in systems
+ * where message malleability or uniqueness of signatures is important this can
+ * cause issues. This forgery can be blocked by all verifiers forcing signers
+ * to use a normalized form.
+ *
+ * The lower-S form reduces the size of signatures slightly on average when
+ * variable length encodings (such as DER) are used and is cheap to verify,
+ * making it a good choice. Security of always using lower-S is assured because
+ * anyone can trivially modify a signature after the fact to enforce this
+ * property anyway.
+ *
+ * The lower S value is always between 0x1 and
+ * 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
+ * inclusive.
+ *
+ * No other forms of ECDSA malleability are known and none seem likely, but
+ * there is no formal proof that ECDSA, even with this additional restriction,
+ * is free of other malleability. Commonly used serialization schemes will also
+ * accept various non-unique encodings, so care should be taken when this
+ * property is required for an application.
+ *
+ * The secp256k1_ecdsa_sign function will by default create signatures in the
+ * lower-S form, and secp256k1_ecdsa_verify will not accept others. In case
+ * signatures come from a system that cannot enforce this property,
+ * secp256k1_ecdsa_signature_normalize must be called before verification.
+ */
+SECP256K1_API int secp256k1_ecdsa_signature_normalize(
+ const secp256k1_context *ctx,
+ secp256k1_ecdsa_signature *sigout,
+ const secp256k1_ecdsa_signature *sigin
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3);
+
+/** An implementation of RFC6979 (using HMAC-SHA256) as nonce generation function.
+ * If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
+ * extra entropy.
+ */
+SECP256K1_API const secp256k1_nonce_function secp256k1_nonce_function_rfc6979;
+
+/** A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979). */
+SECP256K1_API const secp256k1_nonce_function secp256k1_nonce_function_default;
+
+/** Create an ECDSA signature.
+ *
+ * Returns: 1: signature created
+ * 0: the nonce generation function failed, or the secret key was invalid.
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * Out: sig: pointer to an array where the signature will be placed.
+ * In: msghash32: the 32-byte message hash being signed.
+ * seckey: pointer to a 32-byte secret key.
+ * noncefp: pointer to a nonce generation function. If NULL,
+ * secp256k1_nonce_function_default is used.
+ * ndata: pointer to arbitrary data used by the nonce generation function
+ * (can be NULL). If it is non-NULL and
+ * secp256k1_nonce_function_default is used, then ndata must be a
+ * pointer to 32-bytes of additional data.
+ *
+ * The created signature is always in lower-S form. See
+ * secp256k1_ecdsa_signature_normalize for more details.
+ */
+SECP256K1_API int secp256k1_ecdsa_sign(
+ const secp256k1_context *ctx,
+ secp256k1_ecdsa_signature *sig,
+ const unsigned char *msghash32,
+ const unsigned char *seckey,
+ secp256k1_nonce_function noncefp,
+ const void *ndata
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Verify an ECDSA secret key.
+ *
+ * A secret key is valid if it is not 0 and less than the secp256k1 curve order
+ * when interpreted as an integer (most significant byte first). The
+ * probability of choosing a 32-byte string uniformly at random which is an
+ * invalid secret key is negligible.
+ *
+ * Returns: 1: secret key is valid
+ * 0: secret key is invalid
+ * Args: ctx: pointer to a context object.
+ * In: seckey: pointer to a 32-byte secret key.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_verify(
+ const secp256k1_context *ctx,
+ const unsigned char *seckey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
+
+/** Compute the public key for a secret key.
+ *
+ * Returns: 1: secret was valid, public key stores.
+ * 0: secret was invalid, try again.
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * Out: pubkey: pointer to the created public key.
+ * In: seckey: pointer to a 32-byte secret key.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_create(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey,
+ const unsigned char *seckey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Negates a secret key in place.
+ *
+ * Returns: 0 if the given secret key is invalid according to
+ * secp256k1_ec_seckey_verify. 1 otherwise
+ * Args: ctx: pointer to a context object
+ * In/Out: seckey: pointer to the 32-byte secret key to be negated. If the
+ * secret key is invalid according to
+ * secp256k1_ec_seckey_verify, this function returns 0 and
+ * seckey will be set to some unspecified value.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_negate(
+ const secp256k1_context *ctx,
+ unsigned char *seckey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
+
+/** Same as secp256k1_ec_seckey_negate, but DEPRECATED. Will be removed in
+ * future versions. */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_negate(
+ const secp256k1_context *ctx,
+ unsigned char *seckey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_negate instead");
+
+/** Negates a public key in place.
+ *
+ * Returns: 1 always
+ * Args: ctx: pointer to a context object
+ * In/Out: pubkey: pointer to the public key to be negated.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_negate(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
+
+/** Tweak a secret key by adding tweak to it.
+ *
+ * Returns: 0 if the arguments are invalid or the resulting secret key would be
+ * invalid (only when the tweak is the negation of the secret key). 1
+ * otherwise.
+ * Args: ctx: pointer to a context object.
+ * In/Out: seckey: pointer to a 32-byte secret key. If the secret key is
+ * invalid according to secp256k1_ec_seckey_verify, this
+ * function returns 0. seckey will be set to some unspecified
+ * value if this function returns 0.
+ * In: tweak32: pointer to a 32-byte tweak, which must be valid according to
+ * secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly
+ * random 32-byte tweaks, the chance of being invalid is
+ * negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_tweak_add(
+ const secp256k1_context *ctx,
+ unsigned char *seckey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Same as secp256k1_ec_seckey_tweak_add, but DEPRECATED. Will be removed in
+ * future versions. */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add(
+ const secp256k1_context *ctx,
+ unsigned char *seckey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_add instead");
+
+/** Tweak a public key by adding tweak times the generator to it.
+ *
+ * Returns: 0 if the arguments are invalid or the resulting public key would be
+ * invalid (only when the tweak is the negation of the corresponding
+ * secret key). 1 otherwise.
+ * Args: ctx: pointer to a context object.
+ * In/Out: pubkey: pointer to a public key object. pubkey will be set to an
+ * invalid value if this function returns 0.
+ * In: tweak32: pointer to a 32-byte tweak, which must be valid according to
+ * secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly
+ * random 32-byte tweaks, the chance of being invalid is
+ * negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_add(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Tweak a secret key by multiplying it by a tweak.
+ *
+ * Returns: 0 if the arguments are invalid. 1 otherwise.
+ * Args: ctx: pointer to a context object.
+ * In/Out: seckey: pointer to a 32-byte secret key. If the secret key is
+ * invalid according to secp256k1_ec_seckey_verify, this
+ * function returns 0. seckey will be set to some unspecified
+ * value if this function returns 0.
+ * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according to
+ * secp256k1_ec_seckey_verify, this function returns 0. For
+ * uniformly random 32-byte arrays the chance of being invalid
+ * is negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_tweak_mul(
+ const secp256k1_context *ctx,
+ unsigned char *seckey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Same as secp256k1_ec_seckey_tweak_mul, but DEPRECATED. Will be removed in
+ * future versions. */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul(
+ const secp256k1_context *ctx,
+ unsigned char *seckey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
+ SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_mul instead");
+
+/** Tweak a public key by multiplying it by a tweak value.
+ *
+ * Returns: 0 if the arguments are invalid. 1 otherwise.
+ * Args: ctx: pointer to a context object.
+ * In/Out: pubkey: pointer to a public key object. pubkey will be set to an
+ * invalid value if this function returns 0.
+ * In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid according to
+ * secp256k1_ec_seckey_verify, this function returns 0. For
+ * uniformly random 32-byte arrays the chance of being invalid
+ * is negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_mul(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Randomizes the context to provide enhanced protection against side-channel leakage.
+ *
+ * Returns: 1: randomization successful
+ * 0: error
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * In: seed32: pointer to a 32-byte random seed (NULL resets to initial state).
+ *
+ * While secp256k1 code is written and tested to be constant-time no matter what
+ * secret values are, it is possible that a compiler may output code which is not,
+ * and also that the CPU may not emit the same radio frequencies or draw the same
+ * amount of power for all values. Randomization of the context shields against
+ * side-channel observations which aim to exploit secret-dependent behaviour in
+ * certain computations which involve secret keys.
+ *
+ * It is highly recommended to call this function on contexts returned from
+ * secp256k1_context_create or secp256k1_context_clone (or from the corresponding
+ * functions in secp256k1_preallocated.h) before using these contexts to call API
+ * functions that perform computations involving secret keys, e.g., signing and
+ * public key generation. It is possible to call this function more than once on
+ * the same context, and doing so before every few computations involving secret
+ * keys is recommended as a defense-in-depth measure. Randomization of the static
+ * context secp256k1_context_static is not supported.
+ *
+ * Currently, the random seed is mainly used for blinding multiplications of a
+ * secret scalar with the elliptic curve base point. Multiplications of this
+ * kind are performed by exactly those API functions which are documented to
+ * require a context that is not secp256k1_context_static. As a rule of thumb,
+ * these are all functions which take a secret key (or a keypair) as an input.
+ * A notable exception to that rule is the ECDH module, which relies on a different
+ * kind of elliptic curve point multiplication and thus does not benefit from
+ * enhanced protection against side-channel leakage currently.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize(
+ secp256k1_context *ctx,
+ const unsigned char *seed32
+) SECP256K1_ARG_NONNULL(1);
+
+/** Add a number of public keys together.
+ *
+ * Returns: 1: the sum of the public keys is valid.
+ * 0: the sum of the public keys is not valid.
+ * Args: ctx: pointer to a context object.
+ * Out: out: pointer to a public key object for placing the resulting public key.
+ * In: ins: pointer to array of pointers to public keys.
+ * n: the number of public keys to add together (must be at least 1).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *out,
+ const secp256k1_pubkey * const *ins,
+ size_t n
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Compute a tagged hash as defined in BIP-340.
+ *
+ * This is useful for creating a message hash and achieving domain separation
+ * through an application-specific tag. This function returns
+ * SHA256(SHA256(tag)||SHA256(tag)||msg). Therefore, tagged hash
+ * implementations optimized for a specific tag can precompute the SHA256 state
+ * after hashing the tag hashes.
+ *
+ * Returns: 1 always.
+ * Args: ctx: pointer to a context object
+ * Out: hash32: pointer to a 32-byte array to store the resulting hash
+ * In: tag: pointer to an array containing the tag
+ * taglen: length of the tag array
+ * msg: pointer to an array containing the message
+ * msglen: length of the message array
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_tagged_sha256(
+ const secp256k1_context *ctx,
+ unsigned char *hash32,
+ const unsigned char *tag,
+ size_t taglen,
+ const unsigned char *msg,
+ size_t msglen
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_H */
diff --git a/nostrdb/secp256k1_ecdh.h b/nostrdb/secp256k1_ecdh.h
@@ -0,0 +1,63 @@
+#ifndef SECP256K1_ECDH_H
+#define SECP256K1_ECDH_H
+
+#include "secp256k1.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** A pointer to a function that hashes an EC point to obtain an ECDH secret
+ *
+ * Returns: 1 if the point was successfully hashed.
+ * 0 will cause secp256k1_ecdh to fail and return 0.
+ * Other return values are not allowed, and the behaviour of
+ * secp256k1_ecdh is undefined for other return values.
+ * Out: output: pointer to an array to be filled by the function
+ * In: x32: pointer to a 32-byte x coordinate
+ * y32: pointer to a 32-byte y coordinate
+ * data: arbitrary data pointer that is passed through
+ */
+typedef int (*secp256k1_ecdh_hash_function)(
+ unsigned char *output,
+ const unsigned char *x32,
+ const unsigned char *y32,
+ void *data
+);
+
+/** An implementation of SHA256 hash function that applies to compressed public key.
+ * Populates the output parameter with 32 bytes. */
+SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256;
+
+/** A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256).
+ * Populates the output parameter with 32 bytes. */
+SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default;
+
+/** Compute an EC Diffie-Hellman secret in constant time
+ *
+ * Returns: 1: exponentiation was successful
+ * 0: scalar was invalid (zero or overflow) or hashfp returned 0
+ * Args: ctx: pointer to a context object.
+ * Out: output: pointer to an array to be filled by hashfp.
+ * In: pubkey: a pointer to a secp256k1_pubkey containing an initialized public key.
+ * seckey: a 32-byte scalar with which to multiply the point.
+ * hashfp: pointer to a hash function. If NULL,
+ * secp256k1_ecdh_hash_function_sha256 is used
+ * (in which case, 32 bytes will be written to output).
+ * data: arbitrary data pointer that is passed through to hashfp
+ * (can be NULL for secp256k1_ecdh_hash_function_sha256).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh(
+ const secp256k1_context *ctx,
+ unsigned char *output,
+ const secp256k1_pubkey *pubkey,
+ const unsigned char *seckey,
+ secp256k1_ecdh_hash_function hashfp,
+ void *data
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_ECDH_H */
diff --git a/nostrdb/secp256k1_extrakeys.h b/nostrdb/secp256k1_extrakeys.h
@@ -0,0 +1,247 @@
+#ifndef SECP256K1_EXTRAKEYS_H
+#define SECP256K1_EXTRAKEYS_H
+
+#include "secp256k1.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Opaque data structure that holds a parsed and valid "x-only" public key.
+ * An x-only pubkey encodes a point whose Y coordinate is even. It is
+ * serialized using only its X coordinate (32 bytes). See BIP-340 for more
+ * information about x-only pubkeys.
+ *
+ * The exact representation of data inside is implementation defined and not
+ * guaranteed to be portable between different platforms or versions. It is
+ * however guaranteed to be 64 bytes in size, and can be safely copied/moved.
+ * If you need to convert to a format suitable for storage, transmission, use
+ * use secp256k1_xonly_pubkey_serialize and secp256k1_xonly_pubkey_parse. To
+ * compare keys, use secp256k1_xonly_pubkey_cmp.
+ */
+typedef struct {
+ unsigned char data[64];
+} secp256k1_xonly_pubkey;
+
+/** Opaque data structure that holds a keypair consisting of a secret and a
+ * public key.
+ *
+ * The exact representation of data inside is implementation defined and not
+ * guaranteed to be portable between different platforms or versions. It is
+ * however guaranteed to be 96 bytes in size, and can be safely copied/moved.
+ */
+typedef struct {
+ unsigned char data[96];
+} secp256k1_keypair;
+
+/** Parse a 32-byte sequence into a xonly_pubkey object.
+ *
+ * Returns: 1 if the public key was fully valid.
+ * 0 if the public key could not be parsed or is invalid.
+ *
+ * Args: ctx: a secp256k1 context object.
+ * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
+ * parsed version of input. If not, it's set to an invalid value.
+ * In: input32: pointer to a serialized xonly_pubkey.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse(
+ const secp256k1_context *ctx,
+ secp256k1_xonly_pubkey *pubkey,
+ const unsigned char *input32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Serialize an xonly_pubkey object into a 32-byte sequence.
+ *
+ * Returns: 1 always.
+ *
+ * Args: ctx: a secp256k1 context object.
+ * Out: output32: a pointer to a 32-byte array to place the serialized key in.
+ * In: pubkey: a pointer to a secp256k1_xonly_pubkey containing an initialized public key.
+ */
+SECP256K1_API int secp256k1_xonly_pubkey_serialize(
+ const secp256k1_context *ctx,
+ unsigned char *output32,
+ const secp256k1_xonly_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Compare two x-only public keys using lexicographic order
+ *
+ * Returns: <0 if the first public key is less than the second
+ * >0 if the first public key is greater than the second
+ * 0 if the two public keys are equal
+ * Args: ctx: a secp256k1 context object.
+ * In: pubkey1: first public key to compare
+ * pubkey2: second public key to compare
+ */
+SECP256K1_API int secp256k1_xonly_pubkey_cmp(
+ const secp256k1_context *ctx,
+ const secp256k1_xonly_pubkey *pk1,
+ const secp256k1_xonly_pubkey *pk2
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
+ *
+ * Returns: 1 always.
+ *
+ * Args: ctx: pointer to a context object.
+ * Out: xonly_pubkey: pointer to an x-only public key object for placing the converted public key.
+ * pk_parity: Ignored if NULL. Otherwise, pointer to an integer that
+ * will be set to 1 if the point encoded by xonly_pubkey is
+ * the negation of the pubkey and set to 0 otherwise.
+ * In: pubkey: pointer to a public key that is converted.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubkey(
+ const secp256k1_context *ctx,
+ secp256k1_xonly_pubkey *xonly_pubkey,
+ int *pk_parity,
+ const secp256k1_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
+
+/** Tweak an x-only public key by adding the generator multiplied with tweak32
+ * to it.
+ *
+ * Note that the resulting point can not in general be represented by an x-only
+ * pubkey because it may have an odd Y coordinate. Instead, the output_pubkey
+ * is a normal secp256k1_pubkey.
+ *
+ * Returns: 0 if the arguments are invalid or the resulting public key would be
+ * invalid (only when the tweak is the negation of the corresponding
+ * secret key). 1 otherwise.
+ *
+ * Args: ctx: pointer to a context object.
+ * Out: output_pubkey: pointer to a public key to store the result. Will be set
+ * to an invalid value if this function returns 0.
+ * In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to.
+ * tweak32: pointer to a 32-byte tweak, which must be valid
+ * according to secp256k1_ec_seckey_verify or 32 zero
+ * bytes. For uniformly random 32-byte tweaks, the chance of
+ * being invalid is negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *output_pubkey,
+ const secp256k1_xonly_pubkey *internal_pubkey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Checks that a tweaked pubkey is the result of calling
+ * secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.
+ *
+ * The tweaked pubkey is represented by its 32-byte x-only serialization and
+ * its pk_parity, which can both be obtained by converting the result of
+ * tweak_add to a secp256k1_xonly_pubkey.
+ *
+ * Note that this alone does _not_ verify that the tweaked pubkey is a
+ * commitment. If the tweak is not chosen in a specific way, the tweaked pubkey
+ * can easily be the result of a different internal_pubkey and tweak.
+ *
+ * Returns: 0 if the arguments are invalid or the tweaked pubkey is not the
+ * result of tweaking the internal_pubkey with tweak32. 1 otherwise.
+ * Args: ctx: pointer to a context object.
+ * In: tweaked_pubkey32: pointer to a serialized xonly_pubkey.
+ * tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization
+ * is passed in as tweaked_pubkey32). This must match the
+ * pk_parity value that is returned when calling
+ * secp256k1_xonly_pubkey with the tweaked pubkey, or
+ * this function will fail.
+ * internal_pubkey: pointer to an x-only public key object to apply the tweak to.
+ * tweak32: pointer to a 32-byte tweak.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_check(
+ const secp256k1_context *ctx,
+ const unsigned char *tweaked_pubkey32,
+ int tweaked_pk_parity,
+ const secp256k1_xonly_pubkey *internal_pubkey,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
+
+/** Compute the keypair for a secret key.
+ *
+ * Returns: 1: secret was valid, keypair is ready to use
+ * 0: secret was invalid, try again with a different secret
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * Out: keypair: pointer to the created keypair.
+ * In: seckey: pointer to a 32-byte secret key.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_create(
+ const secp256k1_context *ctx,
+ secp256k1_keypair *keypair,
+ const unsigned char *seckey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Get the secret key from a keypair.
+ *
+ * Returns: 1 always.
+ * Args: ctx: pointer to a context object.
+ * Out: seckey: pointer to a 32-byte buffer for the secret key.
+ * In: keypair: pointer to a keypair.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_sec(
+ const secp256k1_context *ctx,
+ unsigned char *seckey,
+ const secp256k1_keypair *keypair
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Get the public key from a keypair.
+ *
+ * Returns: 1 always.
+ * Args: ctx: pointer to a context object.
+ * Out: pubkey: pointer to a pubkey object, set to the keypair public key.
+ * In: keypair: pointer to a keypair.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_pub(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubkey,
+ const secp256k1_keypair *keypair
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Get the x-only public key from a keypair.
+ *
+ * This is the same as calling secp256k1_keypair_pub and then
+ * secp256k1_xonly_pubkey_from_pubkey.
+ *
+ * Returns: 1 always.
+ * Args: ctx: pointer to a context object.
+ * Out: pubkey: pointer to an xonly_pubkey object, set to the keypair
+ * public key after converting it to an xonly_pubkey.
+ * pk_parity: Ignored if NULL. Otherwise, pointer to an integer that will be set to the
+ * pk_parity argument of secp256k1_xonly_pubkey_from_pubkey.
+ * In: keypair: pointer to a keypair.
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_pub(
+ const secp256k1_context *ctx,
+ secp256k1_xonly_pubkey *pubkey,
+ int *pk_parity,
+ const secp256k1_keypair *keypair
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
+
+/** Tweak a keypair by adding tweak32 to the secret key and updating the public
+ * key accordingly.
+ *
+ * Calling this function and then secp256k1_keypair_pub results in the same
+ * public key as calling secp256k1_keypair_xonly_pub and then
+ * secp256k1_xonly_pubkey_tweak_add.
+ *
+ * Returns: 0 if the arguments are invalid or the resulting keypair would be
+ * invalid (only when the tweak is the negation of the keypair's
+ * secret key). 1 otherwise.
+ *
+ * Args: ctx: pointer to a context object.
+ * In/Out: keypair: pointer to a keypair to apply the tweak to. Will be set to
+ * an invalid value if this function returns 0.
+ * In: tweak32: pointer to a 32-byte tweak, which must be valid according to
+ * secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly
+ * random 32-byte tweaks, the chance of being invalid is
+ * negligible (around 1 in 2^128).
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_tweak_add(
+ const secp256k1_context *ctx,
+ secp256k1_keypair *keypair,
+ const unsigned char *tweak32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_EXTRAKEYS_H */
diff --git a/nostrdb/secp256k1_schnorrsig.h b/nostrdb/secp256k1_schnorrsig.h
@@ -0,0 +1,190 @@
+#ifndef SECP256K1_SCHNORRSIG_H
+#define SECP256K1_SCHNORRSIG_H
+
+#include "secp256k1.h"
+#include "secp256k1_extrakeys.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** This module implements a variant of Schnorr signatures compliant with
+ * Bitcoin Improvement Proposal 340 "Schnorr Signatures for secp256k1"
+ * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
+ */
+
+/** A pointer to a function to deterministically generate a nonce.
+ *
+ * Same as secp256k1_nonce function with the exception of accepting an
+ * additional pubkey argument and not requiring an attempt argument. The pubkey
+ * argument can protect signature schemes with key-prefixed challenge hash
+ * inputs against reusing the nonce when signing with the wrong precomputed
+ * pubkey.
+ *
+ * Returns: 1 if a nonce was successfully generated. 0 will cause signing to
+ * return an error.
+ * Out: nonce32: pointer to a 32-byte array to be filled by the function
+ * In: msg: the message being verified. Is NULL if and only if msglen
+ * is 0.
+ * msglen: the length of the message
+ * key32: pointer to a 32-byte secret key (will not be NULL)
+ * xonly_pk32: the 32-byte serialized xonly pubkey corresponding to key32
+ * (will not be NULL)
+ * algo: pointer to an array describing the signature
+ * algorithm (will not be NULL)
+ * algolen: the length of the algo array
+ * data: arbitrary data pointer that is passed through
+ *
+ * Except for test cases, this function should compute some cryptographic hash of
+ * the message, the key, the pubkey, the algorithm description, and data.
+ */
+typedef int (*secp256k1_nonce_function_hardened)(
+ unsigned char *nonce32,
+ const unsigned char *msg,
+ size_t msglen,
+ const unsigned char *key32,
+ const unsigned char *xonly_pk32,
+ const unsigned char *algo,
+ size_t algolen,
+ void *data
+);
+
+/** An implementation of the nonce generation function as defined in Bitcoin
+ * Improvement Proposal 340 "Schnorr Signatures for secp256k1"
+ * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
+ *
+ * If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
+ * auxiliary random data as defined in BIP-340. If the data pointer is NULL,
+ * the nonce derivation procedure follows BIP-340 by setting the auxiliary
+ * random data to zero. The algo argument must be non-NULL, otherwise the
+ * function will fail and return 0. The hash will be tagged with algo.
+ * Therefore, to create BIP-340 compliant signatures, algo must be set to
+ * "BIP0340/nonce" and algolen to 13.
+ */
+SECP256K1_API const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340;
+
+/** Data structure that contains additional arguments for schnorrsig_sign_custom.
+ *
+ * A schnorrsig_extraparams structure object can be initialized correctly by
+ * setting it to SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT.
+ *
+ * Members:
+ * magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization
+ * and has no other function than making sure the object is
+ * initialized.
+ * noncefp: pointer to a nonce generation function. If NULL,
+ * secp256k1_nonce_function_bip340 is used
+ * ndata: pointer to arbitrary data used by the nonce generation function
+ * (can be NULL). If it is non-NULL and
+ * secp256k1_nonce_function_bip340 is used, then ndata must be a
+ * pointer to 32-byte auxiliary randomness as per BIP-340.
+ */
+typedef struct {
+ unsigned char magic[4];
+ secp256k1_nonce_function_hardened noncefp;
+ void *ndata;
+} secp256k1_schnorrsig_extraparams;
+
+#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC { 0xda, 0x6f, 0xb3, 0x8c }
+#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT {\
+ SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC,\
+ NULL,\
+ NULL\
+}
+
+/** Create a Schnorr signature.
+ *
+ * Does _not_ strictly follow BIP-340 because it does not verify the resulting
+ * signature. Instead, you can manually use secp256k1_schnorrsig_verify and
+ * abort if it fails.
+ *
+ * This function only signs 32-byte messages. If you have messages of a
+ * different size (or the same size but without a context-specific tag
+ * prefix), it is recommended to create a 32-byte message hash with
+ * secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows
+ * providing an context-specific tag for domain separation. This prevents
+ * signatures from being valid in multiple contexts by accident.
+ *
+ * Returns 1 on success, 0 on failure.
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * Out: sig64: pointer to a 64-byte array to store the serialized signature.
+ * In: msg32: the 32-byte message being signed.
+ * keypair: pointer to an initialized keypair.
+ * aux_rand32: 32 bytes of fresh randomness. While recommended to provide
+ * this, it is only supplemental to security and can be NULL. A
+ * NULL argument is treated the same as an all-zero one. See
+ * BIP-340 "Default Signing" for a full explanation of this
+ * argument and for guidance if randomness is expensive.
+ */
+SECP256K1_API int secp256k1_schnorrsig_sign32(
+ const secp256k1_context *ctx,
+ unsigned char *sig64,
+ const unsigned char *msg32,
+ const secp256k1_keypair *keypair,
+ const unsigned char *aux_rand32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
+
+/** Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in
+ * future versions. */
+SECP256K1_API int secp256k1_schnorrsig_sign(
+ const secp256k1_context *ctx,
+ unsigned char *sig64,
+ const unsigned char *msg32,
+ const secp256k1_keypair *keypair,
+ const unsigned char *aux_rand32
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
+ SECP256K1_DEPRECATED("Use secp256k1_schnorrsig_sign32 instead");
+
+/** Create a Schnorr signature with a more flexible API.
+ *
+ * Same arguments as secp256k1_schnorrsig_sign except that it allows signing
+ * variable length messages and accepts a pointer to an extraparams object that
+ * allows customizing signing by passing additional arguments.
+ *
+ * Equivalent to secp256k1_schnorrsig_sign32(..., aux_rand32) if msglen is 32
+ * and extraparams is initialized as follows:
+ * ```
+ * secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT;
+ * extraparams.ndata = (unsigned char*)aux_rand32;
+ * ```
+ *
+ * Returns 1 on success, 0 on failure.
+ * Args: ctx: pointer to a context object (not secp256k1_context_static).
+ * Out: sig64: pointer to a 64-byte array to store the serialized signature.
+ * In: msg: the message being signed. Can only be NULL if msglen is 0.
+ * msglen: length of the message.
+ * keypair: pointer to an initialized keypair.
+ * extraparams: pointer to an extraparams object (can be NULL).
+ */
+SECP256K1_API int secp256k1_schnorrsig_sign_custom(
+ const secp256k1_context *ctx,
+ unsigned char *sig64,
+ const unsigned char *msg,
+ size_t msglen,
+ const secp256k1_keypair *keypair,
+ secp256k1_schnorrsig_extraparams *extraparams
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
+
+/** Verify a Schnorr signature.
+ *
+ * Returns: 1: correct signature
+ * 0: incorrect signature
+ * Args: ctx: a secp256k1 context object.
+ * In: sig64: pointer to the 64-byte signature to verify.
+ * msg: the message being verified. Can only be NULL if msglen is 0.
+ * msglen: length of the message
+ * pubkey: pointer to an x-only public key to verify with (cannot be NULL)
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
+ const secp256k1_context *ctx,
+ const unsigned char *sig64,
+ const unsigned char *msg,
+ size_t msglen,
+ const secp256k1_xonly_pubkey *pubkey
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SECP256K1_SCHNORRSIG_H */