基于Stellar公鏈iOSDApp

stellar.png

前段時間,項目需求基于Stellar公鏈發(fā)行的衍生鏈,需求iOS安卓端開發(fā)DAPP錢包配合公鏈run,項目基本屬于破冰,國內(nèi)完全沒有任何資料去參考,一路走來都是新技術(shù)點,全憑google和自己慢慢填坑,完全從0開始,現(xiàn)在項目基本成熟已經(jīng)進入測試階段,所以有空余時間寫篇文章為其他需要玩恒星公鏈的攻城獅提點建議,講講坑.

Stellar API 傳送門: https://www.stellar.org/developers/reference/
Stellar Swift Sdk 傳送門: https://github.com/Soneso/stellar-ios-mac-sdk

首先,項目采用oc,swift混編,嵌入了部分C語言和C++,
整體布局由于去中心化APP的特異性,基本都是采用回調(diào)方式創(chuàng)建事件肾砂、構(gòu)造Operation和Trancations,
采用的隨機方式從.English單詞表生成的12位助記詞,通過bip39共識算法生成隨機公私鑰,

1 -- 創(chuàng)建賬戶

根據(jù)單詞表隨機12位 Mnemonic助記詞
// MARK: - 初始化sdk
 let sdk = StellarSDK.init(withHorizonUrl: "*****") //由于工作原因這里的Horizon暫時不能公開,為公鏈的Horizon地址
 let mnemonic = Wallet.generate12WordMnemonic()
BIP39

與處理錢包seed的原始二進制或十六進制表示形式相比,助記碼或句子更適合于人類交互.這個句子可以寫在紙上,也可以通過電話告訴對方.

(1)首先,生成ENT比特的初始熵entropy(如下面的例子00000000000000000000000000000000,16進制,熵長度為32*4=128).
(2)通過對初始熵entropy取SHA256散列來獲得CS位(CS= 熵長度/32=4,取得到的SHA256散列的前CS位)校驗和,然后將校驗和附加到初始熵的末尾.
(3)接下來,(熵entropy+校驗和)被分成以11位為一組(一共MS組),每個組編碼對應(yīng)一個0-2047的數(shù)字,該數(shù)字作為一個索引到wordlist,對應(yīng)獲得wordlist上相應(yīng)索引的值.
(4)最后,我們將這些數(shù)字轉(zhuǎn)換成單詞,最終合在一起作為助記句.

助記詞必須以32位的倍數(shù)選擇熵值entropy.隨著熵值的增加,句子長度增加,安全性提高.我們將初始熵長度稱為ENT,ENT的允許大小是128-256位,目前我采用的是bip39的256位算法.

為了從助記符創(chuàng)建二進制種子,我們使用PBKDF2函數(shù)(密鑰拉伸(Key stretching)函數(shù)),使用助記詞(UTF-8 NFKD)作為密碼,使用字符串"助記詞"+密碼(UTF-8 NFKD)作為salt.迭代計數(shù)設(shè)置為2048(即重復(fù)運算2048次).使用hma - sha512作為偽隨機函數(shù).派生鍵的長度是512位(= 64字節(jié),即最后的seed的長度).

因為這里考慮到以后錢包要和ETH,BTC等錢包攀上關(guān)系,所以從開始就已經(jīng)著手準備HD協(xié)議:
這個seed之后將被bip32或相似的方法使用來生成hd wallet,將助記句轉(zhuǎn)換為二進制種子句與生成句子完全無關(guān).這導(dǎo)致了相當簡單的代碼;句子結(jié)構(gòu)沒有限制,客戶機可以自由地實現(xiàn)自己的單詞列表,甚至可以實現(xiàn)整個句子生成器,這允許在單詞列表中靈活地進行類型檢測或其他目的.
雖然使用不是由“生成助記符”部分中描述的算法生成的助記符是可能的,但不建議這樣做宏悦,軟件必須使用wordlist計算助記符句子的校驗和镐确,并在其無效時發(fā)出警告.所描述的方法還提供了可信的可否認性,因為每個密碼都生成一個有效的種子(從而產(chǎn)生一個hd wallet),但是只有正確的一個才能使所需的錢包可用.

// MARK: - 根據(jù)12詞助記詞,導(dǎo)入賬戶
let bip39SeedData = Mnemonic.createSeed(mnemonic: mnemonic)
let masterPrivateKey = Ed25519Derivation(seed: bip39SeedData)
let purpose = masterPrivateKey.derived(at: 44) //purpose,coinType,account為3次算法外位偏移量
let coinType = purpose.derived(at: 358)
let account = coinType.derived(at: 0)
let keyPair = try! KeyPair.init(seed: Seed(bytes: account.raw.bytes))
print("key pair - accountId: \(keyPair.accountId)")
print("key pair - secretSeed: \(keyPair.secretSeed!)")

2 -- 查詢賬戶

這里不做過多的描述,因為準備大篇幅的內(nèi)容留在之后Trancation和XDR信封簽名的過程,所以直接展示封裝核心代碼

// MARK: - 查詢賬戶
sdk.accounts.getAccountDetails(accountId: keyPair.accountId) { (response) -> (Void) in
    switch response {
    case .success(let accountDetails):

        for balance in accountDetails.balances {
            switch balance.assetType {
            case AssetTypeAsString.NATIVE:
                print("balance: \(balance.balance) XLM") //native幣余額
            default:
                print("balance: \(balance.balance) \(balance.assetCode!) issuer: \(balance.assetIssuer!)") //其他衍生發(fā)行幣
            }
        }
        for signer in accountDetails.signers {
            print("signer public key: \(signer.publicKey)")
        }

        print("sequence number: \(accountDetails.sequenceNumber)")
        print("auth required: \(accountDetails.flags.authRequired)")
        print("auth revocable: \(accountDetails.flags.authRevocable)")

        for (key, value) in accountDetails.data {
            print("data key: \(key) value: \(value.base64Decoded() ?? "")")
        }
    case .failure(let error):
        print(error.localizedDescription)
    }
}

3 -- 轉(zhuǎn)賬操作

這里就要詳細講一下Operations for Transaction,因為坑是真的很多,而且國內(nèi)也沒有像樣子的詳細介紹說明,由于去中心化的關(guān)系,基本一些邏輯上的操作全部要最小公鏈節(jié)點(DApp)來操作,這就造成了基本一個trancation當中必然要包含多個動作.
我們就以轉(zhuǎn)賬這個操作來說,需要有不低于3個步驟:

(1)確認sourceAccount源賬戶中,余額是否充足,拿到sourceAccountKeyPair用以在接下來創(chuàng)建paymentOperation,以確保我們有當前序列號,
(2)查詢destinationAccount目標賬戶是否激活開戶(因為由于節(jié)點數(shù)據(jù)庫的特異性,不可能鏈上全部賬戶全部存入Horizon數(shù)據(jù)庫),未開戶激活的賬戶,公鏈只默認存在于最小非共識節(jié)點(DApp終端),
(3)通過轉(zhuǎn)賬幣種那種當前幣的Asset,一般本幣為native,其他衍生發(fā)行幣是ASSET_TYPE_CREDIT_ALPHANUM4以下發(fā)行的

通過ALPHANUM4衍生發(fā)行幣拿到Asset對象的簡單過程:
    // MARK: 通過coin名字拿到 asset
    func getCoinNameAsset(coinName:String) -> Asset {
        var coinTypeAsset = Asset(type: AssetType.ASSET_TYPE_NATIVE)
        if coinName == "coin1" {
            do {
                let timeIssuerKeyPair = try KeyPair(accountId: "幣1的發(fā)行人Issue公鑰地址")
                coinTypeAsset = Asset(type: AssetType.ASSET_TYPE_CREDIT_ALPHANUM4, code: coinName, issuer: timeIssuerKeyPair)
            }
            catch {
                // 錯誤
            }
        }
        else if coinName == "coin2" {
            do {
                let hourIssuerKeyPair = try KeyPair(accountId: "幣2的發(fā)行人Issue公鑰地址")
                coinTypeAsset = Asset(type: AssetType.ASSET_TYPE_CREDIT_ALPHANUM4, code: coinName, issuer: hourIssuerKeyPair)
            }
            catch {
                // 錯誤
            }
        }
        return coinTypeAsset!
    }
轉(zhuǎn)賬Operation:
    // MARK: - 轉(zhuǎn)賬
    @objc func transactions(mySecretSeed: String, toAccountId: String, coinAmount: NSInteger, memoText: String, coinName: String) -> Void {
        /* 源帳戶,自己的帳戶 */
        let sourceAccountKeyPair = try! KeyPair(secretSeed:mySecretSeed)
        do {
            /* 目標帳戶 */
            let destinationAccountKeyPair = try KeyPair(accountId: toAccountId)
            /* 獲取帳戶數(shù)據(jù),以確保我們有當前序列號 */
            sdk.accounts.getAccountDetails(accountId: sourceAccountKeyPair.accountId) { (response) -> (Void) in
                switch response {
                case .success(let accountResponse):
                    do {
                        /* 建立支付操作 */
                        let paymentOperation = PaymentOperation(destination: destinationAccountKeyPair,
                                                                asset: self.getCoinNameAsset(coinName: coinName),
                                                                amount: Decimal(coinAmount))
                        /* 構(gòu)建包含我們支付操作的事務(wù)(transaction) */
                        let transaction = try Transaction(sourceAccount: accountResponse,
                                                          operations: [paymentOperation],
                                                          memo: Memo.text(memoText),
                                                          timeBounds:nil)
                        /* 用秘鑰給transaction簽名 */
                        try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.public)
                        /* 提交transaction */
                        try self.sdk.transactions.submitTransaction(transaction: transaction) { (response) -> (Void) in
                            switch response {
                            case .success(_):
                                //success
                            case .failure(let error):
                                StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-交易", horizonRequestError:error)
                            }
                        }
                    } catch {
                        //交易過程中,數(shù)據(jù)錯誤
                    }
                case .failure(let error):
                    StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-查詢", horizonRequestError:error)
                }
            }
        }
        catch  {
            if (self.stellarErrorBlock != nil) {
                self.stellarErrorBlock!("格式錯誤")
            }
        }
    }

以上轉(zhuǎn)賬Operation有幾個小細節(jié)處,Network.public為當前公鏈horizon地址的publicNet,如果這里還是使用原始SDK中的測試net,會根本run不通公鏈horizon:

/* 用秘鑰給transaction簽名 */
 try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.public)

---------------------------------------------------------------------------------------
//  Network.swift
//  stellarsdk
public enum Network: String {
    case `public` = "your public network"
    case testnet = "Test SDF Network ; September 2015"
    var networkId: Data {
        get {
            return self.rawValue.sha256Hash
        }
    }
}

4 -- 新賬戶激活(createOperation)

這里的createOperation有一些簡單說明,激活才做需要sourceAccount為當前已經(jīng)激活的account,而且必須最少有公鏈設(shè)置的賬戶創(chuàng)建最低流明作為初始幣持有(如果低于初始幣持有,會扣除手續(xù)費,并且賬戶激活失敗),很坑...

// MARK: - 給新賬戶,激活賬戶(不低于100流明)
    @objc func createActiviteAccount(mySecretSeed: String, toAccountId: String, coinAmount: NSInteger, memoText: String) -> Void {
        /* 源帳戶,自己的帳戶 */
        let sourceAccountKeyPair = try! KeyPair(secretSeed:mySecretSeed)
        do {
            /* 目標帳戶 */
            let destinationAccountKeyPair = try KeyPair(accountId: toAccountId)
            /* 獲取帳戶數(shù)據(jù),以確保我們有當前序列號 */
            sdk.accounts.getAccountDetails(accountId: sourceAccountKeyPair.accountId) { (response) -> (Void) in
                switch response {
                case .success(let accountResponse):
                    do {
                        /* 建立激活操作 */
                        let createOpention = CreateAccountOperation(destination: destinationAccountKeyPair, startBalance: Decimal(coinAmount))//不低于100流明
                        /* 構(gòu)建包含我們支付操作的事務(wù)(transaction) */
                        let transaction = try Transaction(sourceAccount: accountResponse,
                                                          operations: [createOpention],
                                                          memo: Memo.text(memoText),
                                                          timeBounds:nil)
                        /* 用秘鑰給transaction簽名 */
                        try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.public)
                        /* 提交transaction */
                        try self.sdk.transactions.submitTransaction(transaction: transaction) { (response) -> (Void) in
                            switch response {
                            case .success(_):
                                //success
                            case .failure(let error):
                                StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-交易", horizonRequestError:error)
                            }
                        }
                    } catch {
                        //交易過程中,數(shù)據(jù)錯誤
                    }
                case .failure(let error):
                    StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-查詢", horizonRequestError:error)
                }
            }
        }
        catch  {
            //格式錯誤
        }
    }

5 -- 建立信任線

建立信任線的操作,基本跟轉(zhuǎn)賬Operation中差距不大,只是在打包XDR信封的時候,需要裝入信封的Operation轉(zhuǎn)變?yōu)閏hangeTrustOperation,其余包括Asset對象創(chuàng)建都是同理.
當中有一點需要注意

changeTrustOperation創(chuàng)建的時候要確認,當前幣中在token地址上是否有余額,如果有余額會報錯horizon信任線失敗,只有在全部轉(zhuǎn)出余額為0的時候才能轉(zhuǎn)變信任線為NO,并且當你想到轉(zhuǎn)換信任線為YES的時候,需要資產(chǎn)發(fā)行人Issee,并且需要一個已經(jīng)持有該幣種的最小子節(jié)點(DApp終端)給與你最低流明,并開啟信任操作.

    // MARK: -  "1"->建立信任, "0"->取消信任
    @objc func changeTrustTimeHour(mySecretSeed:String, coinName: String, trust:String) -> Void {
        /* 源帳戶,自己的帳戶 */
        let sourceAccountKeyPair = try! KeyPair(secretSeed:mySecretSeed)
        /* 獲取帳戶數(shù)據(jù),以確保我們有當前序列號 */
        sdk.accounts.getAccountDetails(accountId: sourceAccountKeyPair.accountId) { (response) -> (Void) in
            switch response {
            case .success(let accountResponse):
                do {
                    //Decimal()->建立信任, Decimal(0)->取消信任
                    let changeTrustTimeHourOperation = ChangeTrustOperation(asset: self.getCoinNameAsset(coinName: coinName), limit: (trust == "0" ? 0 : 100000000))
                    /* 構(gòu)建包含我們支付操作的事務(wù)(transaction) */
                    let transaction = try Transaction(sourceAccount: accountResponse,
                                                      operations: [changeTrustTimeHourOperation],
                                                      memo: Memo.none,
                                                      timeBounds:nil)
                    /* 用秘鑰給transaction簽名 */
                    try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.public)
                   /* 提交transaction */
                    try self.sdk.transactions.submitTransaction(transaction: transaction) { (response) -> (Void) in
                        switch response {
                        case .success(_):
                            print((trust == "1" ? "信任" : "取消信任") + "success")
                        case .failure(let error):
                            StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-交易", horizonRequestError:error)
                        }
                    }
                }
                catch {
                    // 信任錯誤
                }
            case .failure(let error):
                StellarSDKLog.printHorizonRequestErrorMessage(tag:"func-transactions-查詢", horizonRequestError:error)
            }
        }
    }

6 -- 查詢交易記錄

 // MARK: - 查詢交易記錄
    @objc func requestPaymentsRecord(accountId:String, limit:Int) -> Void {
        sdk.payments.getPayments(forAccount: accountId, order:Order.ascending, limit:limit) { response in
            switch response {
            case .success(let paymentsResponse):
                for payment in paymentsResponse.records {
                   //響應(yīng)操作
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
    

7 -- SHA256加密算法

#import <CommonCrypto/CommonDigest.h>

- (NSString *)SHA256 {
    const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
    NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
    uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0};
    CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
    NSData *out = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
    NSString *hash = [out description];
    hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""];
    hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""];
    hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""];
    return hash;
}

8 -- 劃重點!!! AES256算法!

嚴格地說,AES和Rijndae并不完全一樣(雖然在實際應(yīng)用中二者可以互換),因為Rijndael加密法可以支持更大范圍的區(qū)塊和密鑰長度:AES的區(qū)塊長度固定為128位,密鑰長度則可以是128,192或256位;而Rijndael使用的密鑰和區(qū)塊長度可以是32位的整數(shù)倍,以128位為下限,256位為上限.加密過程中使用的密鑰是由Rijndael密鑰生成方案產(chǎn)生.
大多數(shù)AES計算是在一個特別的有限域完成的.
不帶模式和填充來獲取AES算法的時候,其默認使用AES/ECB/PKCS5Padding(輸入可以不是16字節(jié),也不需要填充向量).

這里有一個巨坑!!:
安卓和ios一同開發(fā)的攻城獅們注意了,這里的AES算法涉及到偏移位padding5和padding7的區(qū)別時候,肯定會讓你們束手無策,這里有一個矛盾點就是ios的系統(tǒng)庫<CommonCrypto/CommonDigest.h>僅僅支持AES256的padding5算法,而安卓的系統(tǒng)庫僅僅支持AES256的padding7算法,所以就會產(chǎn)生一個最大的矛盾點,如果按照各自平臺的偏移位去加解密,那最后的結(jié)果會導(dǎo)致在各自平臺內(nèi)完全可以加解密成功,但是如果跨平臺的話就是因為偏移位問題出現(xiàn),加解密位數(shù)報錯或者加解密直接失敗,這里最后找到的解決辦法是采用KDF算法,密碼偏移輪詢

CCKeyDerivationPBKDF(kCCPBKDF2,                // algorithm算法
                     password.UTF8String,      // password密碼
                     password.length,          // passwordLength密碼的長度
                     salt.bytes,               // salt內(nèi)容
                     salt.length,              // saltLen長度
                     kCCPRFHmacAlgSHA1,        // PRF
                     10000,                    // rounds循環(huán)次數(shù)
                     derivedKey.mutableBytes,  // derivedKey
                     derivedKey.length);       // derivedKeyLen derive:出自
并且需要設(shè)置一個buff密碼偏移
// 密碼偏移
static Byte saltBuff[] = {0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF};
接下來說下aes加解密
// AES256加密
- (NSString *)aes256_encryptWithPassword:(NSString *)pasword aesIV:(NSString *)iv
{
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    NSData *AESData = [self AES128operation:kCCEncrypt
                                       data:data
                                        key:pasword
                                         iv:iv];
    NSString *baseStr_GTM = [self encodeBase64Data:AESData];
    NSLog(@"加密 \n 密碼:%@ \n iv:%@ \n 結(jié)果:%@", pasword, User.aes256_iv, baseStr_GTM);
    return baseStr_GTM;
}
// AES256解密
- (NSString *)aes256_decryptWithPassword:(NSString *)pasword
{
    NSData *baseData = [[NSData alloc]initWithBase64EncodedString:self options:0];
    NSData *AESData = [self AES128operation:kCCDecrypt
                                       data:baseData
                                        key:pasword
                                         iv:User.aes256_iv];
    NSString *decStr = [[NSString alloc] initWithData:AESData encoding:NSUTF8StringEncoding];
    NSLog(@"解密 \n 密碼:%@ \n iv:%@ \n 結(jié)果:%@", pasword, User.aes256_iv, decStr);
    return decStr;
}
AES256算法核心
/**
 *  AES加解密算法
 *  @param operation kCCEncrypt(加密)kCCDecrypt(解密)
 *  @param data      待操作Data數(shù)據(jù)
 *  @param key       key
 *  @param iv        向量
 */
- (NSData *)AES128operation:(CCOperation)operation data:(NSData *)data key:(NSString *)key iv:(NSString *)iv {
    
    char keyPtr[kCCKeySizeAES256 + 1];  //kCCKeySizeAES128是加密位數(shù) 可以替換成256位的
    bzero(keyPtr, sizeof(keyPtr));    
    // IV
    char ivPtr[kCCBlockSizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
    
    size_t bufferSize = [data length] + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    // 設(shè)置加密參數(shù)
    /** 這里設(shè)置的參數(shù)ios默認為CBC加密方式饼煞,如果需要其他加密方式如ECB源葫,在kCCOptionPKCS7Padding這個參數(shù)后邊加上kCCOptionECBMode,即kCCOptionPKCS7Padding | kCCOptionECBMode砖瞧,但是記得修改上邊的偏移量息堂,因為只有CBC模式有偏移量之說 */
    CCCryptorStatus cryptorStatus = CCCrypt(operation,
                                            kCCAlgorithmAES128,
                                            kCCOptionPKCS7Padding,
                                            [[NSString AESKeyForPassword:key] bytes],
                                            kCCKeySizeAES256,
                                            ivPtr,
                                            [data bytes],
                                            [data length],
                                            buffer,
                                            bufferSize,
                                            &numBytesEncrypted);
    if(cryptorStatus == kCCSuccess) {
        NSLog(@"Success");
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    } else {
        NSLog(@"Error");
    }
    free(buffer);
    return nil;
}

還剩余一些掛單之類的Operation今天沒做過多介紹,本文持續(xù)更新中...

2018幣圈跌宕起伏,熊市太多,希望2019大家一起賺到缽盂滿盆一夜暴富...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市块促,隨后出現(xiàn)的幾起案子储矩,更是在濱河造成了極大的恐慌感耙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件持隧,死亡現(xiàn)場離奇詭異即硼,居然都是意外死亡,警方通過查閱死者的電腦和手機屡拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門只酥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呀狼,你說我怎么就攤上這事裂允。” “怎么了哥艇?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵绝编,是天一觀的道長。 經(jīng)常有香客問我貌踏,道長凸丸,這世上最難降的妖魔是什么蚀之? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任斗搞,我火速辦了婚禮力喷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眷昆。我一直安慰自己蜒秤,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布亚斋。 她就那樣靜靜地躺著作媚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪帅刊。 梳的紋絲不亂的頭發(fā)上掂骏,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音厚掷,去河邊找鬼弟灼。 笑死,一個胖子當著我的面吹牛冒黑,可吹牛的內(nèi)容都是我干的田绑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抡爹,長吁一口氣:“原來是場噩夢啊……” “哼掩驱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤欧穴,失蹤者是張志新(化名)和其女友劉穎民逼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涮帘,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡拼苍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了调缨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疮鲫。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弦叶,靈堂內(nèi)的尸體忽然破棺而出俊犯,到底是詐尸還是另有隱情,我是刑警寧澤伤哺,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布燕侠,位于F島的核電站,受9級特大地震影響立莉,放射性物質(zhì)發(fā)生泄漏绢彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一桃序、第九天 我趴在偏房一處隱蔽的房頂上張望杖虾。 院中可真熱鬧烂瘫,春花似錦媒熊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至葛账,卻和暖如春柠衅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背籍琳。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工菲宴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趋急。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓喝峦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呜达。 傳聞我的和親對象是個殘疾皇子谣蠢,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容